mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-08-06 22:24:23 -07:00
Merge branch 'ansible-collections:main' into add_filter_to_remove_empty_fields
This commit is contained in:
commit
800e6d0d68
121 changed files with 1240 additions and 10791 deletions
|
@ -189,6 +189,8 @@ stages:
|
|||
targets:
|
||||
- name: macOS 15.3
|
||||
test: macos/15.3
|
||||
- name: RHEL 10.0
|
||||
test: rhel/10.0
|
||||
- name: RHEL 9.5
|
||||
test: rhel/9.5
|
||||
- name: FreeBSD 14.2
|
||||
|
|
17
.github/BOTMETA.yml
vendored
17
.github/BOTMETA.yml
vendored
|
@ -77,6 +77,8 @@ files:
|
|||
$callbacks/opentelemetry.py:
|
||||
keywords: opentelemetry observability
|
||||
maintainers: v1v
|
||||
$callbacks/print_task.py:
|
||||
maintainers: demonpig
|
||||
$callbacks/say.py:
|
||||
keywords: brew cask darwin homebrew macosx macports osx
|
||||
labels: macos say
|
||||
|
@ -125,8 +127,6 @@ files:
|
|||
maintainers: $team_ansible_core
|
||||
$doc_fragments/:
|
||||
labels: docs_fragments
|
||||
$doc_fragments/clc.py:
|
||||
maintainers: clc-runner russoz
|
||||
$doc_fragments/django.py:
|
||||
maintainers: russoz
|
||||
$doc_fragments/hpe3par.py:
|
||||
|
@ -252,8 +252,6 @@ files:
|
|||
$inventories/scaleway.py:
|
||||
labels: cloud scaleway
|
||||
maintainers: $team_scaleway
|
||||
$inventories/stackpath_compute.py:
|
||||
maintainers: shayrybak
|
||||
$inventories/virtualbox.py: {}
|
||||
$inventories/xen_orchestra.py:
|
||||
maintainers: ddelnano shinuza
|
||||
|
@ -297,9 +295,6 @@ files:
|
|||
$lookups/lastpass.py: {}
|
||||
$lookups/lmdb_kv.py:
|
||||
maintainers: jpmens
|
||||
$lookups/manifold.py:
|
||||
labels: manifold
|
||||
maintainers: galanoff
|
||||
$lookups/merge_variables.py:
|
||||
maintainers: rlenferink m-a-r-k-e alpex8
|
||||
$lookups/onepass:
|
||||
|
@ -387,6 +382,8 @@ files:
|
|||
$module_utils/pipx.py:
|
||||
labels: pipx
|
||||
maintainers: russoz
|
||||
$module_utils/pkg_req.py:
|
||||
maintainers: russoz
|
||||
$module_utils/python_runner.py:
|
||||
maintainers: russoz
|
||||
$module_utils/puppet.py:
|
||||
|
@ -506,8 +503,6 @@ files:
|
|||
maintainers: NickatEpic
|
||||
$modules/cisco_webex.py:
|
||||
maintainers: drew-russell
|
||||
$modules/clc_:
|
||||
maintainers: clc-runner
|
||||
$modules/cloud_init_data_facts.py:
|
||||
maintainers: resmo
|
||||
$modules/cloudflare_dns.py:
|
||||
|
@ -666,8 +661,6 @@ files:
|
|||
maintainers: marns93
|
||||
$modules/hg.py:
|
||||
maintainers: yeukhon
|
||||
$modules/hipchat.py:
|
||||
maintainers: pb8226 shirou
|
||||
$modules/homebrew.py:
|
||||
ignore: ryansb
|
||||
keywords: brew cask darwin homebrew macosx macports osx
|
||||
|
@ -1141,8 +1134,6 @@ files:
|
|||
maintainers: $team_bsd berenddeboer
|
||||
$modules/pritunl_:
|
||||
maintainers: Lowess
|
||||
$modules/profitbricks:
|
||||
maintainers: baldwinSPC
|
||||
$modules/proxmox:
|
||||
keywords: kvm libvirt proxmox qemu
|
||||
labels: proxmox virt
|
||||
|
|
|
@ -44,7 +44,49 @@ If you want to test a PR locally, refer to [our testing guide](https://github.co
|
|||
|
||||
If you find any inconsistencies or places in this document which can be improved, feel free to raise an issue or pull request to fix it.
|
||||
|
||||
## Run sanity, unit or integration tests locally
|
||||
## Run sanity or unit locally (with antsibull-nox)
|
||||
|
||||
The easiest way to run sanity and unit tests locally is to use [antsibull-nox](https://ansible.readthedocs.io/projects/antsibull-nox/).
|
||||
(If you have [nox](https://nox.thea.codes/en/stable/) installed, it will automatically install antsibull-nox in a virtual environment for you.)
|
||||
|
||||
### Sanity tests
|
||||
|
||||
The following commands show how to run ansible-test sanity tests:
|
||||
|
||||
```.bash
|
||||
# Run basic sanity tests for all files in the collection:
|
||||
nox -Re ansible-test-sanity-devel
|
||||
|
||||
# Run basic sanity tests for the given files and directories:
|
||||
nox -Re ansible-test-sanity-devel -- plugins/modules/system/pids.py tests/integration/targets/pids/
|
||||
|
||||
# Run all other sanity tests for all files in the collection:
|
||||
nox -R
|
||||
```
|
||||
|
||||
If you replace `-Re` with `-e`, respectively. leave `-R` away, then the virtual environments will be re-created. The `-R` re-uses them (if they already exist).
|
||||
|
||||
### Unit tests
|
||||
|
||||
The following commands show how to run unit tests:
|
||||
|
||||
```.bash
|
||||
# Run all unit tests:
|
||||
nox -Re ansible-test-units-devel
|
||||
|
||||
# Run all unit tests for one Python version (a lot faster):
|
||||
nox -Re ansible-test-units-devel -- --python 3.13
|
||||
|
||||
# Run a specific unit test (for the nmcli module) for one Python version:
|
||||
nox -Re ansible-test-units-devel -- --python 3.13 tests/unit/plugins/modules/net_tools/test_nmcli.py
|
||||
```
|
||||
|
||||
If you replace `-Re` with `-e`, then the virtual environments will be re-created. The `-R` re-uses them (if they already exist).
|
||||
|
||||
## Run basic sanity, unit or integration tests locally (with ansible-test)
|
||||
|
||||
Instead of using antsibull-nox, you can also run sanity and unit tests with ansible-test directly.
|
||||
This also allows you to run integration tests.
|
||||
|
||||
You have to check out the repository into a specific path structure to be able to run `ansible-test`. The path to the git checkout must end with `.../ansible_collections/community/general`. Please see [our testing guide](https://github.com/ansible/community-docs/blob/main/test_pr_locally_guide.rst) for instructions on how to check out the repository into a correct path structure. The short version of these instructions is:
|
||||
|
||||
|
@ -56,20 +98,27 @@ cd ~/dev/ansible_collections/community/general
|
|||
|
||||
Then you can run `ansible-test` (which is a part of [ansible-core](https://pypi.org/project/ansible-core/)) inside the checkout. The following example commands expect that you have installed Docker or Podman. Note that Podman has only been supported by more recent ansible-core releases. If you are using Docker, the following will work with Ansible 2.9+.
|
||||
|
||||
### Sanity tests
|
||||
### Basic sanity tests
|
||||
|
||||
The following commands show how to run sanity tests:
|
||||
The following commands show how to run basic sanity tests:
|
||||
|
||||
```.bash
|
||||
# Run sanity tests for all files in the collection:
|
||||
# Run basic sanity tests for all files in the collection:
|
||||
ansible-test sanity --docker -v
|
||||
|
||||
# Run sanity tests for the given files and directories:
|
||||
# Run basic sanity tests for the given files and directories:
|
||||
ansible-test sanity --docker -v plugins/modules/system/pids.py tests/integration/targets/pids/
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
|
||||
Note that for running unit tests, you need to install required collections in the same folder structure that `community.general` is checked out in.
|
||||
Right now, you need to install [`community.internal_test_tools`](https://github.com/ansible-collections/community.internal_test_tools).
|
||||
If you want to use the latest version from GitHub, you can run:
|
||||
```
|
||||
git clone https://github.com/ansible-collections/community.internal_test_tools.git ~/dev/ansible_collections/community/internal_test_tools
|
||||
```
|
||||
|
||||
The following commands show how to run unit tests:
|
||||
|
||||
```.bash
|
||||
|
@ -85,6 +134,16 @@ ansible-test units --docker -v --python 3.8 tests/unit/plugins/modules/net_tools
|
|||
|
||||
### Integration tests
|
||||
|
||||
Note that for running integration tests, you need to install required collections in the same folder structure that `community.general` is checked out in.
|
||||
Right now, depending on the test, you need to install [`ansible.posix`](https://github.com/ansible-collections/ansible.posix), [`community.crypto`](https://github.com/ansible-collections/community.crypto), and [`community.docker`](https://github.com/ansible-collections/community.docker):
|
||||
If you want to use the latest versions from GitHub, you can run:
|
||||
```
|
||||
mkdir -p ~/dev/ansible_collections/ansible
|
||||
git clone https://github.com/ansible-collections/ansible.posix.git ~/dev/ansible_collections/ansible/posix
|
||||
git clone https://github.com/ansible-collections/community.crypto.git ~/dev/ansible_collections/community/crypto
|
||||
git clone https://github.com/ansible-collections/community.docker.git ~/dev/ansible_collections/community/docker
|
||||
```
|
||||
|
||||
The following commands show how to run integration tests:
|
||||
|
||||
#### In Docker
|
||||
|
@ -92,8 +151,8 @@ The following commands show how to run integration tests:
|
|||
Integration tests on Docker have the following parameters:
|
||||
- `image_name` (required): The name of the Docker image. To get the list of supported Docker images, run
|
||||
`ansible-test integration --help` and look for _target docker images_.
|
||||
- `test_name` (optional): The name of the integration test.
|
||||
For modules, this equals the short name of the module; for example, `pacman` in case of `community.general.pacman`.
|
||||
- `test_name` (optional): The name of the integration test.
|
||||
For modules, this equals the short name of the module; for example, `pacman` in case of `community.general.pacman`.
|
||||
For plugins, the plugin type is added before the plugin's short name, for example `callback_yaml` for the `community.general.yaml` callback.
|
||||
```.bash
|
||||
# Test all plugins/modules on fedora40
|
||||
|
|
|
@ -54,3 +54,9 @@ doc_fragment = "community.general.proxmox.actiongroup_proxmox"
|
|||
|
||||
[sessions.build_import_check]
|
||||
run_galaxy_importer = true
|
||||
|
||||
[sessions.ansible_test_sanity]
|
||||
include_devel = true
|
||||
|
||||
[sessions.ansible_test_units]
|
||||
include_devel = true
|
||||
|
|
8
changelogs/fragments/10031-pipx-python-version.yml
Normal file
8
changelogs/fragments/10031-pipx-python-version.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
minor_changes:
|
||||
- pipx module_utils - filtering application list by name now happens in the modules (https://github.com/ansible-collections/community.general/pull/10031).
|
||||
- pipx_info - filtering application list by name now happens in the module (https://github.com/ansible-collections/community.general/pull/10031).
|
||||
- >
|
||||
pipx - parameter ``name`` now accepts Python package specifiers
|
||||
(https://github.com/ansible-collections/community.general/issues/7815, https://github.com/ansible-collections/community.general/pull/10031).
|
||||
deprecated_features:
|
||||
- pipx module_utils - function ``make_process_list()`` is deprecated and will be removed in community.general 13.0.0 (https://github.com/ansible-collections/community.general/pull/10031).
|
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- rundeck_acl_policy - ensure that project ACLs are sent to the correct endpoint (https://github.com/ansible-collections/community.general/pull/10097).
|
2
changelogs/fragments/10128-mark-end-of-options.yml
Normal file
2
changelogs/fragments/10128-mark-end-of-options.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- "kdeconfig - allow option values beginning with a dash (https://github.com/ansible-collections/community.general/issues/10127, https://github.com/ansible-collections/community.general/pull/10128)."
|
2
changelogs/fragments/10134-add-autoconnect-options.yml
Normal file
2
changelogs/fragments/10134-add-autoconnect-options.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- nmcli - adds ``autoconnect_priority`` and ``autoconnect_retries`` options to support autoconnect logic (https://github.com/ansible-collections/community.general/pull/10134).
|
2
changelogs/fragments/10136-cache-pickle-json.yml
Normal file
2
changelogs/fragments/10136-cache-pickle-json.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- pickle cache plugin - avoid extra JSON serialization with ansible-core >= 2.19 (https://github.com/ansible-collections/community.general/pull/10136).
|
3
changelogs/fragments/10137-pipx-cg11.yml
Normal file
3
changelogs/fragments/10137-pipx-cg11.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
removed_features:
|
||||
- pipx - module no longer supports ``pipx`` older than 1.7.0 (https://github.com/ansible-collections/community.general/pull/10137).
|
||||
- pipx_info - module no longer supports ``pipx`` older than 1.7.0 (https://github.com/ansible-collections/community.general/pull/10137).
|
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- cobbler_system - fix bug with Cobbler >= 3.4.0 caused by giving more than 2 positional arguments to ``CobblerXMLRPCInterface.get_system_handle()`` (https://github.com/ansible-collections/community.general/issues/8506, https://github.com/ansible-collections/community.general/pull/10145).
|
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- terraform - adds the ``no_color`` parameter, which suppresses or allows color codes in stdout from Terraform commands (https://github.com/ansible-collections/community.general/pull/10154).
|
2
changelogs/fragments/10155-proxmox-bugfix.yml
Normal file
2
changelogs/fragments/10155-proxmox-bugfix.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- "proxmox - fix crash in module when the used on an existing LXC container with ``state=present`` and ``force=true`` (https://github.com/ansible-collections/community.proxmox/pull/91, https://github.com/ansible-collections/community.general/pull/10155)."
|
2
changelogs/fragments/10177-git-config-required.yml
Normal file
2
changelogs/fragments/10177-git-config-required.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- git_config - remove redundant ``required=False`` from ``argument_spec`` (https://github.com/ansible-collections/community.general/pull/10177).
|
19
changelogs/fragments/deprecations.yml
Normal file
19
changelogs/fragments/deprecations.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
removed_features:
|
||||
- "stackpath_compute inventory plugin - the plugin was removed since the company and the service were sunset in June 2024 (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "manifold lookup plugin - the plugin was removed since the company was acquired in 2021 and service was ceased afterwards (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "clc_* modules and doc fragment - the modules were removed since CenturyLink Cloud services went EOL in September 2023 (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "hipchat - the module was removed since the hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020 (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "profitbrick* modules - the modules were removed since the supporting library is unsupported since 2021 (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "redfish_utils module utils - the ``_init_session`` method has been removed (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "django_manage - the ``ack_venv_creation_deprecation`` option has been removed. It had no effect anymore anyway (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "apt_rpm - the ``present`` and ``installed`` states are no longer equivalent to ``latest``, but to ``present_not_latest`` (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "git_config - it is no longer allowed to use ``state=present`` with no value to read the config value. Use the ``community.general.git_config_info`` module instead (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "git_config - the ``list_all`` option has been removed. Use the ``community.general.git_config_info`` module instead (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "mh.mixins.deps module utils - this module utils has been removed. Use the ``deps`` module utils instead (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "mh.mixins.vars module utils - this module utils has been removed. Use ``VarDict`` from the ``vardict`` module utils instead (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "mh.module_helper module utils - ``VarDict`` is now imported from the ``vardict`` module utils and no longer from the removed ``mh.mixins.vars`` module utils (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "mh.module_helper module utils - ``AnsibleModule`` and ``VarsMixin`` are no longer provided (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "mh.module_helper module utils - the attributes ``use_old_vardict`` and ``mute_vardict_deprecation`` from ``ModuleHelper`` have been removed. We suggest to remove them from your modules if you no longer support community.general < 11.0.0 (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
- "module_helper module utils - ``StateMixin``, ``DependencyCtxMgr``, ``VarMeta``, ``VarDict``, and ``VarsMixin`` are no longer provided (https://github.com/ansible-collections/community.general/pull/10126)."
|
||||
breaking_changes:
|
||||
- "proxmox - the default of ``update`` changed from ``false`` to ``true`` (https://github.com/ansible-collections/community.general/pull/10126)."
|
|
@ -38,7 +38,6 @@ But bear in mind that it does not showcase all of MH's features:
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __run__(self):
|
||||
self.vars.original_message = ''
|
||||
|
@ -84,10 +83,6 @@ section above, but there are more elements that will take part in it.
|
|||
|
||||
facts_name = None # used if generating facts, from parameters or otherwise
|
||||
|
||||
# transitional variables for the new VarDict implementation, see information below
|
||||
use_old_vardict = True
|
||||
mute_vardict_deprecation = False
|
||||
|
||||
module = dict(
|
||||
argument_spec=dict(...),
|
||||
# ...
|
||||
|
@ -207,28 +202,14 @@ By using ``self.vars``, you get a central mechanism to access the parameters but
|
|||
As described in :ref:`ansible_collections.community.general.docsite.guide_vardict`, variables in ``VarDict`` have metadata associated to them.
|
||||
One of the attributes in that metadata marks the variable for output, and MH makes use of that to generate the module's return values.
|
||||
|
||||
.. important::
|
||||
.. note::
|
||||
|
||||
The ``VarDict`` feature described was introduced in community.general 7.1.0, but there was a first
|
||||
implementation of it embedded within ``ModuleHelper``.
|
||||
That older implementation is now deprecated and will be removed in community.general 11.0.0.
|
||||
After community.general 7.1.0, MH modules generate a deprecation message about *using the old VarDict*.
|
||||
There are two ways to prevent that from happening:
|
||||
The ``VarDict`` class was introduced in community.general 7.1.0, as part of ``ModuleHelper`` itself.
|
||||
However, it has been factored out to become an utility on its own, described in :ref:`ansible_collections.community.general.docsite.guide_vardict`,
|
||||
and the older implementation was removed in community.general 11.0.0.
|
||||
|
||||
#. Set ``mute_vardict_deprecation = True`` and the deprecation will be silenced. If the module still uses the old ``VarDict``,
|
||||
it will not be able to update to community.general 11.0.0 (Spring 2025) upon its release.
|
||||
#. Set ``use_old_vardict = False`` to make the MH module use the new ``VarDict`` immediately.
|
||||
We strongly recommend you use the new ``VarDict``, for that you make sure to consult its documentation at
|
||||
:ref:`ansible_collections.community.general.docsite.guide_vardict`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
use_old_vardict = False
|
||||
mute_vardict_deprecation = True
|
||||
...
|
||||
|
||||
These two settings are mutually exclusive, but that is not enforced and the behavior when setting both is not specified.
|
||||
Some code might still refer to the class variables ``use_old_vardict`` and ``mute_vardict_deprecation``, used for the transtition to the new
|
||||
implementation but from community.general 11.0.0 onwards they are no longer used and can be safely removed from the code.
|
||||
|
||||
Contrary to new variables created in ``VarDict``, module parameters are not set for output by default.
|
||||
If you want to include some module parameters in the output, list them in the ``output_params`` class variable.
|
||||
|
@ -410,7 +391,6 @@ By using ``StateModuleHelper`` you can make your code like the excerpt from the
|
|||
module = dict(
|
||||
...
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gconftool2_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 10.7.0
|
||||
version: 11.0.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
|
|
@ -100,7 +100,7 @@ plugin_routing:
|
|||
hashi_vault:
|
||||
redirect: community.hashi_vault.hashi_vault
|
||||
manifold:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Company was acquired in 2021 and service was ceased afterwards.
|
||||
nios:
|
||||
|
@ -129,39 +129,39 @@ plugin_routing:
|
|||
cisco_spark:
|
||||
redirect: community.general.cisco_webex
|
||||
clc_alert_policy:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_blueprint_package:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_firewall_policy:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_group:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_loadbalancer:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_modify_server:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_publicip:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_server:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_server_snapshot:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
consul_acl:
|
||||
|
@ -320,7 +320,7 @@ plugin_routing:
|
|||
hetzner_firewall_info:
|
||||
redirect: community.hrobot.firewall_info
|
||||
hipchat:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
hpilo_facts:
|
||||
|
@ -645,23 +645,23 @@ plugin_routing:
|
|||
postgresql_user_obj_stat_info:
|
||||
redirect: community.postgresql.postgresql_user_obj_stat_info
|
||||
profitbricks:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_datacenter:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_nic:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_volume:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_volume_attachments:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
purefa_facts:
|
||||
|
@ -970,7 +970,7 @@ plugin_routing:
|
|||
kubevirt:
|
||||
redirect: community.kubevirt.kubevirt
|
||||
stackpath_compute:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The company and the service were sunset in June 2024.
|
||||
filter:
|
||||
|
|
1
plugins/cache/pickle.py
vendored
1
plugins/cache/pickle.py
vendored
|
@ -56,6 +56,7 @@ class CacheModule(BaseFileCacheModule):
|
|||
"""
|
||||
A caching module backed by pickle files.
|
||||
"""
|
||||
_persistent = False # prevent unnecessary JSON serialization and key munging
|
||||
|
||||
def _load(self, filepath):
|
||||
# Pickle is a binary format
|
||||
|
|
64
plugins/callback/print_task.py
Normal file
64
plugins/callback/print_task.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2025, Max Mitschke <maxmitschke@fastmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: print_task
|
||||
type: aggregate
|
||||
short_description: Prints playbook task snippet to job output
|
||||
description:
|
||||
- This plugin prints the currently executing playbook task to the job output.
|
||||
version_added: 10.7.0
|
||||
requirements:
|
||||
- enable in configuration
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
ansible.cfg: >
|
||||
# Enable plugin
|
||||
[defaults]
|
||||
callbacks_enabled=community.general.print_task
|
||||
'''
|
||||
|
||||
from yaml import load, dump
|
||||
|
||||
try:
|
||||
from yaml import CSafeDumper as SafeDumper
|
||||
from yaml import CSafeLoader as SafeLoader
|
||||
except ImportError:
|
||||
from yaml import SafeDumper, SafeLoader
|
||||
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""
|
||||
This callback module tells you how long your plays ran for.
|
||||
"""
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'community.general.print_task'
|
||||
|
||||
CALLBACK_NEEDS_ENABLED = True
|
||||
|
||||
def __init__(self):
|
||||
super(CallbackModule, self).__init__()
|
||||
self._printed_message = False
|
||||
|
||||
def _print_task(self, task):
|
||||
if hasattr(task, '_ds'):
|
||||
task_snippet = load(str([task._ds.copy()]), Loader=SafeLoader)
|
||||
task_yaml = dump(task_snippet, sort_keys=False, Dumper=SafeDumper)
|
||||
self._display.display(f"\n{task_yaml}\n")
|
||||
self._printed_message = True
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self._printed_message = False
|
||||
|
||||
def v2_runner_on_start(self, host, task):
|
||||
if not self._printed_message:
|
||||
self._print_task(task)
|
|
@ -1,28 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard documentation fragment
|
||||
DOCUMENTATION = r"""
|
||||
options: {}
|
||||
requirements:
|
||||
- requests >= 2.5.0
|
||||
- clc-sdk
|
||||
notes:
|
||||
- To use this module, it is required to set the below environment variables which enables access to the Centurylink Cloud.
|
||||
- E(CLC_V2_API_USERNAME), the account login ID for the Centurylink Cloud.
|
||||
- E(CLC_V2_API_PASSWORD), the account password for the Centurylink Cloud.
|
||||
- Alternatively, the module accepts the API token and account alias. The API token can be generated using the CLC account
|
||||
login and password using the HTTP API call @ U(https://api.ctl.io/v2/authentication/login).
|
||||
- E(CLC_V2_API_TOKEN), the API token generated from U(https://api.ctl.io/v2/authentication/login).
|
||||
- E(CLC_ACCT_ALIAS), the account alias associated with the Centurylink Cloud.
|
||||
- Users can set E(CLC_V2_API_URL) to specify an endpoint for pointing to a different CLC environment.
|
||||
"""
|
|
@ -14,9 +14,6 @@ options:
|
|||
global:
|
||||
description:
|
||||
- The module will pass the C(--global) argument to C(pipx), to execute actions in global scope.
|
||||
- The C(--global) is only available in C(pipx>=1.6.0), so make sure to have a compatible version when using this option.
|
||||
Moreover, a nasty bug with C(--global) was fixed in C(pipx==1.7.0), so it is strongly recommended you used that version
|
||||
or newer.
|
||||
type: bool
|
||||
default: false
|
||||
executable:
|
||||
|
@ -25,10 +22,9 @@ options:
|
|||
- If not specified, the module will use C(python -m pipx) to run the tool, using the same Python interpreter as ansible
|
||||
itself.
|
||||
type: path
|
||||
requirements:
|
||||
- This module requires C(pipx) version 1.7.0 or above. Please note that C(pipx) 1.7.0 requires Python 3.8 or above.
|
||||
notes:
|
||||
- This module requires C(pipx) version 0.16.2.1 or above. From community.general 11.0.0 onwards, the module will require
|
||||
C(pipx>=1.7.0).
|
||||
- Please note that C(pipx) requires Python 3.6 or above.
|
||||
- This module does not install the C(pipx) python package, however that can be easily done with the module M(ansible.builtin.pip).
|
||||
- This module does not require C(pipx) to be in the shell C(PATH), but it must be loadable by Python as a module.
|
||||
- This module will honor C(pipx) environment variables such as but not limited to E(PIPX_HOME) and E(PIPX_BIN_DIR) passed
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020 Shay Rybak <shay.rybak@stackpath.com>
|
||||
# Copyright (c) 2020 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: stackpath_compute
|
||||
short_description: StackPath Edge Computing inventory source
|
||||
version_added: 1.2.0
|
||||
author:
|
||||
- UNKNOWN (@shayrybak)
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Stackpath (the company) ceased its operations in June 2024. The API URL this plugin relies on is not found in DNS.
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
- constructed
|
||||
description:
|
||||
- Get inventory hosts from StackPath Edge Computing.
|
||||
- Uses a YAML configuration file that ends with stackpath_compute.(yml|yaml).
|
||||
options:
|
||||
plugin:
|
||||
description:
|
||||
- A token that ensures this is a source file for the plugin.
|
||||
required: true
|
||||
type: string
|
||||
choices: ['community.general.stackpath_compute']
|
||||
client_id:
|
||||
description:
|
||||
- An OAuth client ID generated from the API Management section of the StackPath customer portal U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
client_secret:
|
||||
description:
|
||||
- An OAuth client secret generated from the API Management section of the StackPath customer portal U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
stack_slugs:
|
||||
description:
|
||||
- A list of Stack slugs to query instances in. If no entry then get instances in all stacks on the account.
|
||||
type: list
|
||||
elements: str
|
||||
use_internal_ip:
|
||||
description:
|
||||
- Whether or not to use internal IP addresses, If false, uses external IP addresses, internal otherwise.
|
||||
- If an instance doesn't have an external IP it will not be returned when this option is set to false.
|
||||
type: bool
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
plugin: community.general.stackpath_compute
|
||||
client_id: my_client_id
|
||||
client_secret: my_client_secret
|
||||
stack_slugs:
|
||||
- my_first_stack_slug
|
||||
- my_other_stack_slug
|
||||
use_internal_ip: false
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import json
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.plugins.inventory import (
|
||||
BaseInventoryPlugin,
|
||||
Constructable,
|
||||
Cacheable
|
||||
)
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
NAME = 'community.general.stackpath_compute'
|
||||
|
||||
def __init__(self):
|
||||
super(InventoryModule, self).__init__()
|
||||
|
||||
# credentials
|
||||
self.client_id = None
|
||||
self.client_secret = None
|
||||
self.stack_slug = None
|
||||
self.api_host = "https://gateway.stackpath.com"
|
||||
self.group_keys = [
|
||||
"stackSlug",
|
||||
"workloadId",
|
||||
"cityCode",
|
||||
"countryCode",
|
||||
"continent",
|
||||
"target",
|
||||
"name",
|
||||
"workloadSlug"
|
||||
]
|
||||
|
||||
def _validate_config(self, config):
|
||||
if config['plugin'] != 'community.general.stackpath_compute':
|
||||
raise AnsibleError("plugin doesn't match this plugin")
|
||||
try:
|
||||
client_id = config['client_id']
|
||||
if len(client_id) != 32:
|
||||
raise AnsibleError("client_id must be 32 characters long")
|
||||
except KeyError:
|
||||
raise AnsibleError("config missing client_id, a required option")
|
||||
try:
|
||||
client_secret = config['client_secret']
|
||||
if len(client_secret) != 64:
|
||||
raise AnsibleError("client_secret must be 64 characters long")
|
||||
except KeyError:
|
||||
raise AnsibleError("config missing client_id, a required option")
|
||||
return True
|
||||
|
||||
def _set_credentials(self):
|
||||
'''
|
||||
:param config_data: contents of the inventory config file
|
||||
'''
|
||||
self.client_id = self.get_option('client_id')
|
||||
self.client_secret = self.get_option('client_secret')
|
||||
|
||||
def _authenticate(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"grant_type": "client_credentials",
|
||||
}
|
||||
)
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
resp = open_url(
|
||||
f"{self.api_host}/identity/v1/oauth2/token",
|
||||
headers=headers,
|
||||
data=payload,
|
||||
method="POST"
|
||||
)
|
||||
status_code = resp.code
|
||||
if status_code == 200:
|
||||
body = resp.read()
|
||||
self.auth_token = json.loads(body)["access_token"]
|
||||
|
||||
def _query(self):
|
||||
results = []
|
||||
workloads = []
|
||||
self._authenticate()
|
||||
for stack_slug in self.stack_slugs:
|
||||
try:
|
||||
workloads = self._stackpath_query_get_list(f"{self.api_host}/workload/v1/stacks/{stack_slug}/workloads")
|
||||
except Exception:
|
||||
raise AnsibleError(f"Failed to get workloads from the StackPath API: {traceback.format_exc()}")
|
||||
for workload in workloads:
|
||||
try:
|
||||
workload_instances = self._stackpath_query_get_list(
|
||||
f"{self.api_host}/workload/v1/stacks/{stack_slug}/workloads/{workload['id']}/instances"
|
||||
)
|
||||
except Exception:
|
||||
raise AnsibleError(f"Failed to get workload instances from the StackPath API: {traceback.format_exc()}")
|
||||
for instance in workload_instances:
|
||||
if instance["phase"] == "RUNNING":
|
||||
instance["stackSlug"] = stack_slug
|
||||
instance["workloadId"] = workload["id"]
|
||||
instance["workloadSlug"] = workload["slug"]
|
||||
instance["cityCode"] = instance["location"]["cityCode"]
|
||||
instance["countryCode"] = instance["location"]["countryCode"]
|
||||
instance["continent"] = instance["location"]["continent"]
|
||||
instance["target"] = instance["metadata"]["labels"]["workload.platform.stackpath.net/target-name"]
|
||||
try:
|
||||
if instance[self.hostname_key]:
|
||||
results.append(instance)
|
||||
except KeyError:
|
||||
pass
|
||||
return results
|
||||
|
||||
def _populate(self, instances):
|
||||
for instance in instances:
|
||||
for group_key in self.group_keys:
|
||||
group = f"{group_key}_{instance[group_key]}"
|
||||
group = group.lower().replace(" ", "_").replace("-", "_")
|
||||
self.inventory.add_group(group)
|
||||
self.inventory.add_host(instance[self.hostname_key],
|
||||
group=group)
|
||||
|
||||
def _stackpath_query_get_list(self, url):
|
||||
self._authenticate()
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.auth_token}",
|
||||
}
|
||||
next_page = True
|
||||
result = []
|
||||
cursor = '-1'
|
||||
while next_page:
|
||||
resp = open_url(
|
||||
f"{url}?page_request.first=10&page_request.after={cursor}",
|
||||
headers=headers,
|
||||
method="GET"
|
||||
)
|
||||
status_code = resp.code
|
||||
if status_code == 200:
|
||||
body = resp.read()
|
||||
body_json = json.loads(body)
|
||||
result.extend(body_json["results"])
|
||||
next_page = body_json["pageInfo"]["hasNextPage"]
|
||||
if next_page:
|
||||
cursor = body_json["pageInfo"]["endCursor"]
|
||||
return result
|
||||
|
||||
def _get_stack_slugs(self, stacks):
|
||||
self.stack_slugs = [stack["slug"] for stack in stacks]
|
||||
|
||||
def verify_file(self, path):
|
||||
'''
|
||||
:param loader: an ansible.parsing.dataloader.DataLoader object
|
||||
:param path: the path to the inventory config file
|
||||
:return the contents of the config file
|
||||
'''
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
if path.endswith(('stackpath_compute.yml', 'stackpath_compute.yaml')):
|
||||
return True
|
||||
display.debug(
|
||||
"stackpath_compute inventory filename must end with \
|
||||
'stackpath_compute.yml' or 'stackpath_compute.yaml'"
|
||||
)
|
||||
return False
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
config = self._read_config_data(path)
|
||||
self._validate_config(config)
|
||||
self._set_credentials()
|
||||
|
||||
# get user specifications
|
||||
self.use_internal_ip = self.get_option('use_internal_ip')
|
||||
if self.use_internal_ip:
|
||||
self.hostname_key = "ipAddress"
|
||||
else:
|
||||
self.hostname_key = "externalIpAddress"
|
||||
|
||||
self.stack_slugs = self.get_option('stack_slugs')
|
||||
if not self.stack_slugs:
|
||||
try:
|
||||
stacks = self._stackpath_query_get_list(f"{self.api_host}/stack/v1/stacks")
|
||||
self._get_stack_slugs(stacks)
|
||||
except Exception:
|
||||
raise AnsibleError(f"Failed to get stack IDs from the Stackpath API: {traceback.format_exc()}")
|
||||
|
||||
cache_key = self.get_cache_key(path)
|
||||
# false when refresh_cache or --flush-cache is used
|
||||
if cache:
|
||||
# get the user-specified directive
|
||||
cache = self.get_option('cache')
|
||||
|
||||
# Generate inventory
|
||||
cache_needs_update = False
|
||||
if cache:
|
||||
try:
|
||||
results = self._cache[cache_key]
|
||||
except KeyError:
|
||||
# if cache expires or cache file doesn't exist
|
||||
cache_needs_update = True
|
||||
|
||||
if not cache or cache_needs_update:
|
||||
results = self._query()
|
||||
|
||||
self._populate(make_unsafe(results))
|
||||
|
||||
# If the cache has expired/doesn't exist or
|
||||
# if refresh_inventory/flush cache is used
|
||||
# when the user is using caching, update the cached inventory
|
||||
try:
|
||||
if cache_needs_update or (not cache and self.get_option('cache')):
|
||||
self._cache[cache_key] = results
|
||||
except Exception:
|
||||
raise AnsibleError(f"Failed to populate data: {traceback.format_exc()}")
|
|
@ -1,282 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Arigato Machine Inc.
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author:
|
||||
- Kyrylo Galanov (!UNKNOWN) <galanoff@gmail.com>
|
||||
name: manifold
|
||||
short_description: get credentials from Manifold.co
|
||||
description:
|
||||
- Retrieves resources' credentials from Manifold.co
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Manifold (the company) has been acquired in 2021 and the services used by this plugin are no longer operational.
|
||||
alternative: There is none.
|
||||
options:
|
||||
_terms:
|
||||
description:
|
||||
- Optional list of resource labels to lookup on Manifold.co. If no resources are specified, all
|
||||
matched resources will be returned.
|
||||
type: list
|
||||
elements: string
|
||||
required: false
|
||||
api_token:
|
||||
description:
|
||||
- manifold API token
|
||||
type: string
|
||||
required: true
|
||||
env:
|
||||
- name: MANIFOLD_API_TOKEN
|
||||
project:
|
||||
description:
|
||||
- The project label you want to get the resource for.
|
||||
type: string
|
||||
required: false
|
||||
team:
|
||||
description:
|
||||
- The team label you want to get the resource for.
|
||||
type: string
|
||||
required: false
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: all available resources
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.manifold', api_token='SecretToken') }}"
|
||||
- name: all available resources for a specific project in specific team
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.manifold', api_token='SecretToken', project='poject-1', team='team-2') }}"
|
||||
- name: two specific resources
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.manifold', 'resource-1', 'resource-2') }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_raw:
|
||||
description:
|
||||
- dictionary of credentials ready to be consumed as environment variables. If multiple resources define
|
||||
the same environment variable(s), the last one returned by the Manifold API will take precedence.
|
||||
type: dict
|
||||
'''
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.module_utils.urls import open_url, ConnectionError, SSLValidationError
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils import six
|
||||
from ansible.utils.display import Display
|
||||
from traceback import format_exception
|
||||
import json
|
||||
import sys
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ManifoldApiClient(object):
|
||||
http_agent = 'python-manifold-ansible-1.0.0'
|
||||
|
||||
def __init__(self, token):
|
||||
self._token = token
|
||||
|
||||
def _make_url(self, api, endpoint):
|
||||
return f'https://api.{api}.manifold.co/v1/{endpoint}'
|
||||
|
||||
def request(self, api, endpoint, *args, **kwargs):
|
||||
"""
|
||||
Send a request to API backend and pre-process a response.
|
||||
:param api: API to send a request to
|
||||
:type api: str
|
||||
:param endpoint: API endpoint to fetch data from
|
||||
:type endpoint: str
|
||||
:param args: other args for open_url
|
||||
:param kwargs: other kwargs for open_url
|
||||
:return: server response. JSON response is automatically deserialized.
|
||||
:rtype: dict | list | str
|
||||
"""
|
||||
|
||||
default_headers = {
|
||||
'Authorization': f"Bearer {self._token}",
|
||||
'Accept': "*/*" # Otherwise server doesn't set content-type header
|
||||
}
|
||||
|
||||
url = self._make_url(api, endpoint)
|
||||
|
||||
headers = default_headers
|
||||
arg_headers = kwargs.pop('headers', None)
|
||||
if arg_headers:
|
||||
headers.update(arg_headers)
|
||||
|
||||
try:
|
||||
display.vvvv(f'manifold lookup connecting to {url}')
|
||||
response = open_url(url, headers=headers, http_agent=self.http_agent, *args, **kwargs)
|
||||
data = response.read()
|
||||
if response.headers.get('content-type') == 'application/json':
|
||||
data = json.loads(data)
|
||||
return data
|
||||
except ValueError:
|
||||
raise ApiError(f'JSON response can\'t be parsed while requesting {url}:\n{data}')
|
||||
except HTTPError as e:
|
||||
raise ApiError(f'Server returned: {e} while requesting {url}:\n{e.read()}')
|
||||
except URLError as e:
|
||||
raise ApiError(f'Failed lookup url for {url} : {e}')
|
||||
except SSLValidationError as e:
|
||||
raise ApiError(f'Error validating the server\'s certificate for {url}: {e}')
|
||||
except ConnectionError as e:
|
||||
raise ApiError(f'Error connecting to {url}: {e}')
|
||||
|
||||
def get_resources(self, team_id=None, project_id=None, label=None):
|
||||
"""
|
||||
Get resources list
|
||||
:param team_id: ID of the Team to filter resources by
|
||||
:type team_id: str
|
||||
:param project_id: ID of the project to filter resources by
|
||||
:type project_id: str
|
||||
:param label: filter resources by a label, returns a list with one or zero elements
|
||||
:type label: str
|
||||
:return: list of resources
|
||||
:rtype: list
|
||||
"""
|
||||
api = 'marketplace'
|
||||
endpoint = 'resources'
|
||||
query_params = {}
|
||||
|
||||
if team_id:
|
||||
query_params['team_id'] = team_id
|
||||
if project_id:
|
||||
query_params['project_id'] = project_id
|
||||
if label:
|
||||
query_params['label'] = label
|
||||
|
||||
if query_params:
|
||||
endpoint += f"?{urlencode(query_params)}"
|
||||
|
||||
return self.request(api, endpoint)
|
||||
|
||||
def get_teams(self, label=None):
|
||||
"""
|
||||
Get teams list
|
||||
:param label: filter teams by a label, returns a list with one or zero elements
|
||||
:type label: str
|
||||
:return: list of teams
|
||||
:rtype: list
|
||||
"""
|
||||
api = 'identity'
|
||||
endpoint = 'teams'
|
||||
data = self.request(api, endpoint)
|
||||
# Label filtering is not supported by API, however this function provides uniform interface
|
||||
if label:
|
||||
data = list(filter(lambda x: x['body']['label'] == label, data))
|
||||
return data
|
||||
|
||||
def get_projects(self, label=None):
|
||||
"""
|
||||
Get projects list
|
||||
:param label: filter projects by a label, returns a list with one or zero elements
|
||||
:type label: str
|
||||
:return: list of projects
|
||||
:rtype: list
|
||||
"""
|
||||
api = 'marketplace'
|
||||
endpoint = 'projects'
|
||||
query_params = {}
|
||||
|
||||
if label:
|
||||
query_params['label'] = label
|
||||
|
||||
if query_params:
|
||||
endpoint += f"?{urlencode(query_params)}"
|
||||
|
||||
return self.request(api, endpoint)
|
||||
|
||||
def get_credentials(self, resource_id):
|
||||
"""
|
||||
Get resource credentials
|
||||
:param resource_id: ID of the resource to filter credentials by
|
||||
:type resource_id: str
|
||||
:return:
|
||||
"""
|
||||
api = 'marketplace'
|
||||
endpoint = f"credentials?{urlencode({'resource_id': resource_id})}"
|
||||
return self.request(api, endpoint)
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
"""
|
||||
:param terms: a list of resources lookups to run.
|
||||
:param variables: ansible variables active at the time of the lookup
|
||||
:param api_token: API token
|
||||
:param project: optional project label
|
||||
:param team: optional team label
|
||||
:return: a dictionary of resources credentials
|
||||
"""
|
||||
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
|
||||
api_token = self.get_option('api_token')
|
||||
project = self.get_option('project')
|
||||
team = self.get_option('team')
|
||||
|
||||
try:
|
||||
labels = terms
|
||||
client = ManifoldApiClient(api_token)
|
||||
|
||||
if team:
|
||||
team_data = client.get_teams(team)
|
||||
if len(team_data) == 0:
|
||||
raise AnsibleError(f"Team '{team}' does not exist")
|
||||
team_id = team_data[0]['id']
|
||||
else:
|
||||
team_id = None
|
||||
|
||||
if project:
|
||||
project_data = client.get_projects(project)
|
||||
if len(project_data) == 0:
|
||||
raise AnsibleError(f"Project '{project}' does not exist")
|
||||
project_id = project_data[0]['id']
|
||||
else:
|
||||
project_id = None
|
||||
|
||||
if len(labels) == 1: # Use server-side filtering if one resource is requested
|
||||
resources_data = client.get_resources(team_id=team_id, project_id=project_id, label=labels[0])
|
||||
else: # Get all resources and optionally filter labels
|
||||
resources_data = client.get_resources(team_id=team_id, project_id=project_id)
|
||||
if labels:
|
||||
resources_data = list(filter(lambda x: x['body']['label'] in labels, resources_data))
|
||||
|
||||
if labels and len(resources_data) < len(labels):
|
||||
fetched_labels = [r['body']['label'] for r in resources_data]
|
||||
not_found_labels = [label for label in labels if label not in fetched_labels]
|
||||
raise AnsibleError(f"Resource(s) {', '.join(not_found_labels)} do not exist")
|
||||
|
||||
credentials = {}
|
||||
cred_map = {}
|
||||
for resource in resources_data:
|
||||
resource_credentials = client.get_credentials(resource['id'])
|
||||
if len(resource_credentials) and resource_credentials[0]['body']['values']:
|
||||
for cred_key, cred_val in six.iteritems(resource_credentials[0]['body']['values']):
|
||||
label = resource['body']['label']
|
||||
if cred_key in credentials:
|
||||
display.warning(f"'{cred_key}' with label '{cred_map[cred_key]}' was replaced by resource data with label '{label}'")
|
||||
credentials[cred_key] = cred_val
|
||||
cred_map[cred_key] = label
|
||||
|
||||
ret = [credentials]
|
||||
return ret
|
||||
except ApiError as e:
|
||||
raise AnsibleError(f'API Error: {e}')
|
||||
except AnsibleError as e:
|
||||
raise e
|
||||
except Exception:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
raise AnsibleError(format_exception(exc_type, exc_value, exc_traceback))
|
|
@ -67,11 +67,9 @@ class _DjangoRunner(PythonRunner):
|
|||
|
||||
class DjangoModuleHelper(ModuleHelper):
|
||||
module = {}
|
||||
use_old_vardict = False
|
||||
django_admin_cmd = None
|
||||
arg_formats = {}
|
||||
django_admin_arg_order = ()
|
||||
use_old_vardict = False
|
||||
_django_args = []
|
||||
_check_mode_arg = ""
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class DependencyCtxMgr(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use plugins/module_utils/deps.py instead.
|
||||
"""
|
||||
def __init__(self, name, msg=None):
|
||||
self.name = name
|
||||
self.msg = msg
|
||||
self.has_it = False
|
||||
self.exc_type = None
|
||||
self.exc_val = None
|
||||
self.exc_tb = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.has_it = exc_type is None
|
||||
self.exc_type = exc_type
|
||||
self.exc_val = exc_val
|
||||
self.exc_tb = exc_tb
|
||||
return not self.has_it
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.msg or str(self.exc_val)
|
|
@ -1,153 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
|
||||
|
||||
class VarMeta(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
|
||||
NOTHING = object()
|
||||
|
||||
def __init__(self, diff=False, output=True, change=None, fact=False):
|
||||
self.init = False
|
||||
self.initial_value = None
|
||||
self.value = None
|
||||
|
||||
self.diff = diff
|
||||
self.change = diff if change is None else change
|
||||
self.output = output
|
||||
self.fact = fact
|
||||
|
||||
def set(self, diff=None, output=None, change=None, fact=None, initial_value=NOTHING):
|
||||
if diff is not None:
|
||||
self.diff = diff
|
||||
if output is not None:
|
||||
self.output = output
|
||||
if change is not None:
|
||||
self.change = change
|
||||
if fact is not None:
|
||||
self.fact = fact
|
||||
if initial_value is not self.NOTHING:
|
||||
self.initial_value = copy.deepcopy(initial_value)
|
||||
|
||||
def set_value(self, value):
|
||||
if not self.init:
|
||||
self.initial_value = copy.deepcopy(value)
|
||||
self.init = True
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
@property
|
||||
def has_changed(self):
|
||||
return self.change and (self.initial_value != self.value)
|
||||
|
||||
@property
|
||||
def diff_result(self):
|
||||
return None if not (self.diff and self.has_changed) else {
|
||||
'before': self.initial_value,
|
||||
'after': self.value,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "<VarMeta: value={0}, initial={1}, diff={2}, output={3}, change={4}>".format(
|
||||
self.value, self.initial_value, self.diff, self.output, self.change
|
||||
)
|
||||
|
||||
|
||||
class VarDict(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._data = dict()
|
||||
self._meta = dict()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._data[item]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.set(key, value)
|
||||
|
||||
def __getattr__(self, item):
|
||||
try:
|
||||
return self._data[item]
|
||||
except KeyError:
|
||||
return getattr(self._data, item)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in ('_data', '_meta'):
|
||||
super(VarDict, self).__setattr__(key, value)
|
||||
else:
|
||||
self.set(key, value)
|
||||
|
||||
def meta(self, name):
|
||||
return self._meta[name]
|
||||
|
||||
def set_meta(self, name, **kwargs):
|
||||
self.meta(name).set(**kwargs)
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
if name in ('_data', '_meta'):
|
||||
raise ValueError("Names _data and _meta are reserved for use by ModuleHelper")
|
||||
self._data[name] = value
|
||||
if name in self._meta:
|
||||
meta = self.meta(name)
|
||||
else:
|
||||
meta = VarMeta(**kwargs)
|
||||
meta.set_value(value)
|
||||
self._meta[name] = meta
|
||||
|
||||
def output(self):
|
||||
return {k: v for k, v in self._data.items() if self.meta(k).output}
|
||||
|
||||
def diff(self):
|
||||
diff_results = [(k, self.meta(k).diff_result) for k in self._data]
|
||||
diff_results = [dr for dr in diff_results if dr[1] is not None]
|
||||
if diff_results:
|
||||
before = dict((dr[0], dr[1]['before']) for dr in diff_results)
|
||||
after = dict((dr[0], dr[1]['after']) for dr in diff_results)
|
||||
return {'before': before, 'after': after}
|
||||
return None
|
||||
|
||||
def facts(self):
|
||||
facts_result = {k: v for k, v in self._data.items() if self._meta[k].fact}
|
||||
return facts_result if facts_result else None
|
||||
|
||||
def change_vars(self):
|
||||
return [v for v in self._data if self.meta(v).change]
|
||||
|
||||
def has_changed(self, v):
|
||||
return self._meta[v].has_changed
|
||||
|
||||
|
||||
class VarsMixin(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
def __init__(self, module=None):
|
||||
self.vars = VarDict()
|
||||
super(VarsMixin, self).__init__(module)
|
||||
|
||||
def update_vars(self, meta=None, **kwargs):
|
||||
if meta is None:
|
||||
meta = {}
|
||||
for k, v in kwargs.items():
|
||||
self.vars.set(k, v, **meta)
|
|
@ -10,13 +10,9 @@ __metaclass__ = type
|
|||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.vardict import VarDict as _NewVarDict # remove "as NewVarDict" in 11.0.0
|
||||
# (TODO: remove AnsibleModule!) pylint: disable-next=unused-import
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import AnsibleModule # noqa: F401 DEPRECATED, remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.vardict import VarDict
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin
|
||||
# (TODO: remove mh.mixins.vars!) pylint: disable-next=unused-import
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarsMixin, VarDict as _OldVarDict # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deprecate_attrs import DeprecateAttrsMixin
|
||||
|
||||
|
||||
|
@ -26,24 +22,11 @@ class ModuleHelper(DeprecateAttrsMixin, ModuleHelperBase):
|
|||
diff_params = ()
|
||||
change_params = ()
|
||||
facts_params = ()
|
||||
use_old_vardict = True # remove in 11.0.0
|
||||
mute_vardict_deprecation = False
|
||||
|
||||
def __init__(self, module=None):
|
||||
if self.use_old_vardict: # remove first half of the if in 11.0.0
|
||||
self.vars = _OldVarDict()
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
if not self.mute_vardict_deprecation:
|
||||
self.module.deprecate(
|
||||
"This class is using the old VarDict from ModuleHelper, which is deprecated. "
|
||||
"Set the class variable use_old_vardict to False and make the necessary adjustments."
|
||||
"The old VarDict class will be removed in community.general 11.0.0",
|
||||
version="11.0.0", collection_name="community.general"
|
||||
)
|
||||
else:
|
||||
self.vars = _NewVarDict()
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
|
||||
self.vars = VarDict()
|
||||
for name, value in self.module.params.items():
|
||||
self.vars.set(
|
||||
name, value,
|
||||
|
@ -66,9 +49,6 @@ class ModuleHelper(DeprecateAttrsMixin, ModuleHelperBase):
|
|||
self.update_vars(meta={"fact": True}, **kwargs)
|
||||
|
||||
def _vars_changed(self):
|
||||
if self.use_old_vardict:
|
||||
return any(self.vars.has_changed(v) for v in self.vars.change_vars())
|
||||
|
||||
return self.vars.has_changed
|
||||
|
||||
def has_changed(self):
|
||||
|
|
|
@ -11,12 +11,8 @@ __metaclass__ = type
|
|||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.module_helper import (
|
||||
ModuleHelper, StateModuleHelper,
|
||||
AnsibleModule # remove in 11.0.0
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyCtxMgr # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import (
|
||||
cause_changes, module_fails_on_exception, check_mode_skip, check_mode_skip_returns,
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarMeta, VarDict, VarsMixin # noqa: F401 remove in 11.0.0
|
||||
|
|
|
@ -71,36 +71,51 @@ def pipx_runner(module, command, **kwargs):
|
|||
return runner
|
||||
|
||||
|
||||
def make_process_list(mod_helper, **kwargs):
|
||||
def process_list(rc, out, err):
|
||||
if not out:
|
||||
return []
|
||||
def _make_entry(venv_name, venv, include_injected, include_deps):
|
||||
entry = {
|
||||
'name': venv_name,
|
||||
'version': venv['metadata']['main_package']['package_version'],
|
||||
'pinned': venv['metadata']['main_package'].get('pinned'),
|
||||
}
|
||||
if include_injected:
|
||||
entry['injected'] = {k: v['package_version'] for k, v in venv['metadata']['injected_packages'].items()}
|
||||
if include_deps:
|
||||
entry['dependencies'] = list(venv['metadata']['main_package']['app_paths_of_dependencies'])
|
||||
return entry
|
||||
|
||||
results = []
|
||||
|
||||
def make_process_dict(include_injected, include_deps=False):
|
||||
def process_dict(rc, out, err):
|
||||
if not out:
|
||||
return {}
|
||||
|
||||
results = {}
|
||||
raw_data = json.loads(out)
|
||||
for venv_name, venv in raw_data['venvs'].items():
|
||||
results[venv_name] = _make_entry(venv_name, venv, include_injected, include_deps)
|
||||
|
||||
return results, raw_data
|
||||
|
||||
return process_dict
|
||||
|
||||
|
||||
def make_process_list(mod_helper, **kwargs):
|
||||
#
|
||||
# ATTENTION!
|
||||
#
|
||||
# The function `make_process_list()` is deprecated and will be removed in community.general 13.0.0
|
||||
#
|
||||
process_dict = make_process_dict(mod_helper, **kwargs)
|
||||
|
||||
def process_list(rc, out, err):
|
||||
res_dict, raw_data = process_dict(rc, out, err)
|
||||
|
||||
if kwargs.get("include_raw"):
|
||||
mod_helper.vars.raw_output = raw_data
|
||||
|
||||
if kwargs["name"]:
|
||||
if kwargs["name"] in raw_data['venvs']:
|
||||
data = {kwargs["name"]: raw_data['venvs'][kwargs["name"]]}
|
||||
else:
|
||||
data = {}
|
||||
else:
|
||||
data = raw_data['venvs']
|
||||
|
||||
for venv_name, venv in data.items():
|
||||
entry = {
|
||||
'name': venv_name,
|
||||
'version': venv['metadata']['main_package']['package_version'],
|
||||
'pinned': venv['metadata']['main_package'].get('pinned'),
|
||||
}
|
||||
if kwargs.get("include_injected"):
|
||||
entry['injected'] = {k: v['package_version'] for k, v in venv['metadata']['injected_packages'].items()}
|
||||
if kwargs.get("include_deps"):
|
||||
entry['dependencies'] = list(venv['metadata']['main_package']['app_paths_of_dependencies'])
|
||||
results.append(entry)
|
||||
|
||||
return results
|
||||
|
||||
return [
|
||||
entry
|
||||
for name, entry in res_dict.items()
|
||||
if name == kwargs.get("name")
|
||||
]
|
||||
return process_list
|
||||
|
|
75
plugins/module_utils/pkg_req.py
Normal file
75
plugins/module_utils/pkg_req.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2025, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.six import raise_from
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
|
||||
|
||||
with deps.declare("packaging"):
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.version import parse as parse_version, InvalidVersion
|
||||
|
||||
|
||||
class PackageRequirement:
|
||||
def __init__(self, module, name):
|
||||
self.module = module
|
||||
self.parsed_name, self.requirement = self._parse_spec(name)
|
||||
|
||||
def _parse_spec(self, name):
|
||||
"""
|
||||
Parse a package name that may include version specifiers using PEP 508.
|
||||
Returns a tuple of (name, requirement) where requirement is of type packaging.requirements.Requirement and it may be None.
|
||||
|
||||
Example inputs:
|
||||
"package"
|
||||
"package>=1.0"
|
||||
"package>=1.0,<2.0"
|
||||
"package[extra]>=1.0"
|
||||
"package[foo,bar]>=1.0,!=1.5"
|
||||
|
||||
:param name: Package name with optional version specifiers and extras
|
||||
:return: Tuple of (name, requirement)
|
||||
:raises ValueError: If the package specification is invalid
|
||||
"""
|
||||
if not name:
|
||||
return name, None
|
||||
|
||||
# Quick check for simple package names
|
||||
if not any(c in name for c in '>=<!~[]'):
|
||||
return name.strip(), None
|
||||
|
||||
deps.validate(self.module, "packaging")
|
||||
|
||||
try:
|
||||
req = Requirement(name)
|
||||
return req.name, req
|
||||
|
||||
except Exception as e:
|
||||
raise_from(ValueError("Invalid package specification for '{0}': {1}".format(name, e)), e)
|
||||
|
||||
def matches_version(self, version):
|
||||
"""
|
||||
Check if a version string fulfills a version specifier.
|
||||
|
||||
:param version: Version string to check
|
||||
:return: True if version fulfills the requirement, False otherwise
|
||||
:raises ValueError: If version is invalid
|
||||
"""
|
||||
# If no spec provided, any version is valid
|
||||
if not self.requirement:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Parse version string
|
||||
ver = parse_version(version)
|
||||
|
||||
return ver in self.requirement.specifier
|
||||
|
||||
except InvalidVersion as e:
|
||||
raise_from(ValueError("Invalid version '{0}': {1}".format(version, e)))
|
|
@ -451,9 +451,6 @@ class RedfishUtils(object):
|
|||
pass
|
||||
return msg, data
|
||||
|
||||
def _init_session(self):
|
||||
self.module.deprecate("Method _init_session is deprecated and will be removed.", version="11.0.0", collection_name="community.general")
|
||||
|
||||
def _get_vendor(self):
|
||||
# If we got the vendor info once, don't get it again
|
||||
if self._vendor is not None:
|
||||
|
|
|
@ -150,7 +150,6 @@ class AndroidSdk(StateModuleHelper):
|
|||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.sdkmanager = AndroidSdkManager(self.module)
|
||||
|
|
|
@ -220,7 +220,6 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
|||
required_if=[('type', 'both', ['requirements_file'])],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
command = 'ansible-galaxy'
|
||||
command_args_formats = dict(
|
||||
|
|
|
@ -41,8 +41,8 @@ options:
|
|||
description:
|
||||
- (IPv4|IPv6|FQDN) of the balancer member to get or to set attributes to. Port number is autodetected and should not
|
||||
be specified here.
|
||||
- If undefined, the M(community.general.apache2_mod_proxy) module will return a members list of dictionaries of all the current
|
||||
balancer pool members' attributes.
|
||||
- If undefined, the M(community.general.apache2_mod_proxy) module returns a members list of dictionaries of all the
|
||||
current balancer pool members' attributes.
|
||||
state:
|
||||
type: list
|
||||
elements: str
|
||||
|
@ -117,18 +117,19 @@ member:
|
|||
type: dict
|
||||
returned: success
|
||||
sample:
|
||||
{"attributes":
|
||||
{"Busy": "0",
|
||||
"Elected": "42",
|
||||
"Factor": "1",
|
||||
"From": "136K",
|
||||
"Load": "0",
|
||||
"Route": null,
|
||||
"RouteRedir": null,
|
||||
"Set": "0",
|
||||
"Status": "Init Ok ",
|
||||
"To": " 47K",
|
||||
"Worker URL": null
|
||||
{
|
||||
"attributes": {
|
||||
"Busy": "0",
|
||||
"Elected": "42",
|
||||
"Factor": "1",
|
||||
"From": "136K",
|
||||
"Load": "0",
|
||||
"Route": null,
|
||||
"RouteRedir": null,
|
||||
"Set": "0",
|
||||
"Status": "Init Ok ",
|
||||
"To": " 47K",
|
||||
"Worker URL": null
|
||||
},
|
||||
"balancer_url": "http://10.10.0.2/balancer-manager/",
|
||||
"host": "10.10.0.20",
|
||||
|
@ -137,10 +138,10 @@ member:
|
|||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"status": {
|
||||
"disabled": false,
|
||||
"drained": false,
|
||||
"hot_standby": false,
|
||||
"ignore_errors": false
|
||||
"disabled": false,
|
||||
"drained": false,
|
||||
"hot_standby": false,
|
||||
"ignore_errors": false
|
||||
}
|
||||
}
|
||||
members:
|
||||
|
@ -149,7 +150,9 @@ members:
|
|||
returned: success
|
||||
type: list
|
||||
sample:
|
||||
[{"attributes": {
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"Busy": "0",
|
||||
"Elected": "42",
|
||||
"Factor": "1",
|
||||
|
@ -161,21 +164,22 @@ members:
|
|||
"Status": "Init Ok ",
|
||||
"To": " 47K",
|
||||
"Worker URL": null
|
||||
},
|
||||
"balancer_url": "http://10.10.0.2/balancer-manager/",
|
||||
"host": "10.10.0.20",
|
||||
"management_url": "http://10.10.0.2/lb/?b=mywsbalancer&w=http://10.10.0.20:8080/ws&nonce=8925436c-79c6-4841-8936-e7d13b79239b",
|
||||
"path": "/ws",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"status": {
|
||||
},
|
||||
"balancer_url": "http://10.10.0.2/balancer-manager/",
|
||||
"host": "10.10.0.20",
|
||||
"management_url": "http://10.10.0.2/lb/?b=mywsbalancer&w=http://10.10.0.20:8080/ws&nonce=8925436c-79c6-4841-8936-e7d13b79239b",
|
||||
"path": "/ws",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"status": {
|
||||
"disabled": false,
|
||||
"drained": false,
|
||||
"hot_standby": false,
|
||||
"ignore_errors": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{"attributes": {
|
||||
{
|
||||
"attributes": {
|
||||
"Busy": "0",
|
||||
"Elected": "42",
|
||||
"Factor": "1",
|
||||
|
@ -187,18 +191,19 @@ members:
|
|||
"Status": "Init Ok ",
|
||||
"To": " 47K",
|
||||
"Worker URL": null
|
||||
},
|
||||
"balancer_url": "http://10.10.0.2/balancer-manager/",
|
||||
"host": "10.10.0.21",
|
||||
"management_url": "http://10.10.0.2/lb/?b=mywsbalancer&w=http://10.10.0.21:8080/ws&nonce=8925436c-79c6-4841-8936-e7d13b79239b",
|
||||
"path": "/ws",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"status": {
|
||||
},
|
||||
"balancer_url": "http://10.10.0.2/balancer-manager/",
|
||||
"host": "10.10.0.21",
|
||||
"management_url": "http://10.10.0.2/lb/?b=mywsbalancer&w=http://10.10.0.21:8080/ws&nonce=8925436c-79c6-4841-8936-e7d13b79239b",
|
||||
"path": "/ws",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"status": {
|
||||
"disabled": false,
|
||||
"drained": false,
|
||||
"hot_standby": false,
|
||||
"ignore_errors": false}
|
||||
"ignore_errors": false
|
||||
}
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
@ -382,7 +387,6 @@ class ApacheModProxy(ModuleHelper):
|
|||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
deps.validate(self.module)
|
||||
|
|
|
@ -35,9 +35,9 @@ options:
|
|||
state:
|
||||
description:
|
||||
- Indicates the desired package state.
|
||||
- Please note that V(present) and V(installed) are equivalent to V(latest) right now. This will change in the future.
|
||||
To simply ensure that a package is installed, without upgrading it, use the V(present_not_latest) state.
|
||||
- The states V(latest) and V(present_not_latest) have been added in community.general 8.6.0.
|
||||
- Please note before community.general 11.0.0, V(present) and V(installed) were equivalent to V(latest).
|
||||
This changed in community.general 11.0.0. Now they are equivalent to V(present_not_latest).
|
||||
choices:
|
||||
- absent
|
||||
- present
|
||||
|
@ -307,17 +307,6 @@ def main():
|
|||
module.fail_json(msg="cannot find /usr/bin/apt-get and/or /usr/bin/rpm")
|
||||
|
||||
p = module.params
|
||||
if p['state'] in ['installed', 'present']:
|
||||
module.deprecate(
|
||||
'state=%s currently behaves unexpectedly by always upgrading to the latest version if'
|
||||
' the package is already installed. This behavior is deprecated and will change in'
|
||||
' community.general 11.0.0. You can use state=latest to explicitly request this behavior'
|
||||
' or state=present_not_latest to explicitly request the behavior that state=%s will have'
|
||||
' in community.general 11.0.0, namely that the package will not be upgraded if it is'
|
||||
' already installed.' % (p['state'], p['state']),
|
||||
version='11.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
modified = False
|
||||
output = ""
|
||||
|
@ -341,7 +330,7 @@ def main():
|
|||
|
||||
packages = p['package']
|
||||
if p['state'] in ['installed', 'present', 'present_not_latest', 'latest']:
|
||||
(m, out) = install_packages(module, packages, allow_upgrade=p['state'] != 'present_not_latest')
|
||||
(m, out) = install_packages(module, packages, allow_upgrade=p['state'] == 'latest')
|
||||
modified = modified or m
|
||||
output += out
|
||||
|
||||
|
|
|
@ -1,338 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_aa_policy
|
||||
short_description: Create or Delete Anti-Affinity Policies at CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create or Delete Anti-Affinity Policies at CenturyLink Cloud.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the Anti-Affinity Policy.
|
||||
type: str
|
||||
required: true
|
||||
location:
|
||||
description:
|
||||
- Datacenter in which the policy lives/should live.
|
||||
type: str
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether to create or delete the policy.
|
||||
type: str
|
||||
required: false
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create AA Policy
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create an Anti Affinity Policy
|
||||
community.general.clc_aa_policy:
|
||||
name: Hammer Time
|
||||
location: UK3
|
||||
state: present
|
||||
register: policy
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: policy
|
||||
|
||||
- name: Delete AA Policy
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Delete an Anti Affinity Policy
|
||||
community.general.clc_aa_policy:
|
||||
name: Hammer Time
|
||||
location: UK3
|
||||
state: absent
|
||||
register: policy
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: policy
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
policy:
|
||||
description: The anti-affinity policy information.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"id":"1a28dd0988984d87b9cd61fa8da15424",
|
||||
"name":"test_aa_policy",
|
||||
"location":"UC1",
|
||||
"links":[
|
||||
{
|
||||
"rel":"self",
|
||||
"href":"/v2/antiAffinityPolicies/wfad/1a28dd0988984d87b9cd61fa8da15424",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"DELETE",
|
||||
"PUT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rel":"location",
|
||||
"href":"/v2/datacenters/wfad/UC1",
|
||||
"id":"uc1",
|
||||
"name":"UC1 - US West (Santa Clara)"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk:
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcAntiAffinityPolicy:
|
||||
|
||||
clc = clc_sdk
|
||||
module = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.module = module
|
||||
self.policy_dict = {}
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'),
|
||||
exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'),
|
||||
exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
location=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
# Module Behavior Goodness
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
p = self.module.params
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
self.policy_dict = self._get_policies_for_datacenter(p)
|
||||
|
||||
if p['state'] == "absent":
|
||||
changed, policy = self._ensure_policy_is_absent(p)
|
||||
else:
|
||||
changed, policy = self._ensure_policy_is_present(p)
|
||||
|
||||
if hasattr(policy, 'data'):
|
||||
policy = policy.data
|
||||
elif hasattr(policy, '__dict__'):
|
||||
policy = policy.__dict__
|
||||
|
||||
self.module.exit_json(changed=changed, policy=policy)
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _get_policies_for_datacenter(self, p):
|
||||
"""
|
||||
Get the Policies for a datacenter by calling the CLC API.
|
||||
:param p: datacenter to get policies from
|
||||
:return: policies in the datacenter
|
||||
"""
|
||||
response = {}
|
||||
|
||||
policies = self.clc.v2.AntiAffinity.GetAll(location=p['location'])
|
||||
|
||||
for policy in policies:
|
||||
response[policy.name] = policy
|
||||
return response
|
||||
|
||||
def _create_policy(self, p):
|
||||
"""
|
||||
Create an Anti Affinity Policy using the CLC API.
|
||||
:param p: datacenter to create policy in
|
||||
:return: response dictionary from the CLC API.
|
||||
"""
|
||||
try:
|
||||
return self.clc.v2.AntiAffinity.Create(
|
||||
name=p['name'],
|
||||
location=p['location'])
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to create anti affinity policy : {0}. {1}'.format(
|
||||
p['name'], ex.response_text
|
||||
))
|
||||
|
||||
def _delete_policy(self, p):
|
||||
"""
|
||||
Delete an Anti Affinity Policy using the CLC API.
|
||||
:param p: datacenter to delete a policy from
|
||||
:return: none
|
||||
"""
|
||||
try:
|
||||
policy = self.policy_dict[p['name']]
|
||||
policy.Delete()
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to delete anti affinity policy : {0}. {1}'.format(
|
||||
p['name'], ex.response_text
|
||||
))
|
||||
|
||||
def _policy_exists(self, policy_name):
|
||||
"""
|
||||
Check to see if an Anti Affinity Policy exists
|
||||
:param policy_name: name of the policy
|
||||
:return: boolean of if the policy exists
|
||||
"""
|
||||
if policy_name in self.policy_dict:
|
||||
return self.policy_dict.get(policy_name)
|
||||
|
||||
return False
|
||||
|
||||
def _ensure_policy_is_absent(self, p):
|
||||
"""
|
||||
Makes sure that a policy is absent
|
||||
:param p: dictionary of policy name
|
||||
:return: tuple of if a deletion occurred and the name of the policy that was deleted
|
||||
"""
|
||||
changed = False
|
||||
if self._policy_exists(policy_name=p['name']):
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
self._delete_policy(p)
|
||||
return changed, None
|
||||
|
||||
def _ensure_policy_is_present(self, p):
|
||||
"""
|
||||
Ensures that a policy is present
|
||||
:param p: dictionary of a policy name
|
||||
:return: tuple of if an addition occurred and the name of the policy that was added
|
||||
"""
|
||||
changed = False
|
||||
policy = self._policy_exists(policy_name=p['name'])
|
||||
if not policy:
|
||||
changed = True
|
||||
policy = None
|
||||
if not self.module.check_mode:
|
||||
policy = self._create_policy(p)
|
||||
return changed, policy
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcAntiAffinityPolicy._define_module_argument_spec(),
|
||||
supports_check_mode=True)
|
||||
clc_aa_policy = ClcAntiAffinityPolicy(module)
|
||||
clc_aa_policy.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,522 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_alert_policy
|
||||
short_description: Create or Delete Alert Policies at CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create or Delete Alert Policies at CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
alias:
|
||||
description:
|
||||
- The alias of your CLC Account.
|
||||
type: str
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- The name of the alert policy. This is mutually exclusive with O(id).
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- The alert policy ID. This is mutually exclusive with O(name).
|
||||
type: str
|
||||
alert_recipients:
|
||||
description:
|
||||
- A list of recipient email IDs to notify the alert. This is required for O(state=present).
|
||||
type: list
|
||||
elements: str
|
||||
metric:
|
||||
description:
|
||||
- The metric on which to measure the condition that will trigger the alert. This is required for O(state=present).
|
||||
type: str
|
||||
choices: ['cpu', 'memory', 'disk']
|
||||
duration:
|
||||
description:
|
||||
- The length of time in minutes that the condition must exceed the threshold. This is required for O(state=present).
|
||||
type: str
|
||||
threshold:
|
||||
description:
|
||||
- The threshold that will trigger the alert when the metric equals or exceeds it. This is required for O(state=present).
|
||||
This number represents a percentage and must be a value between 5.0 - 95.0 that is a multiple of 5.0.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- Whether to create or delete the policy.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create Alert Policy Example
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create an Alert Policy for disk above 80% for 5 minutes
|
||||
community.general.clc_alert_policy:
|
||||
alias: wfad
|
||||
name: 'alert for disk > 80%'
|
||||
alert_recipients:
|
||||
- test1@centurylink.com
|
||||
- test2@centurylink.com
|
||||
metric: 'disk'
|
||||
duration: '00:05:00'
|
||||
threshold: 80
|
||||
state: present
|
||||
register: policy
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug: var=policy
|
||||
|
||||
- name: Delete Alert Policy Example
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Delete an Alert Policy
|
||||
community.general.clc_alert_policy:
|
||||
alias: wfad
|
||||
name: 'alert for disk > 80%'
|
||||
state: absent
|
||||
register: policy
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug: var=policy
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
policy:
|
||||
description: The alert policy information.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "email",
|
||||
"settings": {
|
||||
"recipients": [
|
||||
"user1@domain.com",
|
||||
"user1@domain.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": "ba54ac54a60d4a4f1ed6d48c1ce240a7",
|
||||
"links": [
|
||||
{
|
||||
"href": "/v2/alertPolicies/alias/ba54ac54a60d4a4fb1d6d48c1ce240a7",
|
||||
"rel": "self",
|
||||
"verbs": [
|
||||
"GET",
|
||||
"DELETE",
|
||||
"PUT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "test_alert",
|
||||
"triggers": [
|
||||
{
|
||||
"duration": "00:05:00",
|
||||
"metric": "disk",
|
||||
"threshold": 80.0
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import APIFailedResponse
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcAlertPolicy:
|
||||
|
||||
clc = clc_sdk
|
||||
module = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.module = module
|
||||
self.policy_dict = {}
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(),
|
||||
id=dict(),
|
||||
alias=dict(required=True),
|
||||
alert_recipients=dict(type='list', elements='str'),
|
||||
metric=dict(
|
||||
choices=[
|
||||
'cpu',
|
||||
'memory',
|
||||
'disk']),
|
||||
duration=dict(type='str'),
|
||||
threshold=dict(type='int'),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
mutually_exclusive = [
|
||||
['name', 'id']
|
||||
]
|
||||
return {'argument_spec': argument_spec,
|
||||
'mutually_exclusive': mutually_exclusive}
|
||||
|
||||
# Module Behavior Goodness
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
p = self.module.params
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
self.policy_dict = self._get_alert_policies(p['alias'])
|
||||
|
||||
if p['state'] == 'present':
|
||||
changed, policy = self._ensure_alert_policy_is_present()
|
||||
else:
|
||||
changed, policy = self._ensure_alert_policy_is_absent()
|
||||
|
||||
self.module.exit_json(changed=changed, policy=policy)
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _ensure_alert_policy_is_present(self):
|
||||
"""
|
||||
Ensures that the alert policy is present
|
||||
:return: (changed, policy)
|
||||
changed: A flag representing if anything is modified
|
||||
policy: the created/updated alert policy
|
||||
"""
|
||||
changed = False
|
||||
p = self.module.params
|
||||
policy_name = p.get('name')
|
||||
|
||||
if not policy_name:
|
||||
self.module.fail_json(msg='Policy name is a required')
|
||||
policy = self._alert_policy_exists(policy_name)
|
||||
if not policy:
|
||||
changed = True
|
||||
policy = None
|
||||
if not self.module.check_mode:
|
||||
policy = self._create_alert_policy()
|
||||
else:
|
||||
changed_u, policy = self._ensure_alert_policy_is_updated(policy)
|
||||
if changed_u:
|
||||
changed = True
|
||||
return changed, policy
|
||||
|
||||
def _ensure_alert_policy_is_absent(self):
|
||||
"""
|
||||
Ensures that the alert policy is absent
|
||||
:return: (changed, None)
|
||||
changed: A flag representing if anything is modified
|
||||
"""
|
||||
changed = False
|
||||
p = self.module.params
|
||||
alert_policy_id = p.get('id')
|
||||
alert_policy_name = p.get('name')
|
||||
alias = p.get('alias')
|
||||
if not alert_policy_id and not alert_policy_name:
|
||||
self.module.fail_json(
|
||||
msg='Either alert policy id or policy name is required')
|
||||
if not alert_policy_id and alert_policy_name:
|
||||
alert_policy_id = self._get_alert_policy_id(
|
||||
self.module,
|
||||
alert_policy_name)
|
||||
if alert_policy_id and alert_policy_id in self.policy_dict:
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
self._delete_alert_policy(alias, alert_policy_id)
|
||||
return changed, None
|
||||
|
||||
def _ensure_alert_policy_is_updated(self, alert_policy):
|
||||
"""
|
||||
Ensures the alert policy is updated if anything is changed in the alert policy configuration
|
||||
:param alert_policy: the target alert policy
|
||||
:return: (changed, policy)
|
||||
changed: A flag representing if anything is modified
|
||||
policy: the updated the alert policy
|
||||
"""
|
||||
changed = False
|
||||
p = self.module.params
|
||||
alert_policy_id = alert_policy.get('id')
|
||||
email_list = p.get('alert_recipients')
|
||||
metric = p.get('metric')
|
||||
duration = p.get('duration')
|
||||
threshold = p.get('threshold')
|
||||
policy = alert_policy
|
||||
if (metric and metric != str(alert_policy.get('triggers')[0].get('metric'))) or \
|
||||
(duration and duration != str(alert_policy.get('triggers')[0].get('duration'))) or \
|
||||
(threshold and float(threshold) != float(alert_policy.get('triggers')[0].get('threshold'))):
|
||||
changed = True
|
||||
elif email_list:
|
||||
t_email_list = list(
|
||||
alert_policy.get('actions')[0].get('settings').get('recipients'))
|
||||
if set(email_list) != set(t_email_list):
|
||||
changed = True
|
||||
if changed and not self.module.check_mode:
|
||||
policy = self._update_alert_policy(alert_policy_id)
|
||||
return changed, policy
|
||||
|
||||
def _get_alert_policies(self, alias):
|
||||
"""
|
||||
Get the alert policies for account alias by calling the CLC API.
|
||||
:param alias: the account alias
|
||||
:return: the alert policies for the account alias
|
||||
"""
|
||||
response = {}
|
||||
|
||||
policies = self.clc.v2.API.Call('GET',
|
||||
'/v2/alertPolicies/%s'
|
||||
% alias)
|
||||
|
||||
for policy in policies.get('items'):
|
||||
response[policy.get('id')] = policy
|
||||
return response
|
||||
|
||||
def _create_alert_policy(self):
|
||||
"""
|
||||
Create an alert Policy using the CLC API.
|
||||
:return: response dictionary from the CLC API.
|
||||
"""
|
||||
p = self.module.params
|
||||
alias = p['alias']
|
||||
email_list = p['alert_recipients']
|
||||
metric = p['metric']
|
||||
duration = p['duration']
|
||||
threshold = p['threshold']
|
||||
policy_name = p['name']
|
||||
arguments = json.dumps(
|
||||
{
|
||||
'name': policy_name,
|
||||
'actions': [{
|
||||
'action': 'email',
|
||||
'settings': {
|
||||
'recipients': email_list
|
||||
}
|
||||
}],
|
||||
'triggers': [{
|
||||
'metric': metric,
|
||||
'duration': duration,
|
||||
'threshold': threshold
|
||||
}]
|
||||
}
|
||||
)
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'POST',
|
||||
'/v2/alertPolicies/%s' % alias,
|
||||
arguments)
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg='Unable to create alert policy "{0}". {1}'.format(
|
||||
policy_name, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def _update_alert_policy(self, alert_policy_id):
|
||||
"""
|
||||
Update alert policy using the CLC API.
|
||||
:param alert_policy_id: The clc alert policy id
|
||||
:return: response dictionary from the CLC API.
|
||||
"""
|
||||
p = self.module.params
|
||||
alias = p['alias']
|
||||
email_list = p['alert_recipients']
|
||||
metric = p['metric']
|
||||
duration = p['duration']
|
||||
threshold = p['threshold']
|
||||
policy_name = p['name']
|
||||
arguments = json.dumps(
|
||||
{
|
||||
'name': policy_name,
|
||||
'actions': [{
|
||||
'action': 'email',
|
||||
'settings': {
|
||||
'recipients': email_list
|
||||
}
|
||||
}],
|
||||
'triggers': [{
|
||||
'metric': metric,
|
||||
'duration': duration,
|
||||
'threshold': threshold
|
||||
}]
|
||||
}
|
||||
)
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'PUT', '/v2/alertPolicies/%s/%s' %
|
||||
(alias, alert_policy_id), arguments)
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg='Unable to update alert policy "{0}". {1}'.format(
|
||||
policy_name, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def _delete_alert_policy(self, alias, policy_id):
|
||||
"""
|
||||
Delete an alert policy using the CLC API.
|
||||
:param alias : the account alias
|
||||
:param policy_id: the alert policy id
|
||||
:return: response dictionary from the CLC API.
|
||||
"""
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'DELETE', '/v2/alertPolicies/%s/%s' %
|
||||
(alias, policy_id), None)
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg='Unable to delete alert policy id "{0}". {1}'.format(
|
||||
policy_id, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def _alert_policy_exists(self, policy_name):
|
||||
"""
|
||||
Check to see if an alert policy exists
|
||||
:param policy_name: name of the alert policy
|
||||
:return: boolean of if the policy exists
|
||||
"""
|
||||
result = False
|
||||
for policy_id in self.policy_dict:
|
||||
if self.policy_dict.get(policy_id).get('name') == policy_name:
|
||||
result = self.policy_dict.get(policy_id)
|
||||
return result
|
||||
|
||||
def _get_alert_policy_id(self, module, alert_policy_name):
|
||||
"""
|
||||
retrieves the alert policy id of the account based on the name of the policy
|
||||
:param module: the AnsibleModule object
|
||||
:param alert_policy_name: the alert policy name
|
||||
:return: alert_policy_id: The alert policy id
|
||||
"""
|
||||
alert_policy_id = None
|
||||
for policy_id in self.policy_dict:
|
||||
if self.policy_dict.get(policy_id).get('name') == alert_policy_name:
|
||||
if not alert_policy_id:
|
||||
alert_policy_id = policy_id
|
||||
else:
|
||||
return module.fail_json(
|
||||
msg='multiple alert policies were found with policy name : %s' % alert_policy_name)
|
||||
return alert_policy_id
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
argument_dict = ClcAlertPolicy._define_module_argument_spec()
|
||||
module = AnsibleModule(supports_check_mode=True, **argument_dict)
|
||||
clc_alert_policy = ClcAlertPolicy(module)
|
||||
clc_alert_policy.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,299 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_blueprint_package
|
||||
short_description: Deploys a blue print package on a set of servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to deploy blue print package on a set of servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
server_ids:
|
||||
description:
|
||||
- A list of server IDs to deploy the blue print package.
|
||||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
package_id:
|
||||
description:
|
||||
- The package ID of the blue print.
|
||||
type: str
|
||||
required: true
|
||||
package_params:
|
||||
description:
|
||||
- The dictionary of arguments required to deploy the blue print.
|
||||
type: dict
|
||||
default: {}
|
||||
required: false
|
||||
state:
|
||||
description:
|
||||
- Whether to install or uninstall the package. Currently it supports only V(present) for install action.
|
||||
type: str
|
||||
required: false
|
||||
default: present
|
||||
choices: ['present']
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the tasks to finish before returning.
|
||||
type: str
|
||||
default: 'True'
|
||||
required: false
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
||||
|
||||
- name: Deploy package
|
||||
community.general.clc_blueprint_package:
|
||||
server_ids:
|
||||
- UC1TEST-SERVER1
|
||||
- UC1TEST-SERVER2
|
||||
package_id: 77abb844-579d-478d-3955-c69ab4a7ba1a
|
||||
package_params: {}
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
server_ids:
|
||||
description: The list of server IDs that are changed.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["UC1TEST-SERVER1", "UC1TEST-SERVER2"]
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcBlueprintPackage:
|
||||
|
||||
clc = clc_sdk
|
||||
module = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.module = module
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
p = self.module.params
|
||||
changed = False
|
||||
changed_server_ids = []
|
||||
self._set_clc_credentials_from_env()
|
||||
server_ids = p['server_ids']
|
||||
package_id = p['package_id']
|
||||
package_params = p['package_params']
|
||||
state = p['state']
|
||||
if state == 'present':
|
||||
changed, changed_server_ids, request_list = self.ensure_package_installed(
|
||||
server_ids, package_id, package_params)
|
||||
self._wait_for_requests_to_complete(request_list)
|
||||
self.module.exit_json(changed=changed, server_ids=changed_server_ids)
|
||||
|
||||
@staticmethod
|
||||
def define_argument_spec():
|
||||
"""
|
||||
This function defines the dictionary object required for
|
||||
package module
|
||||
:return: the package dictionary object
|
||||
"""
|
||||
argument_spec = dict(
|
||||
server_ids=dict(type='list', elements='str', required=True),
|
||||
package_id=dict(required=True),
|
||||
package_params=dict(type='dict', default={}),
|
||||
wait=dict(default=True), # @FIXME should be bool?
|
||||
state=dict(default='present', choices=['present'])
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
def ensure_package_installed(self, server_ids, package_id, package_params):
|
||||
"""
|
||||
Ensure the package is installed in the given list of servers
|
||||
:param server_ids: the server list where the package needs to be installed
|
||||
:param package_id: the blueprint package id
|
||||
:param package_params: the package arguments
|
||||
:return: (changed, server_ids, request_list)
|
||||
changed: A flag indicating if a change was made
|
||||
server_ids: The list of servers modified
|
||||
request_list: The list of request objects from clc-sdk
|
||||
"""
|
||||
changed = False
|
||||
request_list = []
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to get servers from CLC')
|
||||
for server in servers:
|
||||
if not self.module.check_mode:
|
||||
request = self.clc_install_package(
|
||||
server,
|
||||
package_id,
|
||||
package_params)
|
||||
request_list.append(request)
|
||||
changed = True
|
||||
return changed, server_ids, request_list
|
||||
|
||||
def clc_install_package(self, server, package_id, package_params):
|
||||
"""
|
||||
Install the package to a given clc server
|
||||
:param server: The server object where the package needs to be installed
|
||||
:param package_id: The blue print package id
|
||||
:param package_params: the required argument dict for the package installation
|
||||
:return: The result object from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = server.ExecutePackage(
|
||||
package_id=package_id,
|
||||
parameters=package_params)
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to install package : {0} to server {1}. {2}'.format(
|
||||
package_id, server.id, ex.message
|
||||
))
|
||||
return result
|
||||
|
||||
def _wait_for_requests_to_complete(self, request_lst):
|
||||
"""
|
||||
Waits until the CLC requests are complete if the wait argument is True
|
||||
:param request_lst: The list of CLC request objects
|
||||
:return: none
|
||||
"""
|
||||
if not self.module.params['wait']:
|
||||
return
|
||||
for request in request_lst:
|
||||
request.WaitUntilComplete()
|
||||
for request_details in request.requests:
|
||||
if request_details.Status() != 'succeeded':
|
||||
self.module.fail_json(
|
||||
msg='Unable to process package install request')
|
||||
|
||||
def _get_servers_from_clc(self, server_list, message):
|
||||
"""
|
||||
Internal function to fetch list of CLC server objects from a list of server ids
|
||||
:param server_list: the list of server ids
|
||||
:param message: the error message to raise if there is any error
|
||||
:return the list of CLC server objects
|
||||
"""
|
||||
try:
|
||||
return self.clc.v2.Servers(server_list).servers
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg=message + ': %s' % ex)
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function
|
||||
:return: None
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcBlueprintPackage.define_argument_spec(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
clc_blueprint_package = ClcBlueprintPackage(module)
|
||||
clc_blueprint_package.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,586 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_firewall_policy
|
||||
short_description: Create/delete/update firewall policies
|
||||
description:
|
||||
- Create or delete or update firewall policies on Centurylink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
location:
|
||||
description:
|
||||
- Target datacenter for the firewall policy.
|
||||
type: str
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether to create or delete the firewall policy.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
source:
|
||||
description:
|
||||
- The list of source addresses for traffic on the originating firewall. This is required when O(state=present).
|
||||
type: list
|
||||
elements: str
|
||||
destination:
|
||||
description:
|
||||
- The list of destination addresses for traffic on the terminating firewall. This is required when O(state=present).
|
||||
type: list
|
||||
elements: str
|
||||
ports:
|
||||
description:
|
||||
- The list of ports associated with the policy. TCP and UDP can take in single ports or port ranges.
|
||||
- "Example: V(['any', 'icmp', 'TCP/123', 'UDP/123', 'TCP/123-456', 'UDP/123-456'])."
|
||||
type: list
|
||||
elements: str
|
||||
firewall_policy_id:
|
||||
description:
|
||||
- ID of the firewall policy. This is required to update or delete an existing firewall policy.
|
||||
type: str
|
||||
source_account_alias:
|
||||
description:
|
||||
- CLC alias for the source account.
|
||||
type: str
|
||||
required: true
|
||||
destination_account_alias:
|
||||
description:
|
||||
- CLC alias for the destination account.
|
||||
type: str
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the provisioning tasks to finish before returning.
|
||||
type: str
|
||||
default: 'True'
|
||||
enabled:
|
||||
description:
|
||||
- Whether the firewall policy is enabled or disabled.
|
||||
type: str
|
||||
choices: ['True', 'False']
|
||||
default: 'True'
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create Firewall Policy
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create / Verify an Firewall Policy at CenturyLink Cloud
|
||||
clc_firewall:
|
||||
source_account_alias: WFAD
|
||||
location: VA1
|
||||
state: present
|
||||
source: 10.128.216.0/24
|
||||
destination: 10.128.216.0/24
|
||||
ports: Any
|
||||
destination_account_alias: WFAD
|
||||
|
||||
- name: Delete Firewall Policy
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Delete an Firewall Policy at CenturyLink Cloud
|
||||
clc_firewall:
|
||||
source_account_alias: WFAD
|
||||
location: VA1
|
||||
state: absent
|
||||
firewall_policy_id: c62105233d7a4231bd2e91b9c791e43e1
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
firewall_policy_id:
|
||||
description: The firewall policy ID.
|
||||
returned: success
|
||||
type: str
|
||||
sample: fc36f1bfd47242e488a9c44346438c05
|
||||
firewall_policy:
|
||||
description: The firewall policy information.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"destination":[
|
||||
"10.1.1.0/24",
|
||||
"10.2.2.0/24"
|
||||
],
|
||||
"destinationAccount":"wfad",
|
||||
"enabled":true,
|
||||
"id":"fc36f1bfd47242e488a9c44346438c05",
|
||||
"links":[
|
||||
{
|
||||
"href":"http://api.ctl.io/v2-experimental/firewallPolicies/wfad/uc1/fc36f1bfd47242e488a9c44346438c05",
|
||||
"rel":"self",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"PUT",
|
||||
"DELETE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ports":[
|
||||
"any"
|
||||
],
|
||||
"source":[
|
||||
"10.1.1.0/24",
|
||||
"10.2.2.0/24"
|
||||
],
|
||||
"status":"active"
|
||||
}
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
||||
from time import sleep
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import APIFailedResponse
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcFirewallPolicy:
|
||||
|
||||
clc = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.clc = clc_sdk
|
||||
self.module = module
|
||||
self.firewall_dict = {}
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
location=dict(required=True),
|
||||
source_account_alias=dict(required=True),
|
||||
destination_account_alias=dict(),
|
||||
firewall_policy_id=dict(),
|
||||
ports=dict(type='list', elements='str'),
|
||||
source=dict(type='list', elements='str'),
|
||||
destination=dict(type='list', elements='str'),
|
||||
wait=dict(default=True), # @FIXME type=bool
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
enabled=dict(default=True, choices=[True, False])
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Execute the main code path, and handle the request
|
||||
:return: none
|
||||
"""
|
||||
changed = False
|
||||
firewall_policy = None
|
||||
location = self.module.params.get('location')
|
||||
source_account_alias = self.module.params.get('source_account_alias')
|
||||
destination_account_alias = self.module.params.get(
|
||||
'destination_account_alias')
|
||||
firewall_policy_id = self.module.params.get('firewall_policy_id')
|
||||
ports = self.module.params.get('ports')
|
||||
source = self.module.params.get('source')
|
||||
destination = self.module.params.get('destination')
|
||||
wait = self.module.params.get('wait')
|
||||
state = self.module.params.get('state')
|
||||
enabled = self.module.params.get('enabled')
|
||||
|
||||
self.firewall_dict = {
|
||||
'location': location,
|
||||
'source_account_alias': source_account_alias,
|
||||
'destination_account_alias': destination_account_alias,
|
||||
'firewall_policy_id': firewall_policy_id,
|
||||
'ports': ports,
|
||||
'source': source,
|
||||
'destination': destination,
|
||||
'wait': wait,
|
||||
'state': state,
|
||||
'enabled': enabled}
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
|
||||
if state == 'absent':
|
||||
changed, firewall_policy_id, firewall_policy = self._ensure_firewall_policy_is_absent(
|
||||
source_account_alias, location, self.firewall_dict)
|
||||
|
||||
elif state == 'present':
|
||||
changed, firewall_policy_id, firewall_policy = self._ensure_firewall_policy_is_present(
|
||||
source_account_alias, location, self.firewall_dict)
|
||||
|
||||
return self.module.exit_json(
|
||||
changed=changed,
|
||||
firewall_policy_id=firewall_policy_id,
|
||||
firewall_policy=firewall_policy)
|
||||
|
||||
@staticmethod
|
||||
def _get_policy_id_from_response(response):
|
||||
"""
|
||||
Method to parse out the policy id from creation response
|
||||
:param response: response from firewall creation API call
|
||||
:return: policy_id: firewall policy id from creation call
|
||||
"""
|
||||
url = response.get('links')[0]['href']
|
||||
path = urlparse(url).path
|
||||
path_list = os.path.split(path)
|
||||
policy_id = path_list[-1]
|
||||
return policy_id
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _ensure_firewall_policy_is_present(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_dict):
|
||||
"""
|
||||
Ensures that a given firewall policy is present
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_dict: dictionary of request parameters for firewall policy
|
||||
:return: (changed, firewall_policy_id, firewall_policy)
|
||||
changed: flag for if a change occurred
|
||||
firewall_policy_id: the firewall policy id that was created/updated
|
||||
firewall_policy: The firewall_policy object
|
||||
"""
|
||||
firewall_policy = None
|
||||
firewall_policy_id = firewall_dict.get('firewall_policy_id')
|
||||
|
||||
if firewall_policy_id is None:
|
||||
if not self.module.check_mode:
|
||||
response = self._create_firewall_policy(
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_dict)
|
||||
firewall_policy_id = self._get_policy_id_from_response(
|
||||
response)
|
||||
changed = True
|
||||
else:
|
||||
firewall_policy = self._get_firewall_policy(
|
||||
source_account_alias, location, firewall_policy_id)
|
||||
if not firewall_policy:
|
||||
return self.module.fail_json(
|
||||
msg='Unable to find the firewall policy id : {0}'.format(
|
||||
firewall_policy_id))
|
||||
changed = self._compare_get_request_with_dict(
|
||||
firewall_policy,
|
||||
firewall_dict)
|
||||
if not self.module.check_mode and changed:
|
||||
self._update_firewall_policy(
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id,
|
||||
firewall_dict)
|
||||
if changed and firewall_policy_id:
|
||||
firewall_policy = self._wait_for_requests_to_complete(
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id)
|
||||
return changed, firewall_policy_id, firewall_policy
|
||||
|
||||
def _ensure_firewall_policy_is_absent(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_dict):
|
||||
"""
|
||||
Ensures that a given firewall policy is removed if present
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_dict: firewall policy to delete
|
||||
:return: (changed, firewall_policy_id, response)
|
||||
changed: flag for if a change occurred
|
||||
firewall_policy_id: the firewall policy id that was deleted
|
||||
response: response from CLC API call
|
||||
"""
|
||||
changed = False
|
||||
response = []
|
||||
firewall_policy_id = firewall_dict.get('firewall_policy_id')
|
||||
result = self._get_firewall_policy(
|
||||
source_account_alias, location, firewall_policy_id)
|
||||
if result:
|
||||
if not self.module.check_mode:
|
||||
response = self._delete_firewall_policy(
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id)
|
||||
changed = True
|
||||
return changed, firewall_policy_id, response
|
||||
|
||||
def _create_firewall_policy(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_dict):
|
||||
"""
|
||||
Creates the firewall policy for the given account alias
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_dict: dictionary of request parameters for firewall policy
|
||||
:return: response from CLC API call
|
||||
"""
|
||||
payload = {
|
||||
'destinationAccount': firewall_dict.get('destination_account_alias'),
|
||||
'source': firewall_dict.get('source'),
|
||||
'destination': firewall_dict.get('destination'),
|
||||
'ports': firewall_dict.get('ports')}
|
||||
try:
|
||||
response = self.clc.v2.API.Call(
|
||||
'POST', '/v2-experimental/firewallPolicies/%s/%s' %
|
||||
(source_account_alias, location), payload)
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg="Unable to create firewall policy. %s" %
|
||||
str(e.response_text))
|
||||
return response
|
||||
|
||||
def _delete_firewall_policy(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id):
|
||||
"""
|
||||
Deletes a given firewall policy for an account alias in a datacenter
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_policy_id: firewall policy id to delete
|
||||
:return: response: response from CLC API call
|
||||
"""
|
||||
try:
|
||||
response = self.clc.v2.API.Call(
|
||||
'DELETE', '/v2-experimental/firewallPolicies/%s/%s/%s' %
|
||||
(source_account_alias, location, firewall_policy_id))
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg="Unable to delete the firewall policy id : {0}. {1}".format(
|
||||
firewall_policy_id, str(e.response_text)))
|
||||
return response
|
||||
|
||||
def _update_firewall_policy(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id,
|
||||
firewall_dict):
|
||||
"""
|
||||
Updates a firewall policy for a given datacenter and account alias
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_policy_id: firewall policy id to update
|
||||
:param firewall_dict: dictionary of request parameters for firewall policy
|
||||
:return: response: response from CLC API call
|
||||
"""
|
||||
try:
|
||||
response = self.clc.v2.API.Call(
|
||||
'PUT',
|
||||
'/v2-experimental/firewallPolicies/%s/%s/%s' %
|
||||
(source_account_alias,
|
||||
location,
|
||||
firewall_policy_id),
|
||||
firewall_dict)
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg="Unable to update the firewall policy id : {0}. {1}".format(
|
||||
firewall_policy_id, str(e.response_text)))
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _compare_get_request_with_dict(response, firewall_dict):
|
||||
"""
|
||||
Helper method to compare the json response for getting the firewall policy with the request parameters
|
||||
:param response: response from the get method
|
||||
:param firewall_dict: dictionary of request parameters for firewall policy
|
||||
:return: changed: Boolean that returns true if there are differences between
|
||||
the response parameters and the playbook parameters
|
||||
"""
|
||||
|
||||
changed = False
|
||||
|
||||
response_dest_account_alias = response.get('destinationAccount')
|
||||
response_enabled = response.get('enabled')
|
||||
response_source = response.get('source')
|
||||
response_dest = response.get('destination')
|
||||
response_ports = response.get('ports')
|
||||
request_dest_account_alias = firewall_dict.get(
|
||||
'destination_account_alias')
|
||||
request_enabled = firewall_dict.get('enabled')
|
||||
if request_enabled is None:
|
||||
request_enabled = True
|
||||
request_source = firewall_dict.get('source')
|
||||
request_dest = firewall_dict.get('destination')
|
||||
request_ports = firewall_dict.get('ports')
|
||||
|
||||
if (
|
||||
response_dest_account_alias and str(response_dest_account_alias) != str(request_dest_account_alias)) or (
|
||||
response_enabled != request_enabled) or (
|
||||
response_source and response_source != request_source) or (
|
||||
response_dest and response_dest != request_dest) or (
|
||||
response_ports and response_ports != request_ports):
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def _get_firewall_policy(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id):
|
||||
"""
|
||||
Get back details for a particular firewall policy
|
||||
:param source_account_alias: the source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_policy_id: id of the firewall policy to get
|
||||
:return: response - The response from CLC API call
|
||||
"""
|
||||
response = None
|
||||
try:
|
||||
response = self.clc.v2.API.Call(
|
||||
'GET', '/v2-experimental/firewallPolicies/%s/%s/%s' %
|
||||
(source_account_alias, location, firewall_policy_id))
|
||||
except APIFailedResponse as e:
|
||||
if e.response_status_code != 404:
|
||||
self.module.fail_json(
|
||||
msg="Unable to fetch the firewall policy with id : {0}. {1}".format(
|
||||
firewall_policy_id, str(e.response_text)))
|
||||
return response
|
||||
|
||||
def _wait_for_requests_to_complete(
|
||||
self,
|
||||
source_account_alias,
|
||||
location,
|
||||
firewall_policy_id,
|
||||
wait_limit=50):
|
||||
"""
|
||||
Waits until the CLC requests are complete if the wait argument is True
|
||||
:param source_account_alias: The source account alias for the firewall policy
|
||||
:param location: datacenter of the firewall policy
|
||||
:param firewall_policy_id: The firewall policy id
|
||||
:param wait_limit: The number of times to check the status for completion
|
||||
:return: the firewall_policy object
|
||||
"""
|
||||
wait = self.module.params.get('wait')
|
||||
count = 0
|
||||
firewall_policy = None
|
||||
while wait:
|
||||
count += 1
|
||||
firewall_policy = self._get_firewall_policy(
|
||||
source_account_alias, location, firewall_policy_id)
|
||||
status = firewall_policy.get('status')
|
||||
if status == 'active' or count > wait_limit:
|
||||
wait = False
|
||||
else:
|
||||
# wait for 2 seconds
|
||||
sleep(2)
|
||||
return firewall_policy
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcFirewallPolicy._define_module_argument_spec(),
|
||||
supports_check_mode=True)
|
||||
|
||||
clc_firewall = ClcFirewallPolicy(module)
|
||||
clc_firewall.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,512 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_group
|
||||
short_description: Create/delete Server Groups at Centurylink Cloud
|
||||
description:
|
||||
- Create or delete Server Groups at Centurylink Centurylink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the Server Group.
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- A description of the Server Group.
|
||||
type: str
|
||||
required: false
|
||||
parent:
|
||||
description:
|
||||
- The parent group of the server group. If parent is not provided, it creates the group at top level.
|
||||
type: str
|
||||
required: false
|
||||
location:
|
||||
description:
|
||||
- Datacenter to create the group in. If location is not provided, the group gets created in the default datacenter associated
|
||||
with the account.
|
||||
type: str
|
||||
required: false
|
||||
state:
|
||||
description:
|
||||
- Whether to create or delete the group.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the tasks to finish before returning.
|
||||
type: bool
|
||||
default: true
|
||||
required: false
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Create a Server Group
|
||||
- name: Create Server Group
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create / Verify a Server Group at CenturyLink Cloud
|
||||
community.general.clc_group:
|
||||
name: My Cool Server Group
|
||||
parent: Default Group
|
||||
state: present
|
||||
register: clc
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: clc
|
||||
|
||||
# Delete a Server Group
|
||||
- name: Delete Server Group
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Delete / Verify Absent a Server Group at CenturyLink Cloud
|
||||
community.general.clc_group:
|
||||
name: My Cool Server Group
|
||||
parent: Default Group
|
||||
state: absent
|
||||
register: clc
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: clc
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
group:
|
||||
description: The group information.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"changeInfo":{
|
||||
"createdBy":"service.wfad",
|
||||
"createdDate":"2015-07-29T18:52:47Z",
|
||||
"modifiedBy":"service.wfad",
|
||||
"modifiedDate":"2015-07-29T18:52:47Z"
|
||||
},
|
||||
"customFields":[
|
||||
|
||||
],
|
||||
"description":"test group",
|
||||
"groups":[
|
||||
|
||||
],
|
||||
"id":"bb5f12a3c6044ae4ad0a03e73ae12cd1",
|
||||
"links":[
|
||||
{
|
||||
"href":"/v2/groups/wfad",
|
||||
"rel":"createGroup",
|
||||
"verbs":[
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad",
|
||||
"rel":"createServer",
|
||||
"verbs":[
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1",
|
||||
"rel":"self",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"PATCH",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/086ac1dfe0b6411989e8d1b77c4065f0",
|
||||
"id":"086ac1dfe0b6411989e8d1b77c4065f0",
|
||||
"rel":"parentGroup"
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/defaults",
|
||||
"rel":"defaults",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/billing",
|
||||
"rel":"billing"
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/archive",
|
||||
"rel":"archiveGroupAction"
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/statistics",
|
||||
"rel":"statistics"
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/upcomingScheduledActivities",
|
||||
"rel":"upcomingScheduledActivities"
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/horizontalAutoscalePolicy",
|
||||
"rel":"horizontalAutoscalePolicyMapping",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"PUT",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/bb5f12a3c6044ae4ad0a03e73ae12cd1/scheduledActivities",
|
||||
"rel":"scheduledActivities",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"POST"
|
||||
]
|
||||
}
|
||||
],
|
||||
"locationId":"UC1",
|
||||
"name":"test group",
|
||||
"status":"active",
|
||||
"type":"default"
|
||||
}
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcGroup(object):
|
||||
|
||||
clc = None
|
||||
root_group = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.clc = clc_sdk
|
||||
self.module = module
|
||||
self.group_dict = {}
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Execute the main code path, and handle the request
|
||||
:return: none
|
||||
"""
|
||||
location = self.module.params.get('location')
|
||||
group_name = self.module.params.get('name')
|
||||
parent_name = self.module.params.get('parent')
|
||||
group_description = self.module.params.get('description')
|
||||
state = self.module.params.get('state')
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
self.group_dict = self._get_group_tree_for_datacenter(
|
||||
datacenter=location)
|
||||
|
||||
if state == "absent":
|
||||
changed, group, requests = self._ensure_group_is_absent(
|
||||
group_name=group_name, parent_name=parent_name)
|
||||
if requests:
|
||||
self._wait_for_requests_to_complete(requests)
|
||||
else:
|
||||
changed, group = self._ensure_group_is_present(
|
||||
group_name=group_name, parent_name=parent_name, group_description=group_description)
|
||||
try:
|
||||
group = group.data
|
||||
except AttributeError:
|
||||
group = group_name
|
||||
self.module.exit_json(changed=changed, group=group)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
description=dict(),
|
||||
parent=dict(),
|
||||
location=dict(),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
wait=dict(type='bool', default=True))
|
||||
|
||||
return argument_spec
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _ensure_group_is_absent(self, group_name, parent_name):
|
||||
"""
|
||||
Ensure that group_name is absent by deleting it if necessary
|
||||
:param group_name: string - the name of the clc server group to delete
|
||||
:param parent_name: string - the name of the parent group for group_name
|
||||
:return: changed, group
|
||||
"""
|
||||
changed = False
|
||||
group = []
|
||||
results = []
|
||||
|
||||
if self._group_exists(group_name=group_name, parent_name=parent_name):
|
||||
if not self.module.check_mode:
|
||||
group.append(group_name)
|
||||
result = self._delete_group(group_name)
|
||||
results.append(result)
|
||||
changed = True
|
||||
return changed, group, results
|
||||
|
||||
def _delete_group(self, group_name):
|
||||
"""
|
||||
Delete the provided server group
|
||||
:param group_name: string - the server group to delete
|
||||
:return: none
|
||||
"""
|
||||
response = None
|
||||
group, parent = self.group_dict.get(group_name)
|
||||
try:
|
||||
response = group.Delete()
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to delete group :{0}. {1}'.format(
|
||||
group_name, ex.response_text
|
||||
))
|
||||
return response
|
||||
|
||||
def _ensure_group_is_present(
|
||||
self,
|
||||
group_name,
|
||||
parent_name,
|
||||
group_description):
|
||||
"""
|
||||
Checks to see if a server group exists, creates it if it doesn't.
|
||||
:param group_name: the name of the group to validate/create
|
||||
:param parent_name: the name of the parent group for group_name
|
||||
:param group_description: a short description of the server group (used when creating)
|
||||
:return: (changed, group) -
|
||||
changed: Boolean- whether a change was made,
|
||||
group: A clc group object for the group
|
||||
"""
|
||||
if not self.root_group:
|
||||
raise AssertionError("Implementation Error: Root Group not set")
|
||||
parent = parent_name if parent_name is not None else self.root_group.name
|
||||
description = group_description
|
||||
changed = False
|
||||
group = group_name
|
||||
|
||||
parent_exists = self._group_exists(group_name=parent, parent_name=None)
|
||||
child_exists = self._group_exists(
|
||||
group_name=group_name,
|
||||
parent_name=parent)
|
||||
|
||||
if parent_exists and child_exists:
|
||||
group, parent = self.group_dict[group_name]
|
||||
changed = False
|
||||
elif parent_exists and not child_exists:
|
||||
if not self.module.check_mode:
|
||||
group = self._create_group(
|
||||
group=group,
|
||||
parent=parent,
|
||||
description=description)
|
||||
changed = True
|
||||
else:
|
||||
self.module.fail_json(
|
||||
msg="parent group: " +
|
||||
parent +
|
||||
" does not exist")
|
||||
|
||||
return changed, group
|
||||
|
||||
def _create_group(self, group, parent, description):
|
||||
"""
|
||||
Create the provided server group
|
||||
:param group: clc_sdk.Group - the group to create
|
||||
:param parent: clc_sdk.Parent - the parent group for {group}
|
||||
:param description: string - a text description of the group
|
||||
:return: clc_sdk.Group - the created group
|
||||
"""
|
||||
response = None
|
||||
(parent, grandparent) = self.group_dict[parent]
|
||||
try:
|
||||
response = parent.Create(name=group, description=description)
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to create group :{0}. {1}'.format(
|
||||
group, ex.response_text))
|
||||
return response
|
||||
|
||||
def _group_exists(self, group_name, parent_name):
|
||||
"""
|
||||
Check to see if a group exists
|
||||
:param group_name: string - the group to check
|
||||
:param parent_name: string - the parent of group_name
|
||||
:return: boolean - whether the group exists
|
||||
"""
|
||||
result = False
|
||||
if group_name in self.group_dict:
|
||||
(group, parent) = self.group_dict[group_name]
|
||||
if parent_name is None or parent_name == parent.name:
|
||||
result = True
|
||||
return result
|
||||
|
||||
def _get_group_tree_for_datacenter(self, datacenter=None):
|
||||
"""
|
||||
Walk the tree of groups for a datacenter
|
||||
:param datacenter: string - the datacenter to walk (ex: 'UC1')
|
||||
:return: a dictionary of groups and parents
|
||||
"""
|
||||
self.root_group = self.clc.v2.Datacenter(
|
||||
location=datacenter).RootGroup()
|
||||
return self._walk_groups_recursive(
|
||||
parent_group=None,
|
||||
child_group=self.root_group)
|
||||
|
||||
def _walk_groups_recursive(self, parent_group, child_group):
|
||||
"""
|
||||
Walk a parent-child tree of groups, starting with the provided child group
|
||||
:param parent_group: clc_sdk.Group - the parent group to start the walk
|
||||
:param child_group: clc_sdk.Group - the child group to start the walk
|
||||
:return: a dictionary of groups and parents
|
||||
"""
|
||||
result = {str(child_group): (child_group, parent_group)}
|
||||
groups = child_group.Subgroups().groups
|
||||
if len(groups) > 0:
|
||||
for group in groups:
|
||||
if group.type != 'default':
|
||||
continue
|
||||
|
||||
result.update(self._walk_groups_recursive(child_group, group))
|
||||
return result
|
||||
|
||||
def _wait_for_requests_to_complete(self, requests_lst):
|
||||
"""
|
||||
Waits until the CLC requests are complete if the wait argument is True
|
||||
:param requests_lst: The list of CLC request objects
|
||||
:return: none
|
||||
"""
|
||||
if not self.module.params['wait']:
|
||||
return
|
||||
for request in requests_lst:
|
||||
request.WaitUntilComplete()
|
||||
for request_details in request.requests:
|
||||
if request_details.Status() != 'succeeded':
|
||||
self.module.fail_json(
|
||||
msg='Unable to process group request')
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcGroup._define_module_argument_spec(),
|
||||
supports_check_mode=True)
|
||||
|
||||
clc_group = ClcGroup(module)
|
||||
clc_group.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,938 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_loadbalancer
|
||||
short_description: Create, Delete shared loadbalancers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create, Delete shared loadbalancers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the loadbalancer.
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- A description for the loadbalancer.
|
||||
type: str
|
||||
alias:
|
||||
description:
|
||||
- The alias of your CLC Account.
|
||||
type: str
|
||||
required: true
|
||||
location:
|
||||
description:
|
||||
- The location of the datacenter where the load balancer resides in.
|
||||
type: str
|
||||
required: true
|
||||
method:
|
||||
description:
|
||||
- The balancing method for the load balancer pool.
|
||||
type: str
|
||||
choices: ['leastConnection', 'roundRobin']
|
||||
persistence:
|
||||
description:
|
||||
- The persistence method for the load balancer.
|
||||
type: str
|
||||
choices: ['standard', 'sticky']
|
||||
port:
|
||||
description:
|
||||
- Port to configure on the public-facing side of the load balancer pool.
|
||||
type: str
|
||||
choices: ['80', '443']
|
||||
nodes:
|
||||
description:
|
||||
- A list of nodes that needs to be added to the load balancer pool.
|
||||
type: list
|
||||
default: []
|
||||
elements: dict
|
||||
status:
|
||||
description:
|
||||
- The status of the loadbalancer.
|
||||
type: str
|
||||
default: enabled
|
||||
choices: ['enabled', 'disabled']
|
||||
state:
|
||||
description:
|
||||
- Whether to create or delete the load balancer pool.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent', 'port_absent', 'nodes_present', 'nodes_absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
||||
- name: Create Loadbalancer
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Actually Create things
|
||||
community.general.clc_loadbalancer:
|
||||
name: test
|
||||
description: test
|
||||
alias: TEST
|
||||
location: WA1
|
||||
port: 443
|
||||
nodes:
|
||||
- ipAddress: 10.11.22.123
|
||||
privatePort: 80
|
||||
state: present
|
||||
|
||||
- name: Add node to an existing loadbalancer pool
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Actually Create things
|
||||
community.general.clc_loadbalancer:
|
||||
name: test
|
||||
description: test
|
||||
alias: TEST
|
||||
location: WA1
|
||||
port: 443
|
||||
nodes:
|
||||
- ipAddress: 10.11.22.234
|
||||
privatePort: 80
|
||||
state: nodes_present
|
||||
|
||||
- name: Remove node from an existing loadbalancer pool
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Actually Create things
|
||||
community.general.clc_loadbalancer:
|
||||
name: test
|
||||
description: test
|
||||
alias: TEST
|
||||
location: WA1
|
||||
port: 443
|
||||
nodes:
|
||||
- ipAddress: 10.11.22.234
|
||||
privatePort: 80
|
||||
state: nodes_absent
|
||||
|
||||
- name: Delete LoadbalancerPool
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Actually Delete things
|
||||
community.general.clc_loadbalancer:
|
||||
name: test
|
||||
description: test
|
||||
alias: TEST
|
||||
location: WA1
|
||||
port: 443
|
||||
nodes:
|
||||
- ipAddress: 10.11.22.123
|
||||
privatePort: 80
|
||||
state: port_absent
|
||||
|
||||
- name: Delete Loadbalancer
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Actually Delete things
|
||||
community.general.clc_loadbalancer:
|
||||
name: test
|
||||
description: test
|
||||
alias: TEST
|
||||
location: WA1
|
||||
port: 443
|
||||
nodes:
|
||||
- ipAddress: 10.11.22.123
|
||||
privatePort: 80
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
loadbalancer:
|
||||
description: The load balancer result object from CLC.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"description":"test-lb",
|
||||
"id":"ab5b18cb81e94ab9925b61d1ca043fb5",
|
||||
"ipAddress":"66.150.174.197",
|
||||
"links":[
|
||||
{
|
||||
"href":"/v2/sharedLoadBalancers/wfad/wa1/ab5b18cb81e94ab9925b61d1ca043fb5",
|
||||
"rel":"self",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"PUT",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/sharedLoadBalancers/wfad/wa1/ab5b18cb81e94ab9925b61d1ca043fb5/pools",
|
||||
"rel":"pools",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"POST"
|
||||
]
|
||||
}
|
||||
],
|
||||
"name":"test-lb",
|
||||
"pools":[
|
||||
|
||||
],
|
||||
"status":"enabled"
|
||||
}
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
from time import sleep
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import APIFailedResponse
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcLoadBalancer:
|
||||
|
||||
clc = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.clc = clc_sdk
|
||||
self.module = module
|
||||
self.lb_dict = {}
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Execute the main code path, and handle the request
|
||||
:return: none
|
||||
"""
|
||||
changed = False
|
||||
result_lb = None
|
||||
loadbalancer_name = self.module.params.get('name')
|
||||
loadbalancer_alias = self.module.params.get('alias')
|
||||
loadbalancer_location = self.module.params.get('location')
|
||||
loadbalancer_description = self.module.params.get('description')
|
||||
loadbalancer_port = self.module.params.get('port')
|
||||
loadbalancer_method = self.module.params.get('method')
|
||||
loadbalancer_persistence = self.module.params.get('persistence')
|
||||
loadbalancer_nodes = self.module.params.get('nodes')
|
||||
loadbalancer_status = self.module.params.get('status')
|
||||
state = self.module.params.get('state')
|
||||
|
||||
if loadbalancer_description is None:
|
||||
loadbalancer_description = loadbalancer_name
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
|
||||
self.lb_dict = self._get_loadbalancer_list(
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location)
|
||||
|
||||
if state == 'present':
|
||||
changed, result_lb, lb_id = self.ensure_loadbalancer_present(
|
||||
name=loadbalancer_name,
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
description=loadbalancer_description,
|
||||
status=loadbalancer_status)
|
||||
if loadbalancer_port:
|
||||
changed, result_pool, pool_id = self.ensure_loadbalancerpool_present(
|
||||
lb_id=lb_id,
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
method=loadbalancer_method,
|
||||
persistence=loadbalancer_persistence,
|
||||
port=loadbalancer_port)
|
||||
|
||||
if loadbalancer_nodes:
|
||||
changed, result_nodes = self.ensure_lbpool_nodes_set(
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
name=loadbalancer_name,
|
||||
port=loadbalancer_port,
|
||||
nodes=loadbalancer_nodes)
|
||||
elif state == 'absent':
|
||||
changed, result_lb = self.ensure_loadbalancer_absent(
|
||||
name=loadbalancer_name,
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location)
|
||||
|
||||
elif state == 'port_absent':
|
||||
changed, result_lb = self.ensure_loadbalancerpool_absent(
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
name=loadbalancer_name,
|
||||
port=loadbalancer_port)
|
||||
|
||||
elif state == 'nodes_present':
|
||||
changed, result_lb = self.ensure_lbpool_nodes_present(
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
name=loadbalancer_name,
|
||||
port=loadbalancer_port,
|
||||
nodes=loadbalancer_nodes)
|
||||
|
||||
elif state == 'nodes_absent':
|
||||
changed, result_lb = self.ensure_lbpool_nodes_absent(
|
||||
alias=loadbalancer_alias,
|
||||
location=loadbalancer_location,
|
||||
name=loadbalancer_name,
|
||||
port=loadbalancer_port,
|
||||
nodes=loadbalancer_nodes)
|
||||
|
||||
self.module.exit_json(changed=changed, loadbalancer=result_lb)
|
||||
|
||||
def ensure_loadbalancer_present(
|
||||
self, name, alias, location, description, status):
|
||||
"""
|
||||
Checks to see if a load balancer exists and creates one if it does not.
|
||||
:param name: Name of loadbalancer
|
||||
:param alias: Alias of account
|
||||
:param location: Datacenter
|
||||
:param description: Description of loadbalancer
|
||||
:param status: Enabled / Disabled
|
||||
:return: (changed, result, lb_id)
|
||||
changed: Boolean whether a change was made
|
||||
result: The result object from the CLC load balancer request
|
||||
lb_id: The load balancer id
|
||||
"""
|
||||
changed = False
|
||||
result = name
|
||||
lb_id = self._loadbalancer_exists(name=name)
|
||||
if not lb_id:
|
||||
if not self.module.check_mode:
|
||||
result = self.create_loadbalancer(name=name,
|
||||
alias=alias,
|
||||
location=location,
|
||||
description=description,
|
||||
status=status)
|
||||
lb_id = result.get('id')
|
||||
changed = True
|
||||
|
||||
return changed, result, lb_id
|
||||
|
||||
def ensure_loadbalancerpool_present(
|
||||
self, lb_id, alias, location, method, persistence, port):
|
||||
"""
|
||||
Checks to see if a load balancer pool exists and creates one if it does not.
|
||||
:param lb_id: The loadbalancer id
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param method: the load balancing method
|
||||
:param persistence: the load balancing persistence type
|
||||
:param port: the port that the load balancer will listen on
|
||||
:return: (changed, group, pool_id) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
pool_id: The string id of the load balancer pool
|
||||
"""
|
||||
changed = False
|
||||
result = port
|
||||
if not lb_id:
|
||||
return changed, None, None
|
||||
pool_id = self._loadbalancerpool_exists(
|
||||
alias=alias,
|
||||
location=location,
|
||||
port=port,
|
||||
lb_id=lb_id)
|
||||
if not pool_id:
|
||||
if not self.module.check_mode:
|
||||
result = self.create_loadbalancerpool(
|
||||
alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
method=method,
|
||||
persistence=persistence,
|
||||
port=port)
|
||||
pool_id = result.get('id')
|
||||
changed = True
|
||||
|
||||
return changed, result, pool_id
|
||||
|
||||
def ensure_loadbalancer_absent(self, name, alias, location):
|
||||
"""
|
||||
Checks to see if a load balancer exists and deletes it if it does
|
||||
:param name: Name of the load balancer
|
||||
:param alias: Alias of account
|
||||
:param location: Datacenter
|
||||
:return: (changed, result)
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API Call
|
||||
"""
|
||||
changed = False
|
||||
result = name
|
||||
lb_exists = self._loadbalancer_exists(name=name)
|
||||
if lb_exists:
|
||||
if not self.module.check_mode:
|
||||
result = self.delete_loadbalancer(alias=alias,
|
||||
location=location,
|
||||
name=name)
|
||||
changed = True
|
||||
return changed, result
|
||||
|
||||
def ensure_loadbalancerpool_absent(self, alias, location, name, port):
|
||||
"""
|
||||
Checks to see if a load balancer pool exists and deletes it if it does
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param name: the name of the load balancer
|
||||
:param port: the port that the load balancer listens on
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
result = None
|
||||
lb_exists = self._loadbalancer_exists(name=name)
|
||||
if lb_exists:
|
||||
lb_id = self._get_loadbalancer_id(name=name)
|
||||
pool_id = self._loadbalancerpool_exists(
|
||||
alias=alias,
|
||||
location=location,
|
||||
port=port,
|
||||
lb_id=lb_id)
|
||||
if pool_id:
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
result = self.delete_loadbalancerpool(
|
||||
alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
pool_id=pool_id)
|
||||
else:
|
||||
result = "Pool doesn't exist"
|
||||
else:
|
||||
result = "LB Doesn't Exist"
|
||||
return changed, result
|
||||
|
||||
def ensure_lbpool_nodes_set(self, alias, location, name, port, nodes):
|
||||
"""
|
||||
Checks to see if the provided list of nodes exist for the pool
|
||||
and set the nodes if any in the list those doesn't exist
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param name: the name of the load balancer
|
||||
:param port: the port that the load balancer will listen on
|
||||
:param nodes: The list of nodes to be updated to the pool
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
result = {}
|
||||
changed = False
|
||||
lb_exists = self._loadbalancer_exists(name=name)
|
||||
if lb_exists:
|
||||
lb_id = self._get_loadbalancer_id(name=name)
|
||||
pool_id = self._loadbalancerpool_exists(
|
||||
alias=alias,
|
||||
location=location,
|
||||
port=port,
|
||||
lb_id=lb_id)
|
||||
if pool_id:
|
||||
nodes_exist = self._loadbalancerpool_nodes_exists(alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
pool_id=pool_id,
|
||||
nodes_to_check=nodes)
|
||||
if not nodes_exist:
|
||||
changed = True
|
||||
result = self.set_loadbalancernodes(alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
pool_id=pool_id,
|
||||
nodes=nodes)
|
||||
else:
|
||||
result = "Pool doesn't exist"
|
||||
else:
|
||||
result = "Load balancer doesn't Exist"
|
||||
return changed, result
|
||||
|
||||
def ensure_lbpool_nodes_present(self, alias, location, name, port, nodes):
|
||||
"""
|
||||
Checks to see if the provided list of nodes exist for the pool and add the missing nodes to the pool
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param name: the name of the load balancer
|
||||
:param port: the port that the load balancer will listen on
|
||||
:param nodes: the list of nodes to be added
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
lb_exists = self._loadbalancer_exists(name=name)
|
||||
if lb_exists:
|
||||
lb_id = self._get_loadbalancer_id(name=name)
|
||||
pool_id = self._loadbalancerpool_exists(
|
||||
alias=alias,
|
||||
location=location,
|
||||
port=port,
|
||||
lb_id=lb_id)
|
||||
if pool_id:
|
||||
changed, result = self.add_lbpool_nodes(alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
pool_id=pool_id,
|
||||
nodes_to_add=nodes)
|
||||
else:
|
||||
result = "Pool doesn't exist"
|
||||
else:
|
||||
result = "Load balancer doesn't Exist"
|
||||
return changed, result
|
||||
|
||||
def ensure_lbpool_nodes_absent(self, alias, location, name, port, nodes):
|
||||
"""
|
||||
Checks to see if the provided list of nodes exist for the pool and removes them if found any
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param name: the name of the load balancer
|
||||
:param port: the port that the load balancer will listen on
|
||||
:param nodes: the list of nodes to be removed
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
lb_exists = self._loadbalancer_exists(name=name)
|
||||
if lb_exists:
|
||||
lb_id = self._get_loadbalancer_id(name=name)
|
||||
pool_id = self._loadbalancerpool_exists(
|
||||
alias=alias,
|
||||
location=location,
|
||||
port=port,
|
||||
lb_id=lb_id)
|
||||
if pool_id:
|
||||
changed, result = self.remove_lbpool_nodes(alias=alias,
|
||||
location=location,
|
||||
lb_id=lb_id,
|
||||
pool_id=pool_id,
|
||||
nodes_to_remove=nodes)
|
||||
else:
|
||||
result = "Pool doesn't exist"
|
||||
else:
|
||||
result = "Load balancer doesn't Exist"
|
||||
return changed, result
|
||||
|
||||
def create_loadbalancer(self, name, alias, location, description, status):
|
||||
"""
|
||||
Create a loadbalancer w/ params
|
||||
:param name: Name of loadbalancer
|
||||
:param alias: Alias of account
|
||||
:param location: Datacenter
|
||||
:param description: Description for loadbalancer to be created
|
||||
:param status: Enabled / Disabled
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = self.clc.v2.API.Call('POST',
|
||||
'/v2/sharedLoadBalancers/%s/%s' % (alias,
|
||||
location),
|
||||
json.dumps({"name": name,
|
||||
"description": description,
|
||||
"status": status}))
|
||||
sleep(1)
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to create load balancer "{0}". {1}'.format(
|
||||
name, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def create_loadbalancerpool(
|
||||
self, alias, location, lb_id, method, persistence, port):
|
||||
"""
|
||||
Creates a pool on the provided load balancer
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param method: the load balancing method
|
||||
:param persistence: the load balancing persistence type
|
||||
:param port: the port that the load balancer will listen on
|
||||
:return: result: The result from the create API call
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'POST', '/v2/sharedLoadBalancers/%s/%s/%s/pools' %
|
||||
(alias, location, lb_id), json.dumps(
|
||||
{
|
||||
"port": port, "method": method, "persistence": persistence
|
||||
}))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to create pool for load balancer id "{0}". {1}'.format(
|
||||
lb_id, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def delete_loadbalancer(self, alias, location, name):
|
||||
"""
|
||||
Delete CLC loadbalancer
|
||||
:param alias: Alias for account
|
||||
:param location: Datacenter
|
||||
:param name: Name of the loadbalancer to delete
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
lb_id = self._get_loadbalancer_id(name=name)
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'DELETE', '/v2/sharedLoadBalancers/%s/%s/%s' %
|
||||
(alias, location, lb_id))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to delete load balancer "{0}". {1}'.format(
|
||||
name, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def delete_loadbalancerpool(self, alias, location, lb_id, pool_id):
|
||||
"""
|
||||
Delete the pool on the provided load balancer
|
||||
:param alias: The account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param pool_id: the id string of the load balancer pool
|
||||
:return: result: The result from the delete API call
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'DELETE', '/v2/sharedLoadBalancers/%s/%s/%s/pools/%s' %
|
||||
(alias, location, lb_id, pool_id))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to delete pool for load balancer id "{0}". {1}'.format(
|
||||
lb_id, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def _get_loadbalancer_id(self, name):
|
||||
"""
|
||||
Retrieves unique ID of loadbalancer
|
||||
:param name: Name of loadbalancer
|
||||
:return: Unique ID of the loadbalancer
|
||||
"""
|
||||
id = None
|
||||
for lb in self.lb_dict:
|
||||
if lb.get('name') == name:
|
||||
id = lb.get('id')
|
||||
return id
|
||||
|
||||
def _get_loadbalancer_list(self, alias, location):
|
||||
"""
|
||||
Retrieve a list of loadbalancers
|
||||
:param alias: Alias for account
|
||||
:param location: Datacenter
|
||||
:return: JSON data for all loadbalancers at datacenter
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = self.clc.v2.API.Call(
|
||||
'GET', '/v2/sharedLoadBalancers/%s/%s' % (alias, location))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to fetch load balancers for account: {0}. {1}'.format(
|
||||
alias, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def _loadbalancer_exists(self, name):
|
||||
"""
|
||||
Verify a loadbalancer exists
|
||||
:param name: Name of loadbalancer
|
||||
:return: False or the ID of the existing loadbalancer
|
||||
"""
|
||||
result = False
|
||||
|
||||
for lb in self.lb_dict:
|
||||
if lb.get('name') == name:
|
||||
result = lb.get('id')
|
||||
return result
|
||||
|
||||
def _loadbalancerpool_exists(self, alias, location, port, lb_id):
|
||||
"""
|
||||
Checks to see if a pool exists on the specified port on the provided load balancer
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param port: the port to check and see if it exists
|
||||
:param lb_id: the id string of the provided load balancer
|
||||
:return: result: The id string of the pool or False
|
||||
"""
|
||||
result = False
|
||||
try:
|
||||
pool_list = self.clc.v2.API.Call(
|
||||
'GET', '/v2/sharedLoadBalancers/%s/%s/%s/pools' %
|
||||
(alias, location, lb_id))
|
||||
except APIFailedResponse as e:
|
||||
return self.module.fail_json(
|
||||
msg='Unable to fetch the load balancer pools for for load balancer id: {0}. {1}'.format(
|
||||
lb_id, str(e.response_text)))
|
||||
for pool in pool_list:
|
||||
if int(pool.get('port')) == int(port):
|
||||
result = pool.get('id')
|
||||
return result
|
||||
|
||||
def _loadbalancerpool_nodes_exists(
|
||||
self, alias, location, lb_id, pool_id, nodes_to_check):
|
||||
"""
|
||||
Checks to see if a set of nodes exists on the specified port on the provided load balancer
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the provided load balancer
|
||||
:param pool_id: the id string of the load balancer pool
|
||||
:param nodes_to_check: the list of nodes to check for
|
||||
:return: result: True / False indicating if the given nodes exist
|
||||
"""
|
||||
result = False
|
||||
nodes = self._get_lbpool_nodes(alias, location, lb_id, pool_id)
|
||||
for node in nodes_to_check:
|
||||
if not node.get('status'):
|
||||
node['status'] = 'enabled'
|
||||
if node in nodes:
|
||||
result = True
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def set_loadbalancernodes(self, alias, location, lb_id, pool_id, nodes):
|
||||
"""
|
||||
Updates nodes to the provided pool
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param pool_id: the id string of the pool
|
||||
:param nodes: a list of dictionaries containing the nodes to set
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
if not lb_id:
|
||||
return result
|
||||
if not self.module.check_mode:
|
||||
try:
|
||||
result = self.clc.v2.API.Call('PUT',
|
||||
'/v2/sharedLoadBalancers/%s/%s/%s/pools/%s/nodes'
|
||||
% (alias, location, lb_id, pool_id), json.dumps(nodes))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to set nodes for the load balancer pool id "{0}". {1}'.format(
|
||||
pool_id, str(e.response_text)))
|
||||
return result
|
||||
|
||||
def add_lbpool_nodes(self, alias, location, lb_id, pool_id, nodes_to_add):
|
||||
"""
|
||||
Add nodes to the provided pool
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param pool_id: the id string of the pool
|
||||
:param nodes_to_add: a list of dictionaries containing the nodes to add
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
result = {}
|
||||
nodes = self._get_lbpool_nodes(alias, location, lb_id, pool_id)
|
||||
for node in nodes_to_add:
|
||||
if not node.get('status'):
|
||||
node['status'] = 'enabled'
|
||||
if node not in nodes:
|
||||
changed = True
|
||||
nodes.append(node)
|
||||
if changed is True and not self.module.check_mode:
|
||||
result = self.set_loadbalancernodes(
|
||||
alias,
|
||||
location,
|
||||
lb_id,
|
||||
pool_id,
|
||||
nodes)
|
||||
return changed, result
|
||||
|
||||
def remove_lbpool_nodes(
|
||||
self, alias, location, lb_id, pool_id, nodes_to_remove):
|
||||
"""
|
||||
Removes nodes from the provided pool
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param pool_id: the id string of the pool
|
||||
:param nodes_to_remove: a list of dictionaries containing the nodes to remove
|
||||
:return: (changed, result) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
result = {}
|
||||
nodes = self._get_lbpool_nodes(alias, location, lb_id, pool_id)
|
||||
for node in nodes_to_remove:
|
||||
if not node.get('status'):
|
||||
node['status'] = 'enabled'
|
||||
if node in nodes:
|
||||
changed = True
|
||||
nodes.remove(node)
|
||||
if changed is True and not self.module.check_mode:
|
||||
result = self.set_loadbalancernodes(
|
||||
alias,
|
||||
location,
|
||||
lb_id,
|
||||
pool_id,
|
||||
nodes)
|
||||
return changed, result
|
||||
|
||||
def _get_lbpool_nodes(self, alias, location, lb_id, pool_id):
|
||||
"""
|
||||
Return the list of nodes available to the provided load balancer pool
|
||||
:param alias: the account alias
|
||||
:param location: the datacenter the load balancer resides in
|
||||
:param lb_id: the id string of the load balancer
|
||||
:param pool_id: the id string of the pool
|
||||
:return: result: The list of nodes
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = self.clc.v2.API.Call('GET',
|
||||
'/v2/sharedLoadBalancers/%s/%s/%s/pools/%s/nodes'
|
||||
% (alias, location, lb_id, pool_id))
|
||||
except APIFailedResponse as e:
|
||||
self.module.fail_json(
|
||||
msg='Unable to fetch list of available nodes for load balancer pool id: {0}. {1}'.format(
|
||||
pool_id, str(e.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def define_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
description=dict(),
|
||||
location=dict(required=True),
|
||||
alias=dict(required=True),
|
||||
port=dict(choices=[80, 443]),
|
||||
method=dict(choices=['leastConnection', 'roundRobin']),
|
||||
persistence=dict(choices=['standard', 'sticky']),
|
||||
nodes=dict(type='list', default=[], elements='dict'),
|
||||
status=dict(default='enabled', choices=['enabled', 'disabled']),
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=[
|
||||
'present',
|
||||
'absent',
|
||||
'port_absent',
|
||||
'nodes_present',
|
||||
'nodes_absent'])
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
module = AnsibleModule(argument_spec=ClcLoadBalancer.define_argument_spec(),
|
||||
supports_check_mode=True)
|
||||
clc_loadbalancer = ClcLoadBalancer(module)
|
||||
clc_loadbalancer.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,961 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_modify_server
|
||||
short_description: Modify servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to modify servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
server_ids:
|
||||
description:
|
||||
- A list of server IDs to modify.
|
||||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
cpu:
|
||||
description:
|
||||
- How many CPUs to update on the server.
|
||||
type: str
|
||||
memory:
|
||||
description:
|
||||
- Memory (in GB) to set to the server.
|
||||
type: str
|
||||
anti_affinity_policy_id:
|
||||
description:
|
||||
- The anti affinity policy ID to be set for a hyper scale server. This is mutually exclusive with O(anti_affinity_policy_name).
|
||||
type: str
|
||||
anti_affinity_policy_name:
|
||||
description:
|
||||
- The anti affinity policy name to be set for a hyper scale server. This is mutually exclusive with O(anti_affinity_policy_id).
|
||||
type: str
|
||||
alert_policy_id:
|
||||
description:
|
||||
- The alert policy ID to be associated to the server. This is mutually exclusive with O(alert_policy_name).
|
||||
type: str
|
||||
alert_policy_name:
|
||||
description:
|
||||
- The alert policy name to be associated to the server. This is mutually exclusive with O(alert_policy_id).
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- The state to insure that the provided resources are in.
|
||||
type: str
|
||||
default: 'present'
|
||||
choices: ['present', 'absent']
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the provisioning tasks to finish before returning.
|
||||
type: bool
|
||||
default: true
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
||||
|
||||
- name: Set the cpu count to 4 on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
cpu: 4
|
||||
state: present
|
||||
|
||||
- name: Set the memory to 8GB on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
memory: 8
|
||||
state: present
|
||||
|
||||
- name: Set the anti affinity policy on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
anti_affinity_policy_name: 'aa_policy'
|
||||
state: present
|
||||
|
||||
- name: Remove the anti affinity policy on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
anti_affinity_policy_name: 'aa_policy'
|
||||
state: absent
|
||||
|
||||
- name: Add the alert policy on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
alert_policy_name: 'alert_policy'
|
||||
state: present
|
||||
|
||||
- name: Remove the alert policy on a server
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
alert_policy_name: 'alert_policy'
|
||||
state: absent
|
||||
|
||||
- name: Ret the memory to 16GB and cpu to 8 core on a lust if servers
|
||||
community.general.clc_modify_server:
|
||||
server_ids:
|
||||
- UC1TESTSVR01
|
||||
- UC1TESTSVR02
|
||||
cpu: 8
|
||||
memory: 16
|
||||
state: present
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
server_ids:
|
||||
description: The list of server IDs that are changed.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["UC1TEST-SVR01", "UC1TEST-SVR02"]
|
||||
servers:
|
||||
description: The list of server objects that are changed.
|
||||
returned: success
|
||||
type: list
|
||||
sample:
|
||||
[
|
||||
{
|
||||
"changeInfo":{
|
||||
"createdBy":"service.wfad",
|
||||
"createdDate":1438196820,
|
||||
"modifiedBy":"service.wfad",
|
||||
"modifiedDate":1438196820
|
||||
},
|
||||
"description":"test-server",
|
||||
"details":{
|
||||
"alertPolicies":[
|
||||
|
||||
],
|
||||
"cpu":1,
|
||||
"customFields":[
|
||||
|
||||
],
|
||||
"diskCount":3,
|
||||
"disks":[
|
||||
{
|
||||
"id":"0:0",
|
||||
"partitionPaths":[
|
||||
|
||||
],
|
||||
"sizeGB":1
|
||||
},
|
||||
{
|
||||
"id":"0:1",
|
||||
"partitionPaths":[
|
||||
|
||||
],
|
||||
"sizeGB":2
|
||||
},
|
||||
{
|
||||
"id":"0:2",
|
||||
"partitionPaths":[
|
||||
|
||||
],
|
||||
"sizeGB":14
|
||||
}
|
||||
],
|
||||
"hostName":"",
|
||||
"inMaintenanceMode":false,
|
||||
"ipAddresses":[
|
||||
{
|
||||
"internal":"10.1.1.1"
|
||||
}
|
||||
],
|
||||
"memoryGB":1,
|
||||
"memoryMB":1024,
|
||||
"partitions":[
|
||||
|
||||
],
|
||||
"powerState":"started",
|
||||
"snapshots":[
|
||||
|
||||
],
|
||||
"storageGB":17
|
||||
},
|
||||
"groupId":"086ac1dfe0b6411989e8d1b77c4065f0",
|
||||
"id":"test-server",
|
||||
"ipaddress":"10.120.45.23",
|
||||
"isTemplate":false,
|
||||
"links":[
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server",
|
||||
"id":"test-server",
|
||||
"rel":"self",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"PATCH",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/groups/wfad/086ac1dfe0b6411989e8d1b77c4065f0",
|
||||
"id":"086ac1dfe0b6411989e8d1b77c4065f0",
|
||||
"rel":"group"
|
||||
},
|
||||
{
|
||||
"href":"/v2/accounts/wfad",
|
||||
"id":"wfad",
|
||||
"rel":"account"
|
||||
},
|
||||
{
|
||||
"href":"/v2/billing/wfad/serverPricing/test-server",
|
||||
"rel":"billing"
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/publicIPAddresses",
|
||||
"rel":"publicIPAddresses",
|
||||
"verbs":[
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/credentials",
|
||||
"rel":"credentials"
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/statistics",
|
||||
"rel":"statistics"
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/upcomingScheduledActivities",
|
||||
"rel":"upcomingScheduledActivities"
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/scheduledActivities",
|
||||
"rel":"scheduledActivities",
|
||||
"verbs":[
|
||||
"GET",
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/capabilities",
|
||||
"rel":"capabilities"
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/alertPolicies",
|
||||
"rel":"alertPolicyMappings",
|
||||
"verbs":[
|
||||
"POST"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/antiAffinityPolicy",
|
||||
"rel":"antiAffinityPolicyMapping",
|
||||
"verbs":[
|
||||
"PUT",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href":"/v2/servers/wfad/test-server/cpuAutoscalePolicy",
|
||||
"rel":"cpuAutoscalePolicyMapping",
|
||||
"verbs":[
|
||||
"PUT",
|
||||
"DELETE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"locationId":"UC1",
|
||||
"name":"test-server",
|
||||
"os":"ubuntu14_64Bit",
|
||||
"osType":"Ubuntu 14 64-bit",
|
||||
"status":"active",
|
||||
"storageType":"standard",
|
||||
"type":"standard"
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
from clc import APIFailedResponse
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcModifyServer:
|
||||
clc = clc_sdk
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.clc = clc_sdk
|
||||
self.module = module
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
self._set_clc_credentials_from_env()
|
||||
|
||||
p = self.module.params
|
||||
cpu = p.get('cpu')
|
||||
memory = p.get('memory')
|
||||
state = p.get('state')
|
||||
if state == 'absent' and (cpu or memory):
|
||||
return self.module.fail_json(
|
||||
msg='\'absent\' state is not supported for \'cpu\' and \'memory\' arguments')
|
||||
|
||||
server_ids = p['server_ids']
|
||||
if not isinstance(server_ids, list):
|
||||
return self.module.fail_json(
|
||||
msg='server_ids needs to be a list of instances to modify: %s' %
|
||||
server_ids)
|
||||
|
||||
(changed, server_dict_array, changed_server_ids) = self._modify_servers(
|
||||
server_ids=server_ids)
|
||||
|
||||
self.module.exit_json(
|
||||
changed=changed,
|
||||
server_ids=changed_server_ids,
|
||||
servers=server_dict_array)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
server_ids=dict(type='list', required=True, elements='str'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
cpu=dict(),
|
||||
memory=dict(),
|
||||
anti_affinity_policy_id=dict(),
|
||||
anti_affinity_policy_name=dict(),
|
||||
alert_policy_id=dict(),
|
||||
alert_policy_name=dict(),
|
||||
wait=dict(type='bool', default=True)
|
||||
)
|
||||
mutually_exclusive = [
|
||||
['anti_affinity_policy_id', 'anti_affinity_policy_name'],
|
||||
['alert_policy_id', 'alert_policy_name']
|
||||
]
|
||||
return {"argument_spec": argument_spec,
|
||||
"mutually_exclusive": mutually_exclusive}
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _get_servers_from_clc(self, server_list, message):
|
||||
"""
|
||||
Internal function to fetch list of CLC server objects from a list of server ids
|
||||
:param server_list: The list of server ids
|
||||
:param message: the error message to throw in case of any error
|
||||
:return the list of CLC server objects
|
||||
"""
|
||||
try:
|
||||
return self.clc.v2.Servers(server_list).servers
|
||||
except CLCException as ex:
|
||||
return self.module.fail_json(msg=message + ': %s' % ex.message)
|
||||
|
||||
def _modify_servers(self, server_ids):
|
||||
"""
|
||||
modify the servers configuration on the provided list
|
||||
:param server_ids: list of servers to modify
|
||||
:return: a list of dictionaries with server information about the servers that were modified
|
||||
"""
|
||||
p = self.module.params
|
||||
state = p.get('state')
|
||||
server_params = {
|
||||
'cpu': p.get('cpu'),
|
||||
'memory': p.get('memory'),
|
||||
'anti_affinity_policy_id': p.get('anti_affinity_policy_id'),
|
||||
'anti_affinity_policy_name': p.get('anti_affinity_policy_name'),
|
||||
'alert_policy_id': p.get('alert_policy_id'),
|
||||
'alert_policy_name': p.get('alert_policy_name'),
|
||||
}
|
||||
changed = False
|
||||
server_changed = False
|
||||
aa_changed = False
|
||||
ap_changed = False
|
||||
server_dict_array = []
|
||||
result_server_ids = []
|
||||
request_list = []
|
||||
changed_servers = []
|
||||
|
||||
if not isinstance(server_ids, list) or len(server_ids) < 1:
|
||||
return self.module.fail_json(
|
||||
msg='server_ids should be a list of servers, aborting')
|
||||
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
for server in servers:
|
||||
if state == 'present':
|
||||
server_changed, server_result = self._ensure_server_config(
|
||||
server, server_params)
|
||||
if server_result:
|
||||
request_list.append(server_result)
|
||||
aa_changed = self._ensure_aa_policy_present(
|
||||
server,
|
||||
server_params)
|
||||
ap_changed = self._ensure_alert_policy_present(
|
||||
server,
|
||||
server_params)
|
||||
elif state == 'absent':
|
||||
aa_changed = self._ensure_aa_policy_absent(
|
||||
server,
|
||||
server_params)
|
||||
ap_changed = self._ensure_alert_policy_absent(
|
||||
server,
|
||||
server_params)
|
||||
if server_changed or aa_changed or ap_changed:
|
||||
changed_servers.append(server)
|
||||
changed = True
|
||||
|
||||
self._wait_for_requests(self.module, request_list)
|
||||
self._refresh_servers(self.module, changed_servers)
|
||||
|
||||
for server in changed_servers:
|
||||
server_dict_array.append(server.data)
|
||||
result_server_ids.append(server.id)
|
||||
|
||||
return changed, server_dict_array, result_server_ids
|
||||
|
||||
def _ensure_server_config(
|
||||
self, server, server_params):
|
||||
"""
|
||||
ensures the server is updated with the provided cpu and memory
|
||||
:param server: the CLC server object
|
||||
:param server_params: the dictionary of server parameters
|
||||
:return: (changed, group) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
cpu = server_params.get('cpu')
|
||||
memory = server_params.get('memory')
|
||||
changed = False
|
||||
result = None
|
||||
|
||||
if not cpu:
|
||||
cpu = server.cpu
|
||||
if not memory:
|
||||
memory = server.memory
|
||||
if memory != server.memory or cpu != server.cpu:
|
||||
if not self.module.check_mode:
|
||||
result = self._modify_clc_server(
|
||||
self.clc,
|
||||
self.module,
|
||||
server.id,
|
||||
cpu,
|
||||
memory)
|
||||
changed = True
|
||||
return changed, result
|
||||
|
||||
@staticmethod
|
||||
def _modify_clc_server(clc, module, server_id, cpu, memory):
|
||||
"""
|
||||
Modify the memory or CPU of a clc server.
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param server_id: id of the server to modify
|
||||
:param cpu: the new cpu value
|
||||
:param memory: the new memory value
|
||||
:return: the result of CLC API call
|
||||
"""
|
||||
result = None
|
||||
acct_alias = clc.v2.Account.GetAlias()
|
||||
try:
|
||||
# Update the server configuration
|
||||
job_obj = clc.v2.API.Call('PATCH',
|
||||
'servers/%s/%s' % (acct_alias,
|
||||
server_id),
|
||||
json.dumps([{"op": "set",
|
||||
"member": "memory",
|
||||
"value": memory},
|
||||
{"op": "set",
|
||||
"member": "cpu",
|
||||
"value": cpu}]))
|
||||
result = clc.v2.Requests(job_obj)
|
||||
except APIFailedResponse as ex:
|
||||
module.fail_json(
|
||||
msg='Unable to update the server configuration for server : "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _wait_for_requests(module, request_list):
|
||||
"""
|
||||
Block until server provisioning requests are completed.
|
||||
:param module: the AnsibleModule object
|
||||
:param request_list: a list of clc-sdk.Request instances
|
||||
:return: none
|
||||
"""
|
||||
wait = module.params.get('wait')
|
||||
if wait:
|
||||
# Requests.WaitUntilComplete() returns the count of failed requests
|
||||
failed_requests_count = sum(
|
||||
[request.WaitUntilComplete() for request in request_list])
|
||||
|
||||
if failed_requests_count > 0:
|
||||
module.fail_json(
|
||||
msg='Unable to process modify server request')
|
||||
|
||||
@staticmethod
|
||||
def _refresh_servers(module, servers):
|
||||
"""
|
||||
Loop through a list of servers and refresh them.
|
||||
:param module: the AnsibleModule object
|
||||
:param servers: list of clc-sdk.Server instances to refresh
|
||||
:return: none
|
||||
"""
|
||||
for server in servers:
|
||||
try:
|
||||
server.Refresh()
|
||||
except CLCException as ex:
|
||||
module.fail_json(msg='Unable to refresh the server {0}. {1}'.format(
|
||||
server.id, ex.message
|
||||
))
|
||||
|
||||
def _ensure_aa_policy_present(
|
||||
self, server, server_params):
|
||||
"""
|
||||
ensures the server is updated with the provided anti affinity policy
|
||||
:param server: the CLC server object
|
||||
:param server_params: the dictionary of server parameters
|
||||
:return: (changed, group) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
acct_alias = self.clc.v2.Account.GetAlias()
|
||||
|
||||
aa_policy_id = server_params.get('anti_affinity_policy_id')
|
||||
aa_policy_name = server_params.get('anti_affinity_policy_name')
|
||||
if not aa_policy_id and aa_policy_name:
|
||||
aa_policy_id = self._get_aa_policy_id_by_name(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
aa_policy_name)
|
||||
current_aa_policy_id = self._get_aa_policy_id_of_server(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id)
|
||||
|
||||
if aa_policy_id and aa_policy_id != current_aa_policy_id:
|
||||
self._modify_aa_policy(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id,
|
||||
aa_policy_id)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def _ensure_aa_policy_absent(
|
||||
self, server, server_params):
|
||||
"""
|
||||
ensures the provided anti affinity policy is removed from the server
|
||||
:param server: the CLC server object
|
||||
:param server_params: the dictionary of server parameters
|
||||
:return: (changed, group) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
acct_alias = self.clc.v2.Account.GetAlias()
|
||||
aa_policy_id = server_params.get('anti_affinity_policy_id')
|
||||
aa_policy_name = server_params.get('anti_affinity_policy_name')
|
||||
if not aa_policy_id and aa_policy_name:
|
||||
aa_policy_id = self._get_aa_policy_id_by_name(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
aa_policy_name)
|
||||
current_aa_policy_id = self._get_aa_policy_id_of_server(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id)
|
||||
|
||||
if aa_policy_id and aa_policy_id == current_aa_policy_id:
|
||||
self._delete_aa_policy(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
@staticmethod
|
||||
def _modify_aa_policy(clc, module, acct_alias, server_id, aa_policy_id):
|
||||
"""
|
||||
modifies the anti affinity policy of the CLC server
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param acct_alias: the CLC account alias
|
||||
:param server_id: the CLC server id
|
||||
:param aa_policy_id: the anti affinity policy id
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
if not module.check_mode:
|
||||
try:
|
||||
result = clc.v2.API.Call('PUT',
|
||||
'servers/%s/%s/antiAffinityPolicy' % (
|
||||
acct_alias,
|
||||
server_id),
|
||||
json.dumps({"id": aa_policy_id}))
|
||||
except APIFailedResponse as ex:
|
||||
module.fail_json(
|
||||
msg='Unable to modify anti affinity policy to server : "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _delete_aa_policy(clc, module, acct_alias, server_id):
|
||||
"""
|
||||
Delete the anti affinity policy of the CLC server
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param acct_alias: the CLC account alias
|
||||
:param server_id: the CLC server id
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
if not module.check_mode:
|
||||
try:
|
||||
result = clc.v2.API.Call('DELETE',
|
||||
'servers/%s/%s/antiAffinityPolicy' % (
|
||||
acct_alias,
|
||||
server_id),
|
||||
json.dumps({}))
|
||||
except APIFailedResponse as ex:
|
||||
module.fail_json(
|
||||
msg='Unable to delete anti affinity policy to server : "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _get_aa_policy_id_by_name(clc, module, alias, aa_policy_name):
|
||||
"""
|
||||
retrieves the anti affinity policy id of the server based on the name of the policy
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param alias: the CLC account alias
|
||||
:param aa_policy_name: the anti affinity policy name
|
||||
:return: aa_policy_id: The anti affinity policy id
|
||||
"""
|
||||
aa_policy_id = None
|
||||
try:
|
||||
aa_policies = clc.v2.API.Call(method='GET',
|
||||
url='antiAffinityPolicies/%s' % alias)
|
||||
except APIFailedResponse as ex:
|
||||
return module.fail_json(
|
||||
msg='Unable to fetch anti affinity policies from account alias : "{0}". {1}'.format(
|
||||
alias, str(ex.response_text)))
|
||||
for aa_policy in aa_policies.get('items'):
|
||||
if aa_policy.get('name') == aa_policy_name:
|
||||
if not aa_policy_id:
|
||||
aa_policy_id = aa_policy.get('id')
|
||||
else:
|
||||
return module.fail_json(
|
||||
msg='multiple anti affinity policies were found with policy name : %s' % aa_policy_name)
|
||||
if not aa_policy_id:
|
||||
module.fail_json(
|
||||
msg='No anti affinity policy was found with policy name : %s' % aa_policy_name)
|
||||
return aa_policy_id
|
||||
|
||||
@staticmethod
|
||||
def _get_aa_policy_id_of_server(clc, module, alias, server_id):
|
||||
"""
|
||||
retrieves the anti affinity policy id of the server based on the CLC server id
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param alias: the CLC account alias
|
||||
:param server_id: the CLC server id
|
||||
:return: aa_policy_id: The anti affinity policy id
|
||||
"""
|
||||
aa_policy_id = None
|
||||
try:
|
||||
result = clc.v2.API.Call(
|
||||
method='GET', url='servers/%s/%s/antiAffinityPolicy' %
|
||||
(alias, server_id))
|
||||
aa_policy_id = result.get('id')
|
||||
except APIFailedResponse as ex:
|
||||
if ex.response_status_code != 404:
|
||||
module.fail_json(msg='Unable to fetch anti affinity policy for server "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return aa_policy_id
|
||||
|
||||
def _ensure_alert_policy_present(
|
||||
self, server, server_params):
|
||||
"""
|
||||
ensures the server is updated with the provided alert policy
|
||||
:param server: the CLC server object
|
||||
:param server_params: the dictionary of server parameters
|
||||
:return: (changed, group) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
acct_alias = self.clc.v2.Account.GetAlias()
|
||||
alert_policy_id = server_params.get('alert_policy_id')
|
||||
alert_policy_name = server_params.get('alert_policy_name')
|
||||
if not alert_policy_id and alert_policy_name:
|
||||
alert_policy_id = self._get_alert_policy_id_by_name(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
alert_policy_name)
|
||||
if alert_policy_id and not self._alert_policy_exists(
|
||||
server, alert_policy_id):
|
||||
self._add_alert_policy_to_server(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id,
|
||||
alert_policy_id)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def _ensure_alert_policy_absent(
|
||||
self, server, server_params):
|
||||
"""
|
||||
ensures the alert policy is removed from the server
|
||||
:param server: the CLC server object
|
||||
:param server_params: the dictionary of server parameters
|
||||
:return: (changed, group) -
|
||||
changed: Boolean whether a change was made
|
||||
result: The result from the CLC API call
|
||||
"""
|
||||
changed = False
|
||||
|
||||
acct_alias = self.clc.v2.Account.GetAlias()
|
||||
alert_policy_id = server_params.get('alert_policy_id')
|
||||
alert_policy_name = server_params.get('alert_policy_name')
|
||||
if not alert_policy_id and alert_policy_name:
|
||||
alert_policy_id = self._get_alert_policy_id_by_name(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
alert_policy_name)
|
||||
|
||||
if alert_policy_id and self._alert_policy_exists(
|
||||
server, alert_policy_id):
|
||||
self._remove_alert_policy_to_server(
|
||||
self.clc,
|
||||
self.module,
|
||||
acct_alias,
|
||||
server.id,
|
||||
alert_policy_id)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
@staticmethod
|
||||
def _add_alert_policy_to_server(
|
||||
clc, module, acct_alias, server_id, alert_policy_id):
|
||||
"""
|
||||
add the alert policy to CLC server
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param acct_alias: the CLC account alias
|
||||
:param server_id: the CLC server id
|
||||
:param alert_policy_id: the alert policy id
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
if not module.check_mode:
|
||||
try:
|
||||
result = clc.v2.API.Call('POST',
|
||||
'servers/%s/%s/alertPolicies' % (
|
||||
acct_alias,
|
||||
server_id),
|
||||
json.dumps({"id": alert_policy_id}))
|
||||
except APIFailedResponse as ex:
|
||||
module.fail_json(msg='Unable to set alert policy to the server : "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _remove_alert_policy_to_server(
|
||||
clc, module, acct_alias, server_id, alert_policy_id):
|
||||
"""
|
||||
remove the alert policy to the CLC server
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param acct_alias: the CLC account alias
|
||||
:param server_id: the CLC server id
|
||||
:param alert_policy_id: the alert policy id
|
||||
:return: result: The result from the CLC API call
|
||||
"""
|
||||
result = None
|
||||
if not module.check_mode:
|
||||
try:
|
||||
result = clc.v2.API.Call('DELETE',
|
||||
'servers/%s/%s/alertPolicies/%s'
|
||||
% (acct_alias, server_id, alert_policy_id))
|
||||
except APIFailedResponse as ex:
|
||||
module.fail_json(msg='Unable to remove alert policy from the server : "{0}". {1}'.format(
|
||||
server_id, str(ex.response_text)))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _get_alert_policy_id_by_name(clc, module, alias, alert_policy_name):
|
||||
"""
|
||||
retrieves the alert policy id of the server based on the name of the policy
|
||||
:param clc: the clc-sdk instance to use
|
||||
:param module: the AnsibleModule object
|
||||
:param alias: the CLC account alias
|
||||
:param alert_policy_name: the alert policy name
|
||||
:return: alert_policy_id: The alert policy id
|
||||
"""
|
||||
alert_policy_id = None
|
||||
try:
|
||||
alert_policies = clc.v2.API.Call(method='GET',
|
||||
url='alertPolicies/%s' % alias)
|
||||
except APIFailedResponse as ex:
|
||||
return module.fail_json(msg='Unable to fetch alert policies for account : "{0}". {1}'.format(
|
||||
alias, str(ex.response_text)))
|
||||
for alert_policy in alert_policies.get('items'):
|
||||
if alert_policy.get('name') == alert_policy_name:
|
||||
if not alert_policy_id:
|
||||
alert_policy_id = alert_policy.get('id')
|
||||
else:
|
||||
return module.fail_json(
|
||||
msg='multiple alert policies were found with policy name : %s' % alert_policy_name)
|
||||
return alert_policy_id
|
||||
|
||||
@staticmethod
|
||||
def _alert_policy_exists(server, alert_policy_id):
|
||||
"""
|
||||
Checks if the alert policy exists for the server
|
||||
:param server: the clc server object
|
||||
:param alert_policy_id: the alert policy
|
||||
:return: True: if the given alert policy id associated to the server, False otherwise
|
||||
"""
|
||||
result = False
|
||||
alert_policies = server.alertPolicies
|
||||
if alert_policies:
|
||||
for alert_policy in alert_policies:
|
||||
if alert_policy.get('id') == alert_policy_id:
|
||||
result = True
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
|
||||
argument_dict = ClcModifyServer._define_module_argument_spec()
|
||||
module = AnsibleModule(supports_check_mode=True, **argument_dict)
|
||||
clc_modify_server = ClcModifyServer(module)
|
||||
clc_modify_server.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,359 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_publicip
|
||||
short_description: Add and Delete public IPs on servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to add or delete public IP addresses on an existing server or servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
protocol:
|
||||
description:
|
||||
- The protocol that the public IP will listen for.
|
||||
type: str
|
||||
default: TCP
|
||||
choices: ['TCP', 'UDP', 'ICMP']
|
||||
ports:
|
||||
description:
|
||||
- A list of ports to expose. This is required when O(state=present).
|
||||
type: list
|
||||
elements: int
|
||||
server_ids:
|
||||
description:
|
||||
- A list of servers to create public IPs on.
|
||||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
state:
|
||||
description:
|
||||
- Determine whether to create or delete public IPs. If V(present) module will not create a second public IP if one already
|
||||
exists.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the tasks to finish before returning.
|
||||
type: bool
|
||||
default: true
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
||||
|
||||
- name: Add Public IP to Server
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create Public IP For Servers
|
||||
community.general.clc_publicip:
|
||||
protocol: TCP
|
||||
ports:
|
||||
- 80
|
||||
server_ids:
|
||||
- UC1TEST-SVR01
|
||||
- UC1TEST-SVR02
|
||||
state: present
|
||||
register: clc
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: clc
|
||||
|
||||
- name: Delete Public IP from Server
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create Public IP For Servers
|
||||
community.general.clc_publicip:
|
||||
server_ids:
|
||||
- UC1TEST-SVR01
|
||||
- UC1TEST-SVR02
|
||||
state: absent
|
||||
register: clc
|
||||
|
||||
- name: Debug
|
||||
ansible.builtin.debug:
|
||||
var: clc
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
server_ids:
|
||||
description: The list of server IDs that are changed.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["UC1TEST-SVR01", "UC1TEST-SVR02"]
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcPublicIp(object):
|
||||
clc = clc_sdk
|
||||
module = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.module = module
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
self._set_clc_credentials_from_env()
|
||||
params = self.module.params
|
||||
server_ids = params['server_ids']
|
||||
ports = params['ports']
|
||||
protocol = params['protocol']
|
||||
state = params['state']
|
||||
|
||||
if state == 'present':
|
||||
changed, changed_server_ids, requests = self.ensure_public_ip_present(
|
||||
server_ids=server_ids, protocol=protocol, ports=ports)
|
||||
elif state == 'absent':
|
||||
changed, changed_server_ids, requests = self.ensure_public_ip_absent(
|
||||
server_ids=server_ids)
|
||||
else:
|
||||
return self.module.fail_json(msg="Unknown State: " + state)
|
||||
self._wait_for_requests_to_complete(requests)
|
||||
return self.module.exit_json(changed=changed,
|
||||
server_ids=changed_server_ids)
|
||||
|
||||
@staticmethod
|
||||
def _define_module_argument_spec():
|
||||
"""
|
||||
Define the argument spec for the ansible module
|
||||
:return: argument spec dictionary
|
||||
"""
|
||||
argument_spec = dict(
|
||||
server_ids=dict(type='list', required=True, elements='str'),
|
||||
protocol=dict(default='TCP', choices=['TCP', 'UDP', 'ICMP']),
|
||||
ports=dict(type='list', elements='int'),
|
||||
wait=dict(type='bool', default=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
def ensure_public_ip_present(self, server_ids, protocol, ports):
|
||||
"""
|
||||
Ensures the given server ids having the public ip available
|
||||
:param server_ids: the list of server ids
|
||||
:param protocol: the ip protocol
|
||||
:param ports: the list of ports to expose
|
||||
:return: (changed, changed_server_ids, results)
|
||||
changed: A flag indicating if there is any change
|
||||
changed_server_ids : the list of server ids that are changed
|
||||
results: The result list from clc public ip call
|
||||
"""
|
||||
changed = False
|
||||
results = []
|
||||
changed_server_ids = []
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
servers_to_change = [
|
||||
server for server in servers if len(
|
||||
server.PublicIPs().public_ips) == 0]
|
||||
ports_to_expose = [{'protocol': protocol, 'port': port}
|
||||
for port in ports]
|
||||
for server in servers_to_change:
|
||||
if not self.module.check_mode:
|
||||
result = self._add_publicip_to_server(server, ports_to_expose)
|
||||
results.append(result)
|
||||
changed_server_ids.append(server.id)
|
||||
changed = True
|
||||
return changed, changed_server_ids, results
|
||||
|
||||
def _add_publicip_to_server(self, server, ports_to_expose):
|
||||
result = None
|
||||
try:
|
||||
result = server.PublicIPs().Add(ports_to_expose)
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to add public ip to the server : {0}. {1}'.format(
|
||||
server.id, ex.response_text
|
||||
))
|
||||
return result
|
||||
|
||||
def ensure_public_ip_absent(self, server_ids):
|
||||
"""
|
||||
Ensures the given server ids having the public ip removed if there is any
|
||||
:param server_ids: the list of server ids
|
||||
:return: (changed, changed_server_ids, results)
|
||||
changed: A flag indicating if there is any change
|
||||
changed_server_ids : the list of server ids that are changed
|
||||
results: The result list from clc public ip call
|
||||
"""
|
||||
changed = False
|
||||
results = []
|
||||
changed_server_ids = []
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
servers_to_change = [
|
||||
server for server in servers if len(
|
||||
server.PublicIPs().public_ips) > 0]
|
||||
for server in servers_to_change:
|
||||
if not self.module.check_mode:
|
||||
result = self._remove_publicip_from_server(server)
|
||||
results.append(result)
|
||||
changed_server_ids.append(server.id)
|
||||
changed = True
|
||||
return changed, changed_server_ids, results
|
||||
|
||||
def _remove_publicip_from_server(self, server):
|
||||
result = None
|
||||
try:
|
||||
for ip_address in server.PublicIPs().public_ips:
|
||||
result = ip_address.Delete()
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to remove public ip from the server : {0}. {1}'.format(
|
||||
server.id, ex.response_text
|
||||
))
|
||||
return result
|
||||
|
||||
def _wait_for_requests_to_complete(self, requests_lst):
|
||||
"""
|
||||
Waits until the CLC requests are complete if the wait argument is True
|
||||
:param requests_lst: The list of CLC request objects
|
||||
:return: none
|
||||
"""
|
||||
if not self.module.params['wait']:
|
||||
return
|
||||
for request in requests_lst:
|
||||
request.WaitUntilComplete()
|
||||
for request_details in request.requests:
|
||||
if request_details.Status() != 'succeeded':
|
||||
self.module.fail_json(
|
||||
msg='Unable to process public ip request')
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
def _get_servers_from_clc(self, server_ids, message):
|
||||
"""
|
||||
Gets list of servers form CLC api
|
||||
"""
|
||||
try:
|
||||
return self.clc.v2.Servers(server_ids).servers
|
||||
except CLCException as exception:
|
||||
self.module.fail_json(msg=message + ': %s' % exception)
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The main function. Instantiates the module and calls process_request.
|
||||
:return: none
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcPublicIp._define_module_argument_spec(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
clc_public_ip = ClcPublicIp(module)
|
||||
clc_public_ip.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
File diff suppressed because it is too large
Load diff
|
@ -1,409 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2015 CenturyLink
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: clc_server_snapshot
|
||||
short_description: Create, Delete and Restore server snapshots in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create, Delete and Restore server snapshots in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
author:
|
||||
- "CLC Runner (@clc-runner)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
server_ids:
|
||||
description:
|
||||
- The list of CLC server IDs.
|
||||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
expiration_days:
|
||||
description:
|
||||
- The number of days to keep the server snapshot before it expires.
|
||||
type: int
|
||||
default: 7
|
||||
required: false
|
||||
state:
|
||||
description:
|
||||
- The state to insure that the provided resources are in.
|
||||
type: str
|
||||
default: 'present'
|
||||
required: false
|
||||
choices: ['present', 'absent', 'restore']
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for the provisioning tasks to finish before returning.
|
||||
default: 'True'
|
||||
required: false
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
||||
|
||||
- name: Create server snapshot
|
||||
community.general.clc_server_snapshot:
|
||||
server_ids:
|
||||
- UC1TEST-SVR01
|
||||
- UC1TEST-SVR02
|
||||
expiration_days: 10
|
||||
wait: true
|
||||
state: present
|
||||
|
||||
- name: Restore server snapshot
|
||||
community.general.clc_server_snapshot:
|
||||
server_ids:
|
||||
- UC1TEST-SVR01
|
||||
- UC1TEST-SVR02
|
||||
wait: true
|
||||
state: restore
|
||||
|
||||
- name: Delete server snapshot
|
||||
community.general.clc_server_snapshot:
|
||||
server_ids:
|
||||
- UC1TEST-SVR01
|
||||
- UC1TEST-SVR02
|
||||
wait: true
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
server_ids:
|
||||
description: The list of server IDs that are changed.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["UC1TEST-SVR01", "UC1TEST-SVR02"]
|
||||
"""
|
||||
|
||||
__version__ = '${version}'
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
REQUESTS_FOUND = False
|
||||
else:
|
||||
REQUESTS_FOUND = True
|
||||
|
||||
#
|
||||
# Requires the clc-python-sdk.
|
||||
# sudo pip install clc-sdk
|
||||
#
|
||||
CLC_IMP_ERR = None
|
||||
try:
|
||||
import clc as clc_sdk
|
||||
from clc import CLCException
|
||||
except ImportError:
|
||||
CLC_IMP_ERR = traceback.format_exc()
|
||||
CLC_FOUND = False
|
||||
clc_sdk = None
|
||||
else:
|
||||
CLC_FOUND = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class ClcSnapshot:
|
||||
|
||||
clc = clc_sdk
|
||||
module = None
|
||||
|
||||
def __init__(self, module):
|
||||
"""
|
||||
Construct module
|
||||
"""
|
||||
self.module = module
|
||||
|
||||
if not CLC_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
|
||||
if not REQUESTS_FOUND:
|
||||
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
|
||||
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
|
||||
self.module.fail_json(
|
||||
msg='requests library version should be >= 2.5.0')
|
||||
|
||||
self._set_user_agent(self.clc)
|
||||
|
||||
def process_request(self):
|
||||
"""
|
||||
Process the request - Main Code Path
|
||||
:return: Returns with either an exit_json or fail_json
|
||||
"""
|
||||
p = self.module.params
|
||||
server_ids = p['server_ids']
|
||||
expiration_days = p['expiration_days']
|
||||
state = p['state']
|
||||
request_list = []
|
||||
changed = False
|
||||
changed_servers = []
|
||||
|
||||
self._set_clc_credentials_from_env()
|
||||
if state == 'present':
|
||||
changed, request_list, changed_servers = self.ensure_server_snapshot_present(
|
||||
server_ids=server_ids,
|
||||
expiration_days=expiration_days)
|
||||
elif state == 'absent':
|
||||
changed, request_list, changed_servers = self.ensure_server_snapshot_absent(
|
||||
server_ids=server_ids)
|
||||
elif state == 'restore':
|
||||
changed, request_list, changed_servers = self.ensure_server_snapshot_restore(
|
||||
server_ids=server_ids)
|
||||
|
||||
self._wait_for_requests_to_complete(request_list)
|
||||
return self.module.exit_json(
|
||||
changed=changed,
|
||||
server_ids=changed_servers)
|
||||
|
||||
def ensure_server_snapshot_present(self, server_ids, expiration_days):
|
||||
"""
|
||||
Ensures the given set of server_ids have the snapshots created
|
||||
:param server_ids: The list of server_ids to create the snapshot
|
||||
:param expiration_days: The number of days to keep the snapshot
|
||||
:return: (changed, request_list, changed_servers)
|
||||
changed: A flag indicating whether any change was made
|
||||
request_list: the list of clc request objects from CLC API call
|
||||
changed_servers: The list of servers ids that are modified
|
||||
"""
|
||||
request_list = []
|
||||
changed = False
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
servers_to_change = [
|
||||
server for server in servers if len(
|
||||
server.GetSnapshots()) == 0]
|
||||
for server in servers_to_change:
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
request = self._create_server_snapshot(server, expiration_days)
|
||||
request_list.append(request)
|
||||
changed_servers = [
|
||||
server.id for server in servers_to_change if server.id]
|
||||
return changed, request_list, changed_servers
|
||||
|
||||
def _create_server_snapshot(self, server, expiration_days):
|
||||
"""
|
||||
Create the snapshot for the CLC server
|
||||
:param server: the CLC server object
|
||||
:param expiration_days: The number of days to keep the snapshot
|
||||
:return: the create request object from CLC API Call
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = server.CreateSnapshot(
|
||||
delete_existing=True,
|
||||
expiration_days=expiration_days)
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to create snapshot for server : {0}. {1}'.format(
|
||||
server.id, ex.response_text
|
||||
))
|
||||
return result
|
||||
|
||||
def ensure_server_snapshot_absent(self, server_ids):
|
||||
"""
|
||||
Ensures the given set of server_ids have the snapshots removed
|
||||
:param server_ids: The list of server_ids to delete the snapshot
|
||||
:return: (changed, request_list, changed_servers)
|
||||
changed: A flag indicating whether any change was made
|
||||
request_list: the list of clc request objects from CLC API call
|
||||
changed_servers: The list of servers ids that are modified
|
||||
"""
|
||||
request_list = []
|
||||
changed = False
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
servers_to_change = [
|
||||
server for server in servers if len(
|
||||
server.GetSnapshots()) > 0]
|
||||
for server in servers_to_change:
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
request = self._delete_server_snapshot(server)
|
||||
request_list.append(request)
|
||||
changed_servers = [
|
||||
server.id for server in servers_to_change if server.id]
|
||||
return changed, request_list, changed_servers
|
||||
|
||||
def _delete_server_snapshot(self, server):
|
||||
"""
|
||||
Delete snapshot for the CLC server
|
||||
:param server: the CLC server object
|
||||
:return: the delete snapshot request object from CLC API
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = server.DeleteSnapshot()
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to delete snapshot for server : {0}. {1}'.format(
|
||||
server.id, ex.response_text
|
||||
))
|
||||
return result
|
||||
|
||||
def ensure_server_snapshot_restore(self, server_ids):
|
||||
"""
|
||||
Ensures the given set of server_ids have the snapshots restored
|
||||
:param server_ids: The list of server_ids to delete the snapshot
|
||||
:return: (changed, request_list, changed_servers)
|
||||
changed: A flag indicating whether any change was made
|
||||
request_list: the list of clc request objects from CLC API call
|
||||
changed_servers: The list of servers ids that are modified
|
||||
"""
|
||||
request_list = []
|
||||
changed = False
|
||||
servers = self._get_servers_from_clc(
|
||||
server_ids,
|
||||
'Failed to obtain server list from the CLC API')
|
||||
servers_to_change = [
|
||||
server for server in servers if len(
|
||||
server.GetSnapshots()) > 0]
|
||||
for server in servers_to_change:
|
||||
changed = True
|
||||
if not self.module.check_mode:
|
||||
request = self._restore_server_snapshot(server)
|
||||
request_list.append(request)
|
||||
changed_servers = [
|
||||
server.id for server in servers_to_change if server.id]
|
||||
return changed, request_list, changed_servers
|
||||
|
||||
def _restore_server_snapshot(self, server):
|
||||
"""
|
||||
Restore snapshot for the CLC server
|
||||
:param server: the CLC server object
|
||||
:return: the restore snapshot request object from CLC API
|
||||
"""
|
||||
result = None
|
||||
try:
|
||||
result = server.RestoreSnapshot()
|
||||
except CLCException as ex:
|
||||
self.module.fail_json(msg='Failed to restore snapshot for server : {0}. {1}'.format(
|
||||
server.id, ex.response_text
|
||||
))
|
||||
return result
|
||||
|
||||
def _wait_for_requests_to_complete(self, requests_lst):
|
||||
"""
|
||||
Waits until the CLC requests are complete if the wait argument is True
|
||||
:param requests_lst: The list of CLC request objects
|
||||
:return: none
|
||||
"""
|
||||
if not self.module.params['wait']:
|
||||
return
|
||||
for request in requests_lst:
|
||||
request.WaitUntilComplete()
|
||||
for request_details in request.requests:
|
||||
if request_details.Status() != 'succeeded':
|
||||
self.module.fail_json(
|
||||
msg='Unable to process server snapshot request')
|
||||
|
||||
@staticmethod
|
||||
def define_argument_spec():
|
||||
"""
|
||||
This function defines the dictionary object required for
|
||||
package module
|
||||
:return: the package dictionary object
|
||||
"""
|
||||
argument_spec = dict(
|
||||
server_ids=dict(type='list', required=True, elements='str'),
|
||||
expiration_days=dict(default=7, type='int'),
|
||||
wait=dict(default=True),
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=[
|
||||
'present',
|
||||
'absent',
|
||||
'restore']),
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
def _get_servers_from_clc(self, server_list, message):
|
||||
"""
|
||||
Internal function to fetch list of CLC server objects from a list of server ids
|
||||
:param server_list: The list of server ids
|
||||
:param message: The error message to throw in case of any error
|
||||
:return the list of CLC server objects
|
||||
"""
|
||||
try:
|
||||
return self.clc.v2.Servers(server_list).servers
|
||||
except CLCException as ex:
|
||||
return self.module.fail_json(msg=message + ': %s' % ex)
|
||||
|
||||
def _set_clc_credentials_from_env(self):
|
||||
"""
|
||||
Set the CLC Credentials on the sdk by reading environment variables
|
||||
:return: none
|
||||
"""
|
||||
env = os.environ
|
||||
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
||||
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
||||
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
||||
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
||||
api_url = env.get('CLC_V2_API_URL', False)
|
||||
|
||||
if api_url:
|
||||
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
||||
|
||||
if v2_api_token and clc_alias:
|
||||
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
||||
self.clc._V2_ENABLED = True
|
||||
self.clc.ALIAS = clc_alias
|
||||
elif v2_api_username and v2_api_passwd:
|
||||
self.clc.v2.SetCredentials(
|
||||
api_username=v2_api_username,
|
||||
api_passwd=v2_api_passwd)
|
||||
else:
|
||||
return self.module.fail_json(
|
||||
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
||||
"environment variables")
|
||||
|
||||
@staticmethod
|
||||
def _set_user_agent(clc):
|
||||
if hasattr(clc, 'SetRequestsSession'):
|
||||
agent_string = "ClcAnsibleModule/" + __version__
|
||||
ses = requests.Session()
|
||||
ses.headers.update({"Api-Client": agent_string})
|
||||
ses.headers['User-Agent'] += " " + agent_string
|
||||
clc.SetRequestsSession(ses)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function
|
||||
:return: None
|
||||
"""
|
||||
module = AnsibleModule(
|
||||
argument_spec=ClcSnapshot.define_argument_spec(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
clc_snapshot = ClcSnapshot(module)
|
||||
clc_snapshot.process_request()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -161,6 +161,8 @@ from ansible.module_utils.common.text.converters import to_text
|
|||
from ansible_collections.community.general.plugins.module_utils.datetime import (
|
||||
now,
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
|
||||
IFPROPS_MAPPING = dict(
|
||||
bondingopts='bonding_opts',
|
||||
|
@ -278,7 +280,11 @@ def main():
|
|||
|
||||
if system:
|
||||
# Update existing entry
|
||||
system_id = conn.get_system_handle(name, token)
|
||||
system_id = None
|
||||
if LooseVersion(str(conn.version())) >= LooseVersion('3.4.0'):
|
||||
system_id = conn.get_system_handle(name)
|
||||
else:
|
||||
system_id = conn.get_system_handle(name, token)
|
||||
|
||||
for key, value in iteritems(module.params['properties']):
|
||||
if key not in system:
|
||||
|
|
|
@ -204,7 +204,6 @@ class CPANMinus(ModuleHelper):
|
|||
pkg_spec=cmd_runner_fmt.as_list(),
|
||||
cpanm_version=cmd_runner_fmt.as_fixed("--version"),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
v = self.vars
|
||||
|
|
|
@ -132,7 +132,6 @@ def decompress(b_src, b_dest, handler):
|
|||
|
||||
class Decompress(ModuleHelper):
|
||||
destination_filename_template = "%s_decompressed"
|
||||
use_old_vardict = False
|
||||
output_params = 'dest'
|
||||
|
||||
module = dict(
|
||||
|
|
|
@ -122,12 +122,6 @@ options:
|
|||
type: str
|
||||
required: false
|
||||
aliases: [test_runner]
|
||||
ack_venv_creation_deprecation:
|
||||
description:
|
||||
- This option no longer has any effect since community.general 9.0.0.
|
||||
- It will be removed from community.general 11.0.0.
|
||||
type: bool
|
||||
version_added: 5.8.0
|
||||
|
||||
notes:
|
||||
- 'B(ATTENTION): Support for Django releases older than 4.1 has been removed in community.general version 9.0.0. While the
|
||||
|
@ -291,7 +285,6 @@ def main():
|
|||
skip=dict(type='bool'),
|
||||
merge=dict(type='bool'),
|
||||
link=dict(type='bool'),
|
||||
ack_venv_creation_deprecation=dict(type='bool', removed_in_version='11.0.0', removed_from_collection='community.general'),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -128,7 +128,6 @@ class GConftool(StateModuleHelper):
|
|||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gconftool2_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -67,7 +67,6 @@ class GConftoolInfo(ModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gconftool2_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -94,7 +94,6 @@ class GioMime(ModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gio_mime_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -31,17 +31,11 @@ attributes:
|
|||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
list_all:
|
||||
description:
|
||||
- List all settings (optionally limited to a given O(scope)).
|
||||
- This option is B(deprecated) and will be removed from community.general 11.0.0. Please use M(community.general.git_config_info)
|
||||
instead.
|
||||
type: bool
|
||||
default: false
|
||||
name:
|
||||
description:
|
||||
- The name of the setting. If no value is supplied, the value will be read from the config if it has been set.
|
||||
- The name of the setting.
|
||||
type: str
|
||||
required: true
|
||||
repo:
|
||||
description:
|
||||
- Path to a git repository for reading and writing values from a specific repo.
|
||||
|
@ -57,7 +51,7 @@ options:
|
|||
- This is required when setting config values.
|
||||
- If this is set to V(local), you must also specify the O(repo) parameter.
|
||||
- If this is set to V(file), you must also specify the O(file) parameter.
|
||||
- It defaults to system only when not using O(list_all=true).
|
||||
- It defaults to system.
|
||||
choices: ["file", "local", "global", "system"]
|
||||
type: str
|
||||
state:
|
||||
|
@ -70,7 +64,7 @@ options:
|
|||
value:
|
||||
description:
|
||||
- When specifying the name of a single setting, supply a value to set that setting to the given value.
|
||||
- From community.general 11.0.0 on, O(value) will be required if O(state=present). To read values, use the M(community.general.git_config_info)
|
||||
- From community.general 11.0.0 on, O(value) is required if O(state=present). To read values, use the M(community.general.git_config_info)
|
||||
module instead.
|
||||
type: str
|
||||
add_mode:
|
||||
|
@ -144,21 +138,6 @@ EXAMPLES = r"""
|
|||
"""
|
||||
|
||||
RETURN = r"""
|
||||
config_value:
|
||||
description: When O(list_all=false) and value is not set, a string containing the value of the setting in name.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "vim"
|
||||
|
||||
config_values:
|
||||
description: When O(list_all=true), a dict containing key/value pairs of multiple configuration settings.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
core.editor: "vim"
|
||||
color.ui: "auto"
|
||||
alias.diffc: "diff --cached"
|
||||
alias.remotev: "remote -v"
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
@ -167,21 +146,19 @@ from ansible.module_utils.basic import AnsibleModule
|
|||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
list_all=dict(required=False, type='bool', default=False, removed_in_version='11.0.0', removed_from_collection='community.general'),
|
||||
name=dict(type='str'),
|
||||
name=dict(type='str', required=True),
|
||||
repo=dict(type='path'),
|
||||
file=dict(type='path'),
|
||||
add_mode=dict(required=False, type='str', default='replace-all', choices=['add', 'replace-all']),
|
||||
scope=dict(required=False, type='str', choices=['file', 'local', 'global', 'system']),
|
||||
state=dict(required=False, type='str', default='present', choices=['present', 'absent']),
|
||||
value=dict(required=False),
|
||||
add_mode=dict(type='str', default='replace-all', choices=['add', 'replace-all']),
|
||||
scope=dict(type='str', choices=['file', 'local', 'global', 'system']),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
value=dict(),
|
||||
),
|
||||
mutually_exclusive=[['list_all', 'name'], ['list_all', 'value'], ['list_all', 'state']],
|
||||
required_if=[
|
||||
('scope', 'local', ['repo']),
|
||||
('scope', 'file', ['file'])
|
||||
('scope', 'file', ['file']),
|
||||
('state', 'present', ['value']),
|
||||
],
|
||||
required_one_of=[['list_all', 'name']],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
git_path = module.get_bin_path('git', True)
|
||||
|
@ -196,13 +173,8 @@ def main():
|
|||
new_value = params['value'] or ''
|
||||
add_mode = params['add_mode']
|
||||
|
||||
if not unset and not new_value and not params['list_all']:
|
||||
module.deprecate(
|
||||
'If state=present, a value must be specified from community.general 11.0.0 on.'
|
||||
' To read a config value, use the community.general.git_config_info module instead.',
|
||||
version='11.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
if not unset and not new_value:
|
||||
module.fail_json(msg="If state=present, a value must be specified. Use the community.general.git_config_info module to read a config value.")
|
||||
|
||||
scope = determine_scope(params)
|
||||
cwd = determine_cwd(scope, params)
|
||||
|
@ -217,33 +189,18 @@ def main():
|
|||
|
||||
list_args = list(base_args)
|
||||
|
||||
if params['list_all']:
|
||||
list_args.append('-l')
|
||||
|
||||
if name:
|
||||
list_args.append("--get-all")
|
||||
list_args.append(name)
|
||||
list_args.append("--get-all")
|
||||
list_args.append(name)
|
||||
|
||||
(rc, out, err) = module.run_command(list_args, cwd=cwd, expand_user_and_vars=False)
|
||||
|
||||
if params['list_all'] and scope and rc == 128 and 'unable to read config file' in err:
|
||||
# This just means nothing has been set at the given scope
|
||||
module.exit_json(changed=False, msg='', config_values={})
|
||||
elif rc >= 2:
|
||||
if rc >= 2:
|
||||
# If the return code is 1, it just means the option hasn't been set yet, which is fine.
|
||||
module.fail_json(rc=rc, msg=err, cmd=' '.join(list_args))
|
||||
|
||||
old_values = out.rstrip().splitlines()
|
||||
|
||||
if params['list_all']:
|
||||
config_values = {}
|
||||
for value in old_values:
|
||||
k, v = value.split('=', 1)
|
||||
config_values[k] = v
|
||||
module.exit_json(changed=False, msg='', config_values=config_values)
|
||||
elif not new_value and not unset:
|
||||
module.exit_json(changed=False, msg='', config_value=old_values[0] if old_values else '')
|
||||
elif unset and not out:
|
||||
if unset and not out:
|
||||
module.exit_json(changed=False, msg='no setting to unset')
|
||||
elif new_value in old_values and (len(old_values) == 1 or add_mode == "add") and not unset:
|
||||
module.exit_json(changed=False, msg="")
|
||||
|
@ -286,30 +243,22 @@ def main():
|
|||
def determine_scope(params):
|
||||
if params['scope']:
|
||||
return params['scope']
|
||||
elif params['list_all']:
|
||||
return ""
|
||||
else:
|
||||
return 'system'
|
||||
return 'system'
|
||||
|
||||
|
||||
def build_diff_value(value):
|
||||
if not value:
|
||||
return "\n"
|
||||
elif len(value) == 1:
|
||||
if len(value) == 1:
|
||||
return value[0] + "\n"
|
||||
else:
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def determine_cwd(scope, params):
|
||||
if scope == 'local':
|
||||
return params['repo']
|
||||
elif params['list_all'] and params['repo']:
|
||||
# Include local settings from a specific repo when listing all available settings
|
||||
return params['repo']
|
||||
else:
|
||||
# Run from root directory to avoid accidentally picking up any local config settings
|
||||
return "/"
|
||||
# Run from root directory to avoid accidentally picking up any local config settings
|
||||
return "/"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: hipchat
|
||||
short_description: Send a message to Hipchat
|
||||
description:
|
||||
- Send a message to a Hipchat room, with options to control the formatting.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
alternative: There is none.
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
token:
|
||||
type: str
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
room:
|
||||
type: str
|
||||
description:
|
||||
- ID or name of the room.
|
||||
required: true
|
||||
msg_from:
|
||||
type: str
|
||||
description:
|
||||
- Name the message will appear to be sent from. Max length is 15 characters - above this it will be truncated.
|
||||
default: Ansible
|
||||
aliases: [from]
|
||||
msg:
|
||||
type: str
|
||||
description:
|
||||
- The message body.
|
||||
required: true
|
||||
color:
|
||||
type: str
|
||||
description:
|
||||
- Background color for the message.
|
||||
default: yellow
|
||||
choices: ["yellow", "red", "green", "purple", "gray", "random"]
|
||||
msg_format:
|
||||
type: str
|
||||
description:
|
||||
- Message format.
|
||||
default: text
|
||||
choices: ["text", "html"]
|
||||
notify:
|
||||
description:
|
||||
- If true, a notification will be triggered for users in the room.
|
||||
type: bool
|
||||
default: true
|
||||
validate_certs:
|
||||
description:
|
||||
- If V(false), SSL certificates will not be validated. This should only be used on personally controlled sites using
|
||||
self-signed certificates.
|
||||
type: bool
|
||||
default: true
|
||||
api:
|
||||
type: str
|
||||
description:
|
||||
- API URL if using a self-hosted hipchat server. For Hipchat API version 2 use the default URI with C(/v2) instead of
|
||||
C(/v1).
|
||||
default: 'https://api.hipchat.com/v1'
|
||||
|
||||
author:
|
||||
- Shirou Wakayama (@shirou)
|
||||
- Paul Bourdel (@pb8226)
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Send a message to a Hipchat room
|
||||
community.general.hipchat:
|
||||
room: notif
|
||||
msg: Ansible task finished
|
||||
|
||||
- name: Send a message to a Hipchat room using Hipchat API version 2
|
||||
community.general.hipchat:
|
||||
api: https://api.hipchat.com/v2/
|
||||
token: OAUTH2_TOKEN
|
||||
room: notify
|
||||
msg: Ansible task finished
|
||||
"""
|
||||
|
||||
# ===========================================
|
||||
# HipChat module specific support methods.
|
||||
#
|
||||
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.six.moves.urllib.request import pathname2url
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
|
||||
|
||||
DEFAULT_URI = "https://api.hipchat.com/v1"
|
||||
|
||||
MSG_URI_V1 = "/rooms/message"
|
||||
|
||||
NOTIFY_URI_V2 = "/room/{id_or_name}/notification"
|
||||
|
||||
|
||||
def send_msg_v1(module, token, room, msg_from, msg, msg_format='text',
|
||||
color='yellow', notify=False, api=MSG_URI_V1):
|
||||
'''sending message to hipchat v1 server'''
|
||||
|
||||
params = {}
|
||||
params['room_id'] = room
|
||||
params['from'] = msg_from[:15] # max length is 15
|
||||
params['message'] = msg
|
||||
params['message_format'] = msg_format
|
||||
params['color'] = color
|
||||
params['api'] = api
|
||||
params['notify'] = int(notify)
|
||||
|
||||
url = api + MSG_URI_V1 + "?auth_token=%s" % (token)
|
||||
data = urlencode(params)
|
||||
|
||||
if module.check_mode:
|
||||
# In check mode, exit before actually sending the message
|
||||
module.exit_json(changed=False)
|
||||
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
if info['status'] == 200:
|
||||
return response.read()
|
||||
else:
|
||||
module.fail_json(msg="failed to send message, return status=%s" % str(info['status']))
|
||||
|
||||
|
||||
def send_msg_v2(module, token, room, msg_from, msg, msg_format='text',
|
||||
color='yellow', notify=False, api=NOTIFY_URI_V2):
|
||||
'''sending message to hipchat v2 server'''
|
||||
|
||||
headers = {'Authorization': 'Bearer %s' % token, 'Content-Type': 'application/json'}
|
||||
|
||||
body = dict()
|
||||
body['message'] = msg
|
||||
body['color'] = color
|
||||
body['message_format'] = msg_format
|
||||
body['notify'] = notify
|
||||
|
||||
POST_URL = api + NOTIFY_URI_V2
|
||||
|
||||
url = POST_URL.replace('{id_or_name}', pathname2url(room))
|
||||
data = json.dumps(body)
|
||||
|
||||
if module.check_mode:
|
||||
# In check mode, exit before actually sending the message
|
||||
module.exit_json(changed=False)
|
||||
|
||||
response, info = fetch_url(module, url, data=data, headers=headers, method='POST')
|
||||
|
||||
# https://www.hipchat.com/docs/apiv2/method/send_room_notification shows
|
||||
# 204 to be the expected result code.
|
||||
if info['status'] in [200, 204]:
|
||||
return response.read()
|
||||
else:
|
||||
module.fail_json(msg="failed to send message, return status=%s" % str(info['status']))
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True, no_log=True),
|
||||
room=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
msg_from=dict(default="Ansible", aliases=['from']),
|
||||
color=dict(default="yellow", choices=["yellow", "red", "green",
|
||||
"purple", "gray", "random"]),
|
||||
msg_format=dict(default="text", choices=["text", "html"]),
|
||||
notify=dict(default=True, type='bool'),
|
||||
validate_certs=dict(default=True, type='bool'),
|
||||
api=dict(default=DEFAULT_URI),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
token = module.params["token"]
|
||||
room = str(module.params["room"])
|
||||
msg = module.params["msg"]
|
||||
msg_from = module.params["msg_from"]
|
||||
color = module.params["color"]
|
||||
msg_format = module.params["msg_format"]
|
||||
notify = module.params["notify"]
|
||||
api = module.params["api"]
|
||||
|
||||
try:
|
||||
if api.find('/v2') != -1:
|
||||
send_msg_v2(module, token, room, msg_from, msg, msg_format, color, notify, api)
|
||||
else:
|
||||
send_msg_v1(module, token, room, msg_from, msg, msg_format, color, notify, api)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="unable to send msg: %s" % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
changed = True
|
||||
module.exit_json(changed=changed, room=room, msg_from=msg_from, msg=msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -97,7 +97,6 @@ class HPOnCfg(ModuleHelper):
|
|||
verbose=cmd_runner_fmt.as_bool("-v"),
|
||||
minfw=cmd_runner_fmt.as_opt_val("-m"),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __run__(self):
|
||||
runner = CmdRunner(
|
||||
|
|
|
@ -560,7 +560,6 @@ class JIRA(StateModuleHelper):
|
|||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
use_old_vardict = False
|
||||
state_param = 'operation'
|
||||
|
||||
def __init_module__(self):
|
||||
|
|
|
@ -141,7 +141,7 @@ def run_kwriteconfig(module, cmd, path, groups, key, value):
|
|||
else:
|
||||
args.append('false')
|
||||
else:
|
||||
args.append(value)
|
||||
args.extend(['--', value])
|
||||
module.run_command(args, check_rc=True)
|
||||
|
||||
|
||||
|
|
|
@ -65,7 +65,6 @@ class Blacklist(StateModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.pattern = re.compile(r'^blacklist\s+{0}$'.format(re.escape(self.vars.name)))
|
||||
|
|
|
@ -111,7 +111,6 @@ class LocaleGen(StateModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.MECHANISMS = dict(
|
||||
|
|
|
@ -141,7 +141,6 @@ class MkSysB(ModuleHelper):
|
|||
backup_dmapi_fs=cmd_runner_fmt.as_bool("-A"),
|
||||
combined_path=cmd_runner_fmt.as_func(cmd_runner_fmt.unpack_args(lambda p, n: ["%s/%s" % (p, n)])),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
if not os.path.isdir(self.vars.storage_path):
|
||||
|
|
|
@ -46,6 +46,16 @@ options:
|
|||
- Whether the connection profile can be automatically activated.
|
||||
type: bool
|
||||
default: true
|
||||
autoconnect_priority:
|
||||
description:
|
||||
- The priority of the connection profile for autoconnect. If set, connection profiles with higher priority will be preferred.
|
||||
type: int
|
||||
version_added: 11.0.0
|
||||
autoconnect_retries:
|
||||
description:
|
||||
- The number of times to retry autoconnecting.
|
||||
type: int
|
||||
version_added: 11.0.0
|
||||
conn_name:
|
||||
description:
|
||||
- The name used to call the connection. Pattern is V(<type>[-<ifname>][-<num>]).
|
||||
|
@ -1688,6 +1698,8 @@ class Nmcli(object):
|
|||
self.state = module.params['state']
|
||||
self.ignore_unsupported_suboptions = module.params['ignore_unsupported_suboptions']
|
||||
self.autoconnect = module.params['autoconnect']
|
||||
self.autoconnect_priority = module.params['autoconnect_priority']
|
||||
self.autoconnect_retries = module.params['autoconnect_retries']
|
||||
self.conn_name = module.params['conn_name']
|
||||
self.conn_reload = module.params['conn_reload']
|
||||
self.slave_type = module.params['slave_type']
|
||||
|
@ -1819,6 +1831,8 @@ class Nmcli(object):
|
|||
# Options common to multiple connection types.
|
||||
options = {
|
||||
'connection.autoconnect': self.autoconnect,
|
||||
'connection.autoconnect-priority': self.autoconnect_priority,
|
||||
'connection.autoconnect-retries': self.autoconnect_retries,
|
||||
'connection.zone': self.zone,
|
||||
}
|
||||
|
||||
|
@ -2262,6 +2276,9 @@ class Nmcli(object):
|
|||
'802-11-wireless-security.wep-key-flags',
|
||||
'802-11-wireless.mac-address-blacklist'):
|
||||
return list
|
||||
elif setting in ('connection.autoconnect-priority',
|
||||
'connection.autoconnect-retries'):
|
||||
return int
|
||||
return str
|
||||
|
||||
def get_route_params(self, raw_values):
|
||||
|
@ -2571,6 +2588,8 @@ def main():
|
|||
argument_spec=dict(
|
||||
ignore_unsupported_suboptions=dict(type='bool', default=False),
|
||||
autoconnect=dict(type='bool', default=True),
|
||||
autoconnect_priority=dict(type='int'),
|
||||
autoconnect_retries=dict(type='int'),
|
||||
state=dict(type='str', required=True, choices=['absent', 'present', 'up', 'down']),
|
||||
conn_name=dict(type='str', required=True),
|
||||
conn_reload=dict(type='bool', default=False),
|
||||
|
|
|
@ -134,7 +134,6 @@ class Opkg(StateModuleHelper):
|
|||
executable=dict(type="path"),
|
||||
),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.vars.set("install_c", 0, output=False, change=True)
|
||||
|
|
|
@ -164,7 +164,6 @@ class PacemakerResource(StateModuleHelper):
|
|||
required_if=[('state', 'present', ['resource_type', 'resource_option'])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
default_state = "present"
|
||||
|
||||
def __init_module__(self):
|
||||
|
|
|
@ -54,11 +54,17 @@ options:
|
|||
name:
|
||||
type: str
|
||||
description:
|
||||
- The name of the application. In C(pipx) documentation it is also referred to as the name of the virtual environment
|
||||
where the application will be installed.
|
||||
- The name of the application and also the name of the Python package being installed.
|
||||
- In C(pipx) documentation it is also referred to as the name of the virtual environment where the application is installed.
|
||||
- If O(name) is a simple package name without version specifiers, then that name is used as the Python package name
|
||||
to be installed.
|
||||
- Use O(source) for passing package specifications or installing from URLs or directories.
|
||||
- Starting in community.general 10.7.0, you can use package specifiers when O(state=present) or O(state=install). For
|
||||
example, O(name=tox<4.0.0) or O(name=tox>3.0.27).
|
||||
- Please note that when you use O(state=present) and O(name) with version specifiers, contrary to the behavior of C(pipx),
|
||||
this module honors the version specifier and installs a version of the application that satisfies it. If you want
|
||||
to ensure the reinstallation of the application even when the version specifier is met, then you must use O(force=true),
|
||||
or perhaps use O(state=upgrade) instead.
|
||||
- Use O(source) for installing from URLs or directories.
|
||||
source:
|
||||
type: str
|
||||
description:
|
||||
|
@ -69,6 +75,7 @@ options:
|
|||
- The value of this option is passed as-is to C(pipx).
|
||||
- O(name) is still required when using O(source) to establish the application name without fetching the package from
|
||||
a remote source.
|
||||
- The module is not idempotent when using O(source).
|
||||
install_apps:
|
||||
description:
|
||||
- Add apps from the injected packages.
|
||||
|
@ -92,6 +99,7 @@ options:
|
|||
description:
|
||||
- Force modification of the application's virtual environment. See C(pipx) for details.
|
||||
- Only used when O(state=install), O(state=upgrade), O(state=upgrade_all), O(state=latest), or O(state=inject).
|
||||
- The module is not idempotent when O(force=true).
|
||||
type: bool
|
||||
default: false
|
||||
include_injected:
|
||||
|
@ -144,10 +152,10 @@ options:
|
|||
with O(community.general.pipx_info#module:include_raw=true) and obtaining the content from the RV(community.general.pipx_info#module:raw_output).
|
||||
type: path
|
||||
version_added: 9.4.0
|
||||
notes:
|
||||
- This first implementation does not verify whether a specified version constraint has been installed or not. Hence, when
|
||||
using version operators, C(pipx) module will always try to execute the operation, even when the application was previously
|
||||
installed. This feature will be added in the future.
|
||||
requirements:
|
||||
- When using O(name) with version specifiers, the Python package C(packaging) is required.
|
||||
- If the package C(packaging) is at a version lesser than C(22.0.0), it will fail silently when processing invalid specifiers,
|
||||
like C(tox<<<<4.0).
|
||||
author:
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
"""
|
||||
|
@ -201,7 +209,9 @@ version:
|
|||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_list
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_dict
|
||||
from ansible_collections.community.general.plugins.module_utils.pkg_req import PackageRequirement
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.facts.compat import ansible_facts
|
||||
|
||||
|
@ -255,21 +265,15 @@ class PipX(StateModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def _retrieve_installed(self):
|
||||
name = _make_name(self.vars.name, self.vars.suffix)
|
||||
output_process = make_process_list(self, include_injected=True, name=name)
|
||||
installed = self.runner('_list global', output_process=output_process).run()
|
||||
output_process = make_process_dict(include_injected=True)
|
||||
installed, dummy = self.runner('_list global', output_process=output_process).run()
|
||||
|
||||
if name is not None:
|
||||
app_list = [app for app in installed if app['name'] == name]
|
||||
if app_list:
|
||||
return {name: app_list[0]}
|
||||
else:
|
||||
return {}
|
||||
if self.app_name is None:
|
||||
return installed
|
||||
|
||||
return installed
|
||||
return {k: v for k, v in installed.items() if k == self.app_name}
|
||||
|
||||
def __init_module__(self):
|
||||
if self.vars.executable:
|
||||
|
@ -279,12 +283,20 @@ class PipX(StateModuleHelper):
|
|||
self.command = [facts['python']['executable'], '-m', 'pipx']
|
||||
self.runner = pipx_runner(self.module, self.command)
|
||||
|
||||
pkg_req = PackageRequirement(self.module, self.vars.name)
|
||||
self.parsed_name = pkg_req.parsed_name
|
||||
self.parsed_req = pkg_req.requirement
|
||||
self.app_name = _make_name(self.parsed_name, self.vars.suffix)
|
||||
|
||||
self.vars.set('application', self._retrieve_installed(), change=True, diff=True)
|
||||
|
||||
with self.runner("version") as ctx:
|
||||
rc, out, err = ctx.run()
|
||||
self.vars.version = out.strip()
|
||||
|
||||
if LooseVersion(self.vars.version) < LooseVersion("1.7.0"):
|
||||
self.do_raise("The pipx tool must be at least at version 1.7.0")
|
||||
|
||||
def __quit_module__(self):
|
||||
self.vars.application = self._retrieve_installed()
|
||||
|
||||
|
@ -295,12 +307,27 @@ class PipX(StateModuleHelper):
|
|||
self.vars.set('run_info', ctx.run_info, verbosity=4)
|
||||
|
||||
def state_install(self):
|
||||
if not self.vars.application or self.vars.force:
|
||||
self.changed = True
|
||||
args_order = 'state global index_url install_deps force python system_site_packages editable pip_args suffix name_source'
|
||||
with self.runner(args_order, check_mode_skip=True) as ctx:
|
||||
ctx.run(name_source=[self.vars.name, self.vars.source])
|
||||
self._capture_results(ctx)
|
||||
# If we have a version spec and no source, use the version spec as source
|
||||
if self.parsed_req and not self.vars.source:
|
||||
self.vars.source = self.vars.name
|
||||
|
||||
if self.vars.application.get(self.app_name):
|
||||
is_installed = True
|
||||
version_match = self.vars.application[self.app_name]['version'] in self.parsed_req.specifier if self.parsed_req else True
|
||||
force = self.vars.force or (not version_match)
|
||||
else:
|
||||
is_installed = False
|
||||
version_match = False
|
||||
force = self.vars.force
|
||||
|
||||
if is_installed and version_match and not force:
|
||||
return
|
||||
|
||||
self.changed = True
|
||||
args_order = 'state global index_url install_deps force python system_site_packages editable pip_args suffix name_source'
|
||||
with self.runner(args_order, check_mode_skip=True) as ctx:
|
||||
ctx.run(name_source=[self.parsed_name, self.vars.source], force=force)
|
||||
self._capture_results(ctx)
|
||||
|
||||
state_present = state_install
|
||||
|
||||
|
|
|
@ -126,7 +126,8 @@ version:
|
|||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_list
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_dict
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.facts.compat import ansible_facts
|
||||
|
||||
|
@ -144,7 +145,6 @@ class PipXInfo(ModuleHelper):
|
|||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
if self.vars.executable:
|
||||
|
@ -157,10 +157,24 @@ class PipXInfo(ModuleHelper):
|
|||
rc, out, err = ctx.run()
|
||||
self.vars.version = out.strip()
|
||||
|
||||
if LooseVersion(self.vars.version) < LooseVersion("1.7.0"):
|
||||
self.do_raise("The pipx tool must be at least at version 1.7.0")
|
||||
|
||||
def __run__(self):
|
||||
output_process = make_process_list(self, **self.vars.as_dict())
|
||||
output_process = make_process_dict(self.vars.include_injected, self.vars.include_deps)
|
||||
with self.runner('_list global', output_process=output_process) as ctx:
|
||||
self.vars.application = ctx.run()
|
||||
applications, raw_data = ctx.run()
|
||||
if self.vars.include_raw:
|
||||
self.vars.raw_output = raw_data
|
||||
|
||||
if self.vars.name:
|
||||
self.vars.application = [
|
||||
v
|
||||
for k, v in applications.items()
|
||||
if k == self.vars.name
|
||||
]
|
||||
else:
|
||||
self.vars.application = list(applications.values())
|
||||
self._capture_results(ctx)
|
||||
|
||||
def _capture_results(self, ctx):
|
||||
|
|
|
@ -1,671 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: profitbricks
|
||||
short_description: Create, destroy, start, stop, and reboot a ProfitBricks virtual machine
|
||||
description:
|
||||
- Create, destroy, update, start, stop, and reboot a ProfitBricks virtual machine. When the virtual machine is created it
|
||||
can optionally wait for it to be 'running' before returning. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
auto_increment:
|
||||
description:
|
||||
- Whether or not to increment a single number in the name for created virtual machines.
|
||||
type: bool
|
||||
default: true
|
||||
name:
|
||||
description:
|
||||
- The name of the virtual machine.
|
||||
type: str
|
||||
image:
|
||||
description:
|
||||
- The system image ID for creating the virtual machine, for example V(a3eae284-a2fe-11e4-b187-5f1f641608c8).
|
||||
type: str
|
||||
image_password:
|
||||
description:
|
||||
- Password set for the administrative user.
|
||||
type: str
|
||||
ssh_keys:
|
||||
description:
|
||||
- Public SSH keys allowing access to the virtual machine.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
datacenter:
|
||||
description:
|
||||
- The datacenter to provision this virtual machine.
|
||||
type: str
|
||||
cores:
|
||||
description:
|
||||
- The number of CPU cores to allocate to the virtual machine.
|
||||
default: 2
|
||||
type: int
|
||||
ram:
|
||||
description:
|
||||
- The amount of memory to allocate to the virtual machine.
|
||||
default: 2048
|
||||
type: int
|
||||
cpu_family:
|
||||
description:
|
||||
- The CPU family type to allocate to the virtual machine.
|
||||
type: str
|
||||
default: AMD_OPTERON
|
||||
choices: ["AMD_OPTERON", "INTEL_XEON"]
|
||||
volume_size:
|
||||
description:
|
||||
- The size in GB of the boot volume.
|
||||
type: int
|
||||
default: 10
|
||||
bus:
|
||||
description:
|
||||
- The bus type for the volume.
|
||||
type: str
|
||||
default: VIRTIO
|
||||
choices: ["IDE", "VIRTIO"]
|
||||
instance_ids:
|
||||
description:
|
||||
- List of instance IDs, currently only used when state='absent' to remove instances.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
count:
|
||||
description:
|
||||
- The number of virtual machines to create.
|
||||
type: int
|
||||
default: 1
|
||||
location:
|
||||
description:
|
||||
- The datacenter location. Use only if you want to create the Datacenter or else this value is ignored.
|
||||
type: str
|
||||
default: us/las
|
||||
choices: ["us/las", "de/fra", "de/fkb"]
|
||||
assign_public_ip:
|
||||
description:
|
||||
- This will assign the machine to the public LAN. If no LAN exists with public Internet access it is created.
|
||||
type: bool
|
||||
default: false
|
||||
lan:
|
||||
description:
|
||||
- The ID of the LAN you wish to add the servers to.
|
||||
type: int
|
||||
default: 1
|
||||
subscription_user:
|
||||
description:
|
||||
- The ProfitBricks username. Overrides the E(PB_SUBSCRIPTION_ID) environment variable.
|
||||
type: str
|
||||
subscription_password:
|
||||
description:
|
||||
- THe ProfitBricks password. Overrides the E(PB_PASSWORD) environment variable.
|
||||
type: str
|
||||
wait:
|
||||
description:
|
||||
- Wait for the instance to be in state 'running' before returning.
|
||||
type: bool
|
||||
default: true
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds.
|
||||
type: int
|
||||
default: 600
|
||||
remove_boot_volume:
|
||||
description:
|
||||
- Remove the bootVolume of the virtual machine you are destroying.
|
||||
type: bool
|
||||
default: true
|
||||
state:
|
||||
description:
|
||||
- Create or terminate instances.
|
||||
- 'The choices available are: V(running), V(stopped), V(absent), V(present).'
|
||||
type: str
|
||||
default: 'present'
|
||||
disk_type:
|
||||
description:
|
||||
- The type of disk to be allocated.
|
||||
type: str
|
||||
choices: [SSD, HDD]
|
||||
default: HDD
|
||||
|
||||
requirements:
|
||||
- "profitbricks"
|
||||
author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Note: These examples do not set authentication details, see the AWS Guide for details.
|
||||
|
||||
# Provisioning example
|
||||
- name: Create three servers and enumerate their names
|
||||
community.general.profitbricks:
|
||||
datacenter: Tardis One
|
||||
name: web%02d.stackpointcloud.com
|
||||
cores: 4
|
||||
ram: 2048
|
||||
volume_size: 50
|
||||
cpu_family: INTEL_XEON
|
||||
image: a3eae284-a2fe-11e4-b187-5f1f641608c8
|
||||
location: us/las
|
||||
count: 3
|
||||
assign_public_ip: true
|
||||
|
||||
- name: Remove virtual machines
|
||||
community.general.profitbricks:
|
||||
datacenter: Tardis One
|
||||
instance_ids:
|
||||
- 'web001.stackpointcloud.com'
|
||||
- 'web002.stackpointcloud.com'
|
||||
- 'web003.stackpointcloud.com'
|
||||
wait_timeout: 500
|
||||
state: absent
|
||||
|
||||
- name: Start virtual machines
|
||||
community.general.profitbricks:
|
||||
datacenter: Tardis One
|
||||
instance_ids:
|
||||
- 'web001.stackpointcloud.com'
|
||||
- 'web002.stackpointcloud.com'
|
||||
- 'web003.stackpointcloud.com'
|
||||
wait_timeout: 500
|
||||
state: running
|
||||
|
||||
- name: Stop virtual machines
|
||||
community.general.profitbricks:
|
||||
datacenter: Tardis One
|
||||
instance_ids:
|
||||
- 'web001.stackpointcloud.com'
|
||||
- 'web002.stackpointcloud.com'
|
||||
- 'web003.stackpointcloud.com'
|
||||
wait_timeout: 500
|
||||
state: stopped
|
||||
"""
|
||||
|
||||
import re
|
||||
import uuid
|
||||
import time
|
||||
import traceback
|
||||
|
||||
HAS_PB_SDK = True
|
||||
|
||||
try:
|
||||
from profitbricks.client import ProfitBricksService, Volume, Server, Datacenter, NIC, LAN
|
||||
except ImportError:
|
||||
HAS_PB_SDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves import xrange
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
LOCATIONS = ['us/las',
|
||||
'de/fra',
|
||||
'de/fkb']
|
||||
|
||||
uuid_match = re.compile(
|
||||
r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
|
||||
|
||||
|
||||
def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
|
||||
if not promise:
|
||||
return
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
time.sleep(5)
|
||||
operation_result = profitbricks.get_request(
|
||||
request_id=promise['requestId'],
|
||||
status=True)
|
||||
|
||||
if operation_result['metadata']['status'] == "DONE":
|
||||
return
|
||||
elif operation_result['metadata']['status'] == "FAILED":
|
||||
raise Exception(
|
||||
'Request failed to complete ' + msg + ' "' + str(
|
||||
promise['requestId']) + '" to complete.')
|
||||
|
||||
raise Exception(
|
||||
'Timed out waiting for async operation ' + msg + ' "' + str(
|
||||
promise['requestId']
|
||||
) + '" to complete.')
|
||||
|
||||
|
||||
def _create_machine(module, profitbricks, datacenter, name):
|
||||
cores = module.params.get('cores')
|
||||
ram = module.params.get('ram')
|
||||
cpu_family = module.params.get('cpu_family')
|
||||
volume_size = module.params.get('volume_size')
|
||||
disk_type = module.params.get('disk_type')
|
||||
image_password = module.params.get('image_password')
|
||||
ssh_keys = module.params.get('ssh_keys')
|
||||
bus = module.params.get('bus')
|
||||
lan = module.params.get('lan')
|
||||
assign_public_ip = module.params.get('assign_public_ip')
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
location = module.params.get('location')
|
||||
image = module.params.get('image')
|
||||
assign_public_ip = module.boolean(module.params.get('assign_public_ip'))
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
|
||||
if assign_public_ip:
|
||||
public_found = False
|
||||
|
||||
lans = profitbricks.list_lans(datacenter)
|
||||
for lan in lans['items']:
|
||||
if lan['properties']['public']:
|
||||
public_found = True
|
||||
lan = lan['id']
|
||||
|
||||
if not public_found:
|
||||
i = LAN(
|
||||
name='public',
|
||||
public=True)
|
||||
|
||||
lan_response = profitbricks.create_lan(datacenter, i)
|
||||
_wait_for_completion(profitbricks, lan_response,
|
||||
wait_timeout, "_create_machine")
|
||||
lan = lan_response['id']
|
||||
|
||||
v = Volume(
|
||||
name=str(uuid.uuid4()).replace('-', '')[:10],
|
||||
size=volume_size,
|
||||
image=image,
|
||||
image_password=image_password,
|
||||
ssh_keys=ssh_keys,
|
||||
disk_type=disk_type,
|
||||
bus=bus)
|
||||
|
||||
n = NIC(
|
||||
lan=int(lan)
|
||||
)
|
||||
|
||||
s = Server(
|
||||
name=name,
|
||||
ram=ram,
|
||||
cores=cores,
|
||||
cpu_family=cpu_family,
|
||||
create_volumes=[v],
|
||||
nics=[n],
|
||||
)
|
||||
|
||||
try:
|
||||
create_server_response = profitbricks.create_server(
|
||||
datacenter_id=datacenter, server=s)
|
||||
|
||||
_wait_for_completion(profitbricks, create_server_response,
|
||||
wait_timeout, "create_virtual_machine")
|
||||
|
||||
server_response = profitbricks.get_server(
|
||||
datacenter_id=datacenter,
|
||||
server_id=create_server_response['id'],
|
||||
depth=3
|
||||
)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to create the new server: %s" % str(e))
|
||||
else:
|
||||
return server_response
|
||||
|
||||
|
||||
def _startstop_machine(module, profitbricks, datacenter_id, server_id):
|
||||
state = module.params.get('state')
|
||||
|
||||
try:
|
||||
if state == 'running':
|
||||
profitbricks.start_server(datacenter_id, server_id)
|
||||
else:
|
||||
profitbricks.stop_server(datacenter_id, server_id)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to start or stop the virtual machine %s at %s: %s" % (server_id, datacenter_id, str(e)))
|
||||
|
||||
|
||||
def _create_datacenter(module, profitbricks):
|
||||
datacenter = module.params.get('datacenter')
|
||||
location = module.params.get('location')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
|
||||
i = Datacenter(
|
||||
name=datacenter,
|
||||
location=location
|
||||
)
|
||||
|
||||
try:
|
||||
datacenter_response = profitbricks.create_datacenter(datacenter=i)
|
||||
|
||||
_wait_for_completion(profitbricks, datacenter_response,
|
||||
wait_timeout, "_create_datacenter")
|
||||
|
||||
return datacenter_response
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to create the new server(s): %s" % str(e))
|
||||
|
||||
|
||||
def create_virtual_machine(module, profitbricks):
|
||||
"""
|
||||
Create new virtual machine
|
||||
|
||||
module : AnsibleModule object
|
||||
community.general.profitbricks: authenticated profitbricks object
|
||||
|
||||
Returns:
|
||||
True if a new virtual machine was created, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
name = module.params.get('name')
|
||||
auto_increment = module.params.get('auto_increment')
|
||||
count = module.params.get('count')
|
||||
lan = module.params.get('lan')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
failed = True
|
||||
datacenter_found = False
|
||||
|
||||
virtual_machines = []
|
||||
virtual_machine_ids = []
|
||||
|
||||
# Locate UUID for datacenter if referenced by name.
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
datacenter_id = _get_datacenter_id(datacenter_list, datacenter)
|
||||
if datacenter_id:
|
||||
datacenter_found = True
|
||||
|
||||
if not datacenter_found:
|
||||
datacenter_response = _create_datacenter(module, profitbricks)
|
||||
datacenter_id = datacenter_response['id']
|
||||
|
||||
_wait_for_completion(profitbricks, datacenter_response,
|
||||
wait_timeout, "create_virtual_machine")
|
||||
|
||||
if auto_increment:
|
||||
numbers = set()
|
||||
count_offset = 1
|
||||
|
||||
try:
|
||||
name % 0
|
||||
except TypeError as e:
|
||||
if e.message.startswith('not all'):
|
||||
name = '%s%%d' % name
|
||||
else:
|
||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
||||
|
||||
number_range = xrange(count_offset, count_offset + count + len(numbers))
|
||||
available_numbers = list(set(number_range).difference(numbers))
|
||||
names = []
|
||||
numbers_to_use = available_numbers[:count]
|
||||
for number in numbers_to_use:
|
||||
names.append(name % number)
|
||||
else:
|
||||
names = [name]
|
||||
|
||||
# Prefetch a list of servers for later comparison.
|
||||
server_list = profitbricks.list_servers(datacenter_id)
|
||||
for name in names:
|
||||
# Skip server creation if the server already exists.
|
||||
if _get_server_id(server_list, name):
|
||||
continue
|
||||
|
||||
create_response = _create_machine(module, profitbricks, str(datacenter_id), name)
|
||||
nics = profitbricks.list_nics(datacenter_id, create_response['id'])
|
||||
for n in nics['items']:
|
||||
if lan == n['properties']['lan']:
|
||||
create_response.update({'public_ip': n['properties']['ips'][0]})
|
||||
|
||||
virtual_machines.append(create_response)
|
||||
|
||||
failed = False
|
||||
|
||||
results = {
|
||||
'failed': failed,
|
||||
'machines': virtual_machines,
|
||||
'action': 'create',
|
||||
'instance_ids': {
|
||||
'instances': [i['id'] for i in virtual_machines],
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def remove_virtual_machine(module, profitbricks):
|
||||
"""
|
||||
Removes a virtual machine.
|
||||
|
||||
This will remove the virtual machine along with the bootVolume.
|
||||
|
||||
module : AnsibleModule object
|
||||
community.general.profitbricks: authenticated profitbricks object.
|
||||
|
||||
Not yet supported: handle deletion of attached data disks.
|
||||
|
||||
Returns:
|
||||
True if a new virtual server was deleted, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
instance_ids = module.params.get('instance_ids')
|
||||
remove_boot_volume = module.params.get('remove_boot_volume')
|
||||
changed = False
|
||||
|
||||
if not isinstance(module.params.get('instance_ids'), list) or len(module.params.get('instance_ids')) < 1:
|
||||
module.fail_json(msg='instance_ids should be a list of virtual machine ids or names, aborting')
|
||||
|
||||
# Locate UUID for datacenter if referenced by name.
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
datacenter_id = _get_datacenter_id(datacenter_list, datacenter)
|
||||
if not datacenter_id:
|
||||
module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter))
|
||||
|
||||
# Prefetch server list for later comparison.
|
||||
server_list = profitbricks.list_servers(datacenter_id)
|
||||
for instance in instance_ids:
|
||||
# Locate UUID for server if referenced by name.
|
||||
server_id = _get_server_id(server_list, instance)
|
||||
if server_id:
|
||||
# Remove the server's boot volume
|
||||
if remove_boot_volume:
|
||||
_remove_boot_volume(module, profitbricks, datacenter_id, server_id)
|
||||
|
||||
# Remove the server
|
||||
try:
|
||||
server_response = profitbricks.delete_server(datacenter_id, server_id)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to terminate the virtual server: %s" % to_native(e), exception=traceback.format_exc())
|
||||
else:
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def _remove_boot_volume(module, profitbricks, datacenter_id, server_id):
|
||||
"""
|
||||
Remove the boot volume from the server
|
||||
"""
|
||||
try:
|
||||
server = profitbricks.get_server(datacenter_id, server_id)
|
||||
volume_id = server['properties']['bootVolume']['id']
|
||||
volume_response = profitbricks.delete_volume(datacenter_id, volume_id)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to remove the server's boot volume: %s" % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
def startstop_machine(module, profitbricks, state):
|
||||
"""
|
||||
Starts or Stops a virtual machine.
|
||||
|
||||
module : AnsibleModule object
|
||||
community.general.profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True when the servers process the action successfully, false otherwise.
|
||||
"""
|
||||
if not isinstance(module.params.get('instance_ids'), list) or len(module.params.get('instance_ids')) < 1:
|
||||
module.fail_json(msg='instance_ids should be a list of virtual machine ids or names, aborting')
|
||||
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
changed = False
|
||||
|
||||
datacenter = module.params.get('datacenter')
|
||||
instance_ids = module.params.get('instance_ids')
|
||||
|
||||
# Locate UUID for datacenter if referenced by name.
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
datacenter_id = _get_datacenter_id(datacenter_list, datacenter)
|
||||
if not datacenter_id:
|
||||
module.fail_json(msg='Virtual data center \'%s\' not found.' % str(datacenter))
|
||||
|
||||
# Prefetch server list for later comparison.
|
||||
server_list = profitbricks.list_servers(datacenter_id)
|
||||
for instance in instance_ids:
|
||||
# Locate UUID of server if referenced by name.
|
||||
server_id = _get_server_id(server_list, instance)
|
||||
if server_id:
|
||||
_startstop_machine(module, profitbricks, datacenter_id, server_id)
|
||||
changed = True
|
||||
|
||||
if wait:
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
matched_instances = []
|
||||
for res in profitbricks.list_servers(datacenter_id)['items']:
|
||||
if state == 'running':
|
||||
if res['properties']['vmState'].lower() == state:
|
||||
matched_instances.append(res)
|
||||
elif state == 'stopped':
|
||||
if res['properties']['vmState'].lower() == 'shutoff':
|
||||
matched_instances.append(res)
|
||||
|
||||
if len(matched_instances) < len(instance_ids):
|
||||
time.sleep(5)
|
||||
else:
|
||||
break
|
||||
|
||||
if wait_timeout <= time.time():
|
||||
# waiting took too long
|
||||
module.fail_json(msg="wait for virtual machine state timeout on %s" % time.asctime())
|
||||
|
||||
return (changed)
|
||||
|
||||
|
||||
def _get_datacenter_id(datacenters, identity):
|
||||
"""
|
||||
Fetch and return datacenter UUID by datacenter name if found.
|
||||
"""
|
||||
for datacenter in datacenters['items']:
|
||||
if identity in (datacenter['properties']['name'], datacenter['id']):
|
||||
return datacenter['id']
|
||||
return None
|
||||
|
||||
|
||||
def _get_server_id(servers, identity):
|
||||
"""
|
||||
Fetch and return server UUID by server name if found.
|
||||
"""
|
||||
for server in servers['items']:
|
||||
if identity in (server['properties']['name'], server['id']):
|
||||
return server['id']
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
datacenter=dict(),
|
||||
name=dict(),
|
||||
image=dict(),
|
||||
cores=dict(type='int', default=2),
|
||||
ram=dict(type='int', default=2048),
|
||||
cpu_family=dict(choices=['AMD_OPTERON', 'INTEL_XEON'],
|
||||
default='AMD_OPTERON'),
|
||||
volume_size=dict(type='int', default=10),
|
||||
disk_type=dict(choices=['HDD', 'SSD'], default='HDD'),
|
||||
image_password=dict(no_log=True),
|
||||
ssh_keys=dict(type='list', elements='str', default=[], no_log=False),
|
||||
bus=dict(choices=['VIRTIO', 'IDE'], default='VIRTIO'),
|
||||
lan=dict(type='int', default=1),
|
||||
count=dict(type='int', default=1),
|
||||
auto_increment=dict(type='bool', default=True),
|
||||
instance_ids=dict(type='list', elements='str', default=[]),
|
||||
subscription_user=dict(),
|
||||
subscription_password=dict(no_log=True),
|
||||
location=dict(choices=LOCATIONS, default='us/las'),
|
||||
assign_public_ip=dict(type='bool', default=False),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(type='int', default=600),
|
||||
remove_boot_volume=dict(type='bool', default=True),
|
||||
state=dict(default='present'),
|
||||
)
|
||||
)
|
||||
|
||||
if not HAS_PB_SDK:
|
||||
module.fail_json(msg='profitbricks required for this module')
|
||||
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
|
||||
profitbricks = ProfitBricksService(
|
||||
username=subscription_user,
|
||||
password=subscription_password)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'absent':
|
||||
if not module.params.get('datacenter'):
|
||||
module.fail_json(msg='datacenter parameter is required ' +
|
||||
'for running or stopping machines.')
|
||||
|
||||
try:
|
||||
(changed) = remove_virtual_machine(module, profitbricks)
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set instance state: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
elif state in ('running', 'stopped'):
|
||||
if not module.params.get('datacenter'):
|
||||
module.fail_json(msg='datacenter parameter is required for ' +
|
||||
'running or stopping machines.')
|
||||
try:
|
||||
(changed) = startstop_machine(module, profitbricks, state)
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set instance state: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
elif state == 'present':
|
||||
if not module.params.get('name'):
|
||||
module.fail_json(msg='name parameter is required for new instance')
|
||||
if not module.params.get('image'):
|
||||
module.fail_json(msg='image parameter is required for new instance')
|
||||
if not module.params.get('subscription_user'):
|
||||
module.fail_json(msg='subscription_user parameter is ' +
|
||||
'required for new instance')
|
||||
if not module.params.get('subscription_password'):
|
||||
module.fail_json(msg='subscription_password parameter is ' +
|
||||
'required for new instance')
|
||||
|
||||
try:
|
||||
(machine_dict_array) = create_virtual_machine(module, profitbricks)
|
||||
module.exit_json(**machine_dict_array)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set instance state: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,273 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: profitbricks_datacenter
|
||||
short_description: Create or destroy a ProfitBricks Virtual Datacenter
|
||||
description:
|
||||
- This is a simple module that supports creating or removing vDCs. A vDC is required before you can create servers. This
|
||||
module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the virtual datacenter.
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- The description of the virtual datacenter.
|
||||
type: str
|
||||
required: false
|
||||
location:
|
||||
description:
|
||||
- The datacenter location.
|
||||
type: str
|
||||
required: false
|
||||
default: us/las
|
||||
choices: ["us/las", "de/fra", "de/fkb"]
|
||||
subscription_user:
|
||||
description:
|
||||
- The ProfitBricks username. Overrides the E(PB_SUBSCRIPTION_ID) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
subscription_password:
|
||||
description:
|
||||
- THe ProfitBricks password. Overrides the E(PB_PASSWORD) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
wait:
|
||||
description:
|
||||
- Wait for the datacenter to be created before returning.
|
||||
required: false
|
||||
default: true
|
||||
type: bool
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds.
|
||||
type: int
|
||||
default: 600
|
||||
state:
|
||||
description:
|
||||
- Create or terminate datacenters.
|
||||
- 'The available choices are: V(present), V(absent).'
|
||||
type: str
|
||||
required: false
|
||||
default: 'present'
|
||||
|
||||
requirements: ["profitbricks"]
|
||||
author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create a datacenter
|
||||
community.general.profitbricks_datacenter:
|
||||
datacenter: Tardis One
|
||||
wait_timeout: 500
|
||||
|
||||
- name: Destroy a datacenter (remove all servers, volumes, and other objects in the datacenter)
|
||||
community.general.profitbricks_datacenter:
|
||||
datacenter: Tardis One
|
||||
wait_timeout: 500
|
||||
state: absent
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
HAS_PB_SDK = True
|
||||
try:
|
||||
from profitbricks.client import ProfitBricksService, Datacenter
|
||||
except ImportError:
|
||||
HAS_PB_SDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
LOCATIONS = ['us/las',
|
||||
'de/fra',
|
||||
'de/fkb']
|
||||
|
||||
uuid_match = re.compile(
|
||||
r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
|
||||
|
||||
|
||||
def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
|
||||
if not promise:
|
||||
return
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
time.sleep(5)
|
||||
operation_result = profitbricks.get_request(
|
||||
request_id=promise['requestId'],
|
||||
status=True)
|
||||
|
||||
if operation_result['metadata']['status'] == "DONE":
|
||||
return
|
||||
elif operation_result['metadata']['status'] == "FAILED":
|
||||
raise Exception(
|
||||
'Request failed to complete ' + msg + ' "' + str(
|
||||
promise['requestId']) + '" to complete.')
|
||||
|
||||
raise Exception(
|
||||
'Timed out waiting for async operation ' + msg + ' "' + str(
|
||||
promise['requestId']
|
||||
) + '" to complete.')
|
||||
|
||||
|
||||
def _remove_datacenter(module, profitbricks, datacenter):
|
||||
try:
|
||||
profitbricks.delete_datacenter(datacenter)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to remove the datacenter: %s" % str(e))
|
||||
|
||||
|
||||
def create_datacenter(module, profitbricks):
|
||||
"""
|
||||
Creates a Datacenter
|
||||
|
||||
This will create a new Datacenter in the specified location.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if a new datacenter was created, false otherwise
|
||||
"""
|
||||
name = module.params.get('name')
|
||||
location = module.params.get('location')
|
||||
description = module.params.get('description')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = int(module.params.get('wait_timeout'))
|
||||
|
||||
i = Datacenter(
|
||||
name=name,
|
||||
location=location,
|
||||
description=description
|
||||
)
|
||||
|
||||
try:
|
||||
datacenter_response = profitbricks.create_datacenter(datacenter=i)
|
||||
|
||||
if wait:
|
||||
_wait_for_completion(profitbricks, datacenter_response,
|
||||
wait_timeout, "_create_datacenter")
|
||||
|
||||
results = {
|
||||
'datacenter_id': datacenter_response['id']
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to create the new datacenter: %s" % str(e))
|
||||
|
||||
|
||||
def remove_datacenter(module, profitbricks):
|
||||
"""
|
||||
Removes a Datacenter.
|
||||
|
||||
This will remove a datacenter.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the datacenter was deleted, false otherwise
|
||||
"""
|
||||
name = module.params.get('name')
|
||||
changed = False
|
||||
|
||||
if uuid_match.match(name):
|
||||
_remove_datacenter(module, profitbricks, name)
|
||||
changed = True
|
||||
else:
|
||||
datacenters = profitbricks.list_datacenters()
|
||||
|
||||
for d in datacenters['items']:
|
||||
vdc = profitbricks.get_datacenter(d['id'])
|
||||
|
||||
if name == vdc['properties']['name']:
|
||||
name = d['id']
|
||||
_remove_datacenter(module, profitbricks, name)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(),
|
||||
description=dict(),
|
||||
location=dict(choices=LOCATIONS, default='us/las'),
|
||||
subscription_user=dict(),
|
||||
subscription_password=dict(no_log=True),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(default=600, type='int'),
|
||||
state=dict(default='present'), # @TODO add choices
|
||||
)
|
||||
)
|
||||
if not HAS_PB_SDK:
|
||||
module.fail_json(msg='profitbricks required for this module')
|
||||
|
||||
if not module.params.get('subscription_user'):
|
||||
module.fail_json(msg='subscription_user parameter is required')
|
||||
if not module.params.get('subscription_password'):
|
||||
module.fail_json(msg='subscription_password parameter is required')
|
||||
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
|
||||
profitbricks = ProfitBricksService(
|
||||
username=subscription_user,
|
||||
password=subscription_password)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'absent':
|
||||
if not module.params.get('name'):
|
||||
module.fail_json(msg='name parameter is required deleting a virtual datacenter.')
|
||||
|
||||
try:
|
||||
(changed) = remove_datacenter(module, profitbricks)
|
||||
module.exit_json(
|
||||
changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set datacenter state: %s' % str(e))
|
||||
|
||||
elif state == 'present':
|
||||
if not module.params.get('name'):
|
||||
module.fail_json(msg='name parameter is required for a new datacenter')
|
||||
if not module.params.get('location'):
|
||||
module.fail_json(msg='location parameter is required for a new datacenter')
|
||||
|
||||
try:
|
||||
(datacenter_dict_array) = create_datacenter(module, profitbricks)
|
||||
module.exit_json(**datacenter_dict_array)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set datacenter state: %s' % str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,303 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: profitbricks_nic
|
||||
short_description: Create or Remove a NIC
|
||||
description:
|
||||
- This module allows you to create or restore a volume snapshot. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
datacenter:
|
||||
description:
|
||||
- The datacenter in which to operate.
|
||||
type: str
|
||||
required: true
|
||||
server:
|
||||
description:
|
||||
- The server name or ID.
|
||||
type: str
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- The name or ID of the NIC. This is only required on deletes, but not on create.
|
||||
- If not specified, it defaults to a value based on UUID4.
|
||||
type: str
|
||||
lan:
|
||||
description:
|
||||
- The LAN to place the NIC on. You can pass a LAN that does not exist and it will be created. Required on create.
|
||||
type: str
|
||||
subscription_user:
|
||||
description:
|
||||
- The ProfitBricks username. Overrides the E(PB_SUBSCRIPTION_ID) environment variable.
|
||||
type: str
|
||||
required: true
|
||||
subscription_password:
|
||||
description:
|
||||
- THe ProfitBricks password. Overrides the E(PB_PASSWORD) environment variable.
|
||||
type: str
|
||||
required: true
|
||||
wait:
|
||||
description:
|
||||
- Wait for the operation to complete before returning.
|
||||
required: false
|
||||
default: true
|
||||
type: bool
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds.
|
||||
type: int
|
||||
default: 600
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource.
|
||||
- 'The available choices are: V(present), V(absent).'
|
||||
type: str
|
||||
required: false
|
||||
default: 'present'
|
||||
|
||||
requirements: ["profitbricks"]
|
||||
author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create a NIC
|
||||
community.general.profitbricks_nic:
|
||||
datacenter: Tardis One
|
||||
server: node002
|
||||
lan: 2
|
||||
wait_timeout: 500
|
||||
state: present
|
||||
|
||||
- name: Remove a NIC
|
||||
community.general.profitbricks_nic:
|
||||
datacenter: Tardis One
|
||||
server: node002
|
||||
name: 7341c2454f
|
||||
wait_timeout: 500
|
||||
state: absent
|
||||
"""
|
||||
|
||||
import re
|
||||
import uuid
|
||||
import time
|
||||
|
||||
HAS_PB_SDK = True
|
||||
try:
|
||||
from profitbricks.client import ProfitBricksService, NIC
|
||||
except ImportError:
|
||||
HAS_PB_SDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
uuid_match = re.compile(
|
||||
r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
|
||||
|
||||
|
||||
def _make_default_name():
|
||||
return str(uuid.uuid4()).replace('-', '')[:10]
|
||||
|
||||
|
||||
def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
|
||||
if not promise:
|
||||
return
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
time.sleep(5)
|
||||
operation_result = profitbricks.get_request(
|
||||
request_id=promise['requestId'],
|
||||
status=True)
|
||||
|
||||
if operation_result['metadata']['status'] == "DONE":
|
||||
return
|
||||
elif operation_result['metadata']['status'] == "FAILED":
|
||||
raise Exception(
|
||||
'Request failed to complete ' + msg + ' "' + str(
|
||||
promise['requestId']) + '" to complete.')
|
||||
|
||||
raise Exception(
|
||||
'Timed out waiting for async operation ' + msg + ' "' + str(
|
||||
promise['requestId']
|
||||
) + '" to complete.')
|
||||
|
||||
|
||||
def create_nic(module, profitbricks):
|
||||
"""
|
||||
Creates a NIC.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the nic creates, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
server = module.params.get('server')
|
||||
lan = module.params.get('lan')
|
||||
name = module.params.get('name')
|
||||
if name is None:
|
||||
name = _make_default_name()
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Server
|
||||
if not (uuid_match.match(server)):
|
||||
server_list = profitbricks.list_servers(datacenter)
|
||||
for s in server_list['items']:
|
||||
if server == s['properties']['name']:
|
||||
server = s['id']
|
||||
break
|
||||
try:
|
||||
n = NIC(
|
||||
name=name,
|
||||
lan=lan
|
||||
)
|
||||
|
||||
nic_response = profitbricks.create_nic(datacenter, server, n)
|
||||
|
||||
if wait:
|
||||
_wait_for_completion(profitbricks, nic_response,
|
||||
wait_timeout, "create_nic")
|
||||
|
||||
return nic_response
|
||||
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to create the NIC: %s" % str(e))
|
||||
|
||||
|
||||
def delete_nic(module, profitbricks):
|
||||
"""
|
||||
Removes a NIC
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the NIC was removed, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
server = module.params.get('server')
|
||||
name = module.params.get('name')
|
||||
if name is None:
|
||||
name = _make_default_name()
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Server
|
||||
server_found = False
|
||||
if not (uuid_match.match(server)):
|
||||
server_list = profitbricks.list_servers(datacenter)
|
||||
for s in server_list['items']:
|
||||
if server == s['properties']['name']:
|
||||
server_found = True
|
||||
server = s['id']
|
||||
break
|
||||
|
||||
if not server_found:
|
||||
return False
|
||||
|
||||
# Locate UUID for NIC
|
||||
nic_found = False
|
||||
if not (uuid_match.match(name)):
|
||||
nic_list = profitbricks.list_nics(datacenter, server)
|
||||
for n in nic_list['items']:
|
||||
if name == n['properties']['name']:
|
||||
nic_found = True
|
||||
name = n['id']
|
||||
break
|
||||
|
||||
if not nic_found:
|
||||
return False
|
||||
|
||||
try:
|
||||
nic_response = profitbricks.delete_nic(datacenter, server, name)
|
||||
return nic_response
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to remove the NIC: %s" % str(e))
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
datacenter=dict(required=True),
|
||||
server=dict(required=True),
|
||||
name=dict(),
|
||||
lan=dict(),
|
||||
subscription_user=dict(required=True),
|
||||
subscription_password=dict(required=True, no_log=True),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(type='int', default=600),
|
||||
state=dict(default='present'),
|
||||
),
|
||||
required_if=(
|
||||
('state', 'absent', ['name']),
|
||||
('state', 'present', ['lan']),
|
||||
)
|
||||
)
|
||||
|
||||
if not HAS_PB_SDK:
|
||||
module.fail_json(msg='profitbricks required for this module')
|
||||
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
|
||||
profitbricks = ProfitBricksService(
|
||||
username=subscription_user,
|
||||
password=subscription_password)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'absent':
|
||||
try:
|
||||
(changed) = delete_nic(module, profitbricks)
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set nic state: %s' % str(e))
|
||||
|
||||
elif state == 'present':
|
||||
try:
|
||||
(nic_dict) = create_nic(module, profitbricks)
|
||||
module.exit_json(nics=nic_dict) # @FIXME changed not calculated?
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set nic state: %s' % str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,448 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: profitbricks_volume
|
||||
short_description: Create or destroy a volume
|
||||
description:
|
||||
- Allows you to create or remove a volume from a ProfitBricks datacenter. This module has a dependency on profitbricks >=
|
||||
1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
datacenter:
|
||||
description:
|
||||
- The datacenter in which to create the volumes.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- The name of the volumes. You can enumerate the names using auto_increment.
|
||||
type: str
|
||||
size:
|
||||
description:
|
||||
- The size of the volume.
|
||||
type: int
|
||||
required: false
|
||||
default: 10
|
||||
bus:
|
||||
description:
|
||||
- The bus type.
|
||||
type: str
|
||||
required: false
|
||||
default: VIRTIO
|
||||
choices: ["IDE", "VIRTIO"]
|
||||
image:
|
||||
description:
|
||||
- The system image ID for the volume, for example V(a3eae284-a2fe-11e4-b187-5f1f641608c8). This can also be a snapshot
|
||||
image ID.
|
||||
type: str
|
||||
image_password:
|
||||
description:
|
||||
- Password set for the administrative user.
|
||||
type: str
|
||||
required: false
|
||||
ssh_keys:
|
||||
description:
|
||||
- Public SSH keys allowing access to the virtual machine.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
disk_type:
|
||||
description:
|
||||
- The disk type of the volume.
|
||||
type: str
|
||||
required: false
|
||||
default: HDD
|
||||
choices: ["HDD", "SSD"]
|
||||
licence_type:
|
||||
description:
|
||||
- The licence type for the volume. This is used when the image is non-standard.
|
||||
- 'The available choices are: V(LINUX), V(WINDOWS), V(UNKNOWN), V(OTHER).'
|
||||
type: str
|
||||
required: false
|
||||
default: UNKNOWN
|
||||
count:
|
||||
description:
|
||||
- The number of volumes you wish to create.
|
||||
type: int
|
||||
required: false
|
||||
default: 1
|
||||
auto_increment:
|
||||
description:
|
||||
- Whether or not to increment a single number in the name for created virtual machines.
|
||||
default: true
|
||||
type: bool
|
||||
instance_ids:
|
||||
description:
|
||||
- List of instance IDs, currently only used when O(state=absent) to remove instances.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
subscription_user:
|
||||
description:
|
||||
- The ProfitBricks username. Overrides the E(PB_SUBSCRIPTION_ID) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
subscription_password:
|
||||
description:
|
||||
- THe ProfitBricks password. Overrides the E(PB_PASSWORD) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
wait:
|
||||
description:
|
||||
- Wait for the datacenter to be created before returning.
|
||||
required: false
|
||||
default: true
|
||||
type: bool
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds.
|
||||
type: int
|
||||
default: 600
|
||||
state:
|
||||
description:
|
||||
- Create or terminate datacenters.
|
||||
- 'The available choices are: V(present), V(absent).'
|
||||
type: str
|
||||
required: false
|
||||
default: 'present'
|
||||
server:
|
||||
description:
|
||||
- Server name to attach the volume to.
|
||||
type: str
|
||||
|
||||
requirements: ["profitbricks"]
|
||||
author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create multiple volumes
|
||||
community.general.profitbricks_volume:
|
||||
datacenter: Tardis One
|
||||
name: vol%02d
|
||||
count: 5
|
||||
auto_increment: true
|
||||
wait_timeout: 500
|
||||
state: present
|
||||
|
||||
- name: Remove Volumes
|
||||
community.general.profitbricks_volume:
|
||||
datacenter: Tardis One
|
||||
instance_ids:
|
||||
- 'vol01'
|
||||
- 'vol02'
|
||||
wait_timeout: 500
|
||||
state: absent
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
HAS_PB_SDK = True
|
||||
try:
|
||||
from profitbricks.client import ProfitBricksService, Volume
|
||||
except ImportError:
|
||||
HAS_PB_SDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves import xrange
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
uuid_match = re.compile(
|
||||
r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
|
||||
|
||||
|
||||
def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
|
||||
if not promise:
|
||||
return
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
time.sleep(5)
|
||||
operation_result = profitbricks.get_request(
|
||||
request_id=promise['requestId'],
|
||||
status=True)
|
||||
|
||||
if operation_result['metadata']['status'] == "DONE":
|
||||
return
|
||||
elif operation_result['metadata']['status'] == "FAILED":
|
||||
raise Exception(
|
||||
'Request failed to complete ' + msg + ' "' + str(
|
||||
promise['requestId']) + '" to complete.')
|
||||
|
||||
raise Exception(
|
||||
'Timed out waiting for async operation ' + msg + ' "' + str(
|
||||
promise['requestId']
|
||||
) + '" to complete.')
|
||||
|
||||
|
||||
def _create_volume(module, profitbricks, datacenter, name):
|
||||
size = module.params.get('size')
|
||||
bus = module.params.get('bus')
|
||||
image = module.params.get('image')
|
||||
image_password = module.params.get('image_password')
|
||||
ssh_keys = module.params.get('ssh_keys')
|
||||
disk_type = module.params.get('disk_type')
|
||||
licence_type = module.params.get('licence_type')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
wait = module.params.get('wait')
|
||||
|
||||
try:
|
||||
v = Volume(
|
||||
name=name,
|
||||
size=size,
|
||||
bus=bus,
|
||||
image=image,
|
||||
image_password=image_password,
|
||||
ssh_keys=ssh_keys,
|
||||
disk_type=disk_type,
|
||||
licence_type=licence_type
|
||||
)
|
||||
|
||||
volume_response = profitbricks.create_volume(datacenter, v)
|
||||
|
||||
if wait:
|
||||
_wait_for_completion(profitbricks, volume_response,
|
||||
wait_timeout, "_create_volume")
|
||||
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to create the volume: %s" % str(e))
|
||||
|
||||
return volume_response
|
||||
|
||||
|
||||
def _delete_volume(module, profitbricks, datacenter, volume):
|
||||
try:
|
||||
profitbricks.delete_volume(datacenter, volume)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="failed to remove the volume: %s" % str(e))
|
||||
|
||||
|
||||
def create_volume(module, profitbricks):
|
||||
"""
|
||||
Creates a volume.
|
||||
|
||||
This will create a volume in a datacenter.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the volume was created, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
name = module.params.get('name')
|
||||
auto_increment = module.params.get('auto_increment')
|
||||
count = module.params.get('count')
|
||||
|
||||
datacenter_found = False
|
||||
failed = True
|
||||
volumes = []
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
datacenter_found = True
|
||||
break
|
||||
|
||||
if not datacenter_found:
|
||||
module.fail_json(msg='datacenter could not be found.')
|
||||
|
||||
if auto_increment:
|
||||
numbers = set()
|
||||
count_offset = 1
|
||||
|
||||
try:
|
||||
name % 0
|
||||
except TypeError as e:
|
||||
if e.message.startswith('not all'):
|
||||
name = '%s%%d' % name
|
||||
else:
|
||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
||||
|
||||
number_range = xrange(count_offset, count_offset + count + len(numbers))
|
||||
available_numbers = list(set(number_range).difference(numbers))
|
||||
names = []
|
||||
numbers_to_use = available_numbers[:count]
|
||||
for number in numbers_to_use:
|
||||
names.append(name % number)
|
||||
else:
|
||||
names = [name] * count
|
||||
|
||||
for name in names:
|
||||
create_response = _create_volume(module, profitbricks, str(datacenter), name)
|
||||
volumes.append(create_response)
|
||||
_attach_volume(module, profitbricks, datacenter, create_response['id'])
|
||||
failed = False
|
||||
|
||||
results = {
|
||||
'failed': failed,
|
||||
'volumes': volumes,
|
||||
'action': 'create',
|
||||
'instance_ids': {
|
||||
'instances': [i['id'] for i in volumes],
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def delete_volume(module, profitbricks):
|
||||
"""
|
||||
Removes a volume.
|
||||
|
||||
This will create a volume in a datacenter.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the volume was removed, false otherwise
|
||||
"""
|
||||
if not isinstance(module.params.get('instance_ids'), list) or len(module.params.get('instance_ids')) < 1:
|
||||
module.fail_json(msg='instance_ids should be a list of virtual machine ids or names, aborting')
|
||||
|
||||
datacenter = module.params.get('datacenter')
|
||||
changed = False
|
||||
instance_ids = module.params.get('instance_ids')
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
break
|
||||
|
||||
for n in instance_ids:
|
||||
if uuid_match.match(n):
|
||||
_delete_volume(module, profitbricks, datacenter, n)
|
||||
changed = True
|
||||
else:
|
||||
volumes = profitbricks.list_volumes(datacenter)
|
||||
for v in volumes['items']:
|
||||
if n == v['properties']['name']:
|
||||
volume_id = v['id']
|
||||
_delete_volume(module, profitbricks, datacenter, volume_id)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def _attach_volume(module, profitbricks, datacenter, volume):
|
||||
"""
|
||||
Attaches a volume.
|
||||
|
||||
This will attach a volume to the server.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the volume was attached, false otherwise
|
||||
"""
|
||||
server = module.params.get('server')
|
||||
|
||||
# Locate UUID for Server
|
||||
if server:
|
||||
if not (uuid_match.match(server)):
|
||||
server_list = profitbricks.list_servers(datacenter)
|
||||
for s in server_list['items']:
|
||||
if server == s['properties']['name']:
|
||||
server = s['id']
|
||||
break
|
||||
|
||||
try:
|
||||
return profitbricks.attach_volume(datacenter, server, volume)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to attach volume: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
datacenter=dict(),
|
||||
server=dict(),
|
||||
name=dict(),
|
||||
size=dict(type='int', default=10),
|
||||
bus=dict(choices=['VIRTIO', 'IDE'], default='VIRTIO'),
|
||||
image=dict(),
|
||||
image_password=dict(no_log=True),
|
||||
ssh_keys=dict(type='list', elements='str', default=[], no_log=False),
|
||||
disk_type=dict(choices=['HDD', 'SSD'], default='HDD'),
|
||||
licence_type=dict(default='UNKNOWN'),
|
||||
count=dict(type='int', default=1),
|
||||
auto_increment=dict(type='bool', default=True),
|
||||
instance_ids=dict(type='list', elements='str', default=[]),
|
||||
subscription_user=dict(),
|
||||
subscription_password=dict(no_log=True),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(type='int', default=600),
|
||||
state=dict(default='present'),
|
||||
)
|
||||
)
|
||||
|
||||
if not module.params.get('subscription_user'):
|
||||
module.fail_json(msg='subscription_user parameter is required')
|
||||
if not module.params.get('subscription_password'):
|
||||
module.fail_json(msg='subscription_password parameter is required')
|
||||
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
|
||||
profitbricks = ProfitBricksService(
|
||||
username=subscription_user,
|
||||
password=subscription_password)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'absent':
|
||||
if not module.params.get('datacenter'):
|
||||
module.fail_json(msg='datacenter parameter is required for running or stopping machines.')
|
||||
|
||||
try:
|
||||
(changed) = delete_volume(module, profitbricks)
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set volume state: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
elif state == 'present':
|
||||
if not module.params.get('datacenter'):
|
||||
module.fail_json(msg='datacenter parameter is required for new instance')
|
||||
if not module.params.get('name'):
|
||||
module.fail_json(msg='name parameter is required for new instance')
|
||||
|
||||
try:
|
||||
(volume_dict_array) = create_volume(module, profitbricks)
|
||||
module.exit_json(**volume_dict_array)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set volume state: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,273 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: profitbricks_volume_attachments
|
||||
short_description: Attach or detach a volume
|
||||
description:
|
||||
- Allows you to attach or detach a volume from a ProfitBricks server. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
datacenter:
|
||||
description:
|
||||
- The datacenter in which to operate.
|
||||
type: str
|
||||
server:
|
||||
description:
|
||||
- The name of the server you wish to detach or attach the volume.
|
||||
type: str
|
||||
volume:
|
||||
description:
|
||||
- The volume name or ID.
|
||||
type: str
|
||||
subscription_user:
|
||||
description:
|
||||
- The ProfitBricks username. Overrides the E(PB_SUBSCRIPTION_ID) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
subscription_password:
|
||||
description:
|
||||
- THe ProfitBricks password. Overrides the E(PB_PASSWORD) environment variable.
|
||||
type: str
|
||||
required: false
|
||||
wait:
|
||||
description:
|
||||
- Wait for the operation to complete before returning.
|
||||
required: false
|
||||
default: true
|
||||
type: bool
|
||||
wait_timeout:
|
||||
description:
|
||||
- How long before wait gives up, in seconds.
|
||||
type: int
|
||||
default: 600
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource.
|
||||
- 'The available choices are: V(present), V(absent).'
|
||||
type: str
|
||||
required: false
|
||||
default: 'present'
|
||||
|
||||
requirements: ["profitbricks"]
|
||||
author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Attach a volume
|
||||
community.general.profitbricks_volume_attachments:
|
||||
datacenter: Tardis One
|
||||
server: node002
|
||||
volume: vol01
|
||||
wait_timeout: 500
|
||||
state: present
|
||||
|
||||
- name: Detach a volume
|
||||
community.general.profitbricks_volume_attachments:
|
||||
datacenter: Tardis One
|
||||
server: node002
|
||||
volume: vol01
|
||||
wait_timeout: 500
|
||||
state: absent
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
HAS_PB_SDK = True
|
||||
try:
|
||||
from profitbricks.client import ProfitBricksService
|
||||
except ImportError:
|
||||
HAS_PB_SDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
uuid_match = re.compile(
|
||||
r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
|
||||
|
||||
|
||||
def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
|
||||
if not promise:
|
||||
return
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait_timeout > time.time():
|
||||
time.sleep(5)
|
||||
operation_result = profitbricks.get_request(
|
||||
request_id=promise['requestId'],
|
||||
status=True)
|
||||
|
||||
if operation_result['metadata']['status'] == "DONE":
|
||||
return
|
||||
elif operation_result['metadata']['status'] == "FAILED":
|
||||
raise Exception(
|
||||
'Request failed to complete ' + msg + ' "' + str(
|
||||
promise['requestId']) + '" to complete.')
|
||||
|
||||
raise Exception(
|
||||
'Timed out waiting for async operation ' + msg + ' "' + str(
|
||||
promise['requestId']
|
||||
) + '" to complete.')
|
||||
|
||||
|
||||
def attach_volume(module, profitbricks):
|
||||
"""
|
||||
Attaches a volume.
|
||||
|
||||
This will attach a volume to the server.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the volume was attached, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
server = module.params.get('server')
|
||||
volume = module.params.get('volume')
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Server
|
||||
if not (uuid_match.match(server)):
|
||||
server_list = profitbricks.list_servers(datacenter)
|
||||
for s in server_list['items']:
|
||||
if server == s['properties']['name']:
|
||||
server = s['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Volume
|
||||
if not (uuid_match.match(volume)):
|
||||
volume_list = profitbricks.list_volumes(datacenter)
|
||||
for v in volume_list['items']:
|
||||
if volume == v['properties']['name']:
|
||||
volume = v['id']
|
||||
break
|
||||
|
||||
return profitbricks.attach_volume(datacenter, server, volume)
|
||||
|
||||
|
||||
def detach_volume(module, profitbricks):
|
||||
"""
|
||||
Detaches a volume.
|
||||
|
||||
This will remove a volume from the server.
|
||||
|
||||
module : AnsibleModule object
|
||||
profitbricks: authenticated profitbricks object.
|
||||
|
||||
Returns:
|
||||
True if the volume was detached, false otherwise
|
||||
"""
|
||||
datacenter = module.params.get('datacenter')
|
||||
server = module.params.get('server')
|
||||
volume = module.params.get('volume')
|
||||
|
||||
# Locate UUID for Datacenter
|
||||
if not (uuid_match.match(datacenter)):
|
||||
datacenter_list = profitbricks.list_datacenters()
|
||||
for d in datacenter_list['items']:
|
||||
dc = profitbricks.get_datacenter(d['id'])
|
||||
if datacenter == dc['properties']['name']:
|
||||
datacenter = d['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Server
|
||||
if not (uuid_match.match(server)):
|
||||
server_list = profitbricks.list_servers(datacenter)
|
||||
for s in server_list['items']:
|
||||
if server == s['properties']['name']:
|
||||
server = s['id']
|
||||
break
|
||||
|
||||
# Locate UUID for Volume
|
||||
if not (uuid_match.match(volume)):
|
||||
volume_list = profitbricks.list_volumes(datacenter)
|
||||
for v in volume_list['items']:
|
||||
if volume == v['properties']['name']:
|
||||
volume = v['id']
|
||||
break
|
||||
|
||||
return profitbricks.detach_volume(datacenter, server, volume)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
datacenter=dict(),
|
||||
server=dict(),
|
||||
volume=dict(),
|
||||
subscription_user=dict(),
|
||||
subscription_password=dict(no_log=True),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(type='int', default=600),
|
||||
state=dict(default='present'),
|
||||
)
|
||||
)
|
||||
|
||||
if not HAS_PB_SDK:
|
||||
module.fail_json(msg='profitbricks required for this module')
|
||||
|
||||
if not module.params.get('subscription_user'):
|
||||
module.fail_json(msg='subscription_user parameter is required')
|
||||
if not module.params.get('subscription_password'):
|
||||
module.fail_json(msg='subscription_password parameter is required')
|
||||
if not module.params.get('datacenter'):
|
||||
module.fail_json(msg='datacenter parameter is required')
|
||||
if not module.params.get('server'):
|
||||
module.fail_json(msg='server parameter is required')
|
||||
if not module.params.get('volume'):
|
||||
module.fail_json(msg='volume parameter is required')
|
||||
|
||||
subscription_user = module.params.get('subscription_user')
|
||||
subscription_password = module.params.get('subscription_password')
|
||||
|
||||
profitbricks = ProfitBricksService(
|
||||
username=subscription_user,
|
||||
password=subscription_password)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'absent':
|
||||
try:
|
||||
(changed) = detach_volume(module, profitbricks)
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set volume_attach state: %s' % str(e))
|
||||
elif state == 'present':
|
||||
try:
|
||||
attach_volume(module, profitbricks)
|
||||
module.exit_json()
|
||||
except Exception as e:
|
||||
module.fail_json(msg='failed to set volume_attach state: %s' % str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -228,10 +228,11 @@ options:
|
|||
update:
|
||||
description:
|
||||
- If V(true), the container will be updated with new values.
|
||||
- The current default value of V(false) is deprecated and should will change to V(true) in community.general 11.0.0.
|
||||
Please set O(update) explicitly to V(false) or V(true) to avoid surprises and get rid of the deprecation warning.
|
||||
- If V(false), it will not be updated.
|
||||
- The default changed from V(false) to V(true) in community.general 11.0.0.
|
||||
type: bool
|
||||
version_added: 8.1.0
|
||||
default: true
|
||||
force:
|
||||
description:
|
||||
- Forcing operations.
|
||||
|
@ -700,7 +701,7 @@ def get_proxmox_args():
|
|||
nameserver=dict(),
|
||||
searchdomain=dict(),
|
||||
timeout=dict(type="int", default=30),
|
||||
update=dict(type="bool"),
|
||||
update=dict(type="bool", default=True),
|
||||
force=dict(type="bool", default=False),
|
||||
purge=dict(type="bool", default=False),
|
||||
state=dict(
|
||||
|
@ -844,15 +845,6 @@ class ProxmoxLxcAnsible(ProxmoxAnsible):
|
|||
|
||||
# check if the container exists already
|
||||
if lxc is not None:
|
||||
if update is None:
|
||||
# TODO: Remove deprecation warning in version 11.0.0
|
||||
self.module.deprecate(
|
||||
msg="The default value of false for 'update' has been deprecated and will be changed to true in version 11.0.0.",
|
||||
version="11.0.0",
|
||||
collection_name="community.general",
|
||||
)
|
||||
update = False
|
||||
|
||||
if update:
|
||||
# Update it if we should
|
||||
identifier = self.format_vm_identifier(vmid, hostname)
|
||||
|
@ -890,6 +882,7 @@ class ProxmoxLxcAnsible(ProxmoxAnsible):
|
|||
self.module.exit_json(
|
||||
changed=False, vmid=vmid, msg="VM %s already exists." % identifier
|
||||
)
|
||||
identifier = self.format_vm_identifier(vmid, lxc["name"])
|
||||
self.module.debug(
|
||||
"VM %s already exists, but we don't update and instead forcefully recreate it."
|
||||
% identifier
|
||||
|
|
|
@ -129,11 +129,18 @@ from ansible_collections.community.general.plugins.module_utils.rundeck import (
|
|||
class RundeckACLManager:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
if module.params.get("project"):
|
||||
self.endpoint = "project/%s/acl/%s.aclpolicy" % (
|
||||
self.module.params["project"],
|
||||
self.module.params["name"],
|
||||
)
|
||||
else:
|
||||
self.endpoint = "system/acl/%s.aclpolicy" % self.module.params["name"]
|
||||
|
||||
def get_acl(self):
|
||||
resp, info = api_request(
|
||||
module=self.module,
|
||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||
endpoint=self.endpoint,
|
||||
)
|
||||
|
||||
return resp
|
||||
|
@ -147,7 +154,7 @@ class RundeckACLManager:
|
|||
|
||||
resp, info = api_request(
|
||||
module=self.module,
|
||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||
endpoint=self.endpoint,
|
||||
method="POST",
|
||||
data={"contents": self.module.params["policy"]},
|
||||
)
|
||||
|
@ -171,7 +178,7 @@ class RundeckACLManager:
|
|||
|
||||
resp, info = api_request(
|
||||
module=self.module,
|
||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||
endpoint=self.endpoint,
|
||||
method="PUT",
|
||||
data={"contents": self.module.params["policy"]},
|
||||
)
|
||||
|
@ -194,7 +201,7 @@ class RundeckACLManager:
|
|||
if not self.module.check_mode:
|
||||
api_request(
|
||||
module=self.module,
|
||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||
endpoint=self.endpoint,
|
||||
method="DELETE",
|
||||
)
|
||||
|
||||
|
|
|
@ -203,7 +203,6 @@ class Snap(StateModuleHelper):
|
|||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
@staticmethod
|
||||
def _first_non_zero(a):
|
||||
|
|
|
@ -109,7 +109,6 @@ class SnapAlias(StateModuleHelper):
|
|||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def _aliases(self):
|
||||
n = self.vars.name
|
||||
|
|
|
@ -165,6 +165,13 @@ options:
|
|||
- Restrict concurrent operations when Terraform applies the plan.
|
||||
type: int
|
||||
version_added: '3.8.0'
|
||||
no_color:
|
||||
description:
|
||||
- If V(true), suppress color codes in output from Terraform commands.
|
||||
- If V(false), allows Terraform to use color codes in its output.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 11.0.0
|
||||
notes:
|
||||
- To just run a C(terraform plan), use check mode.
|
||||
requirements: ["terraform"]
|
||||
|
@ -177,6 +184,12 @@ EXAMPLES = r"""
|
|||
project_path: '{{ project_dir }}'
|
||||
state: present
|
||||
|
||||
- name: Deploy with color output enabled
|
||||
community.general.terraform:
|
||||
project_path: '{{ project_dir }}'
|
||||
state: present
|
||||
no_color: false
|
||||
|
||||
- name: Define the backend configuration at init
|
||||
community.general.terraform:
|
||||
project_path: 'project/'
|
||||
|
@ -291,17 +304,20 @@ def get_version(bin_path):
|
|||
return terraform_version
|
||||
|
||||
|
||||
def preflight_validation(bin_path, project_path, version, variables_args=None, plan_file=None):
|
||||
def preflight_validation(bin_path, project_path, version, variables_args=None, plan_file=None, no_color=True):
|
||||
if project_path is None or '/' not in project_path:
|
||||
module.fail_json(msg="Path for Terraform project can not be None or ''.")
|
||||
if not os.path.exists(bin_path):
|
||||
module.fail_json(msg="Path for Terraform binary '{0}' doesn't exist on this host - check the path and try again please.".format(bin_path))
|
||||
if not os.path.isdir(project_path):
|
||||
module.fail_json(msg="Path for Terraform project '{0}' doesn't exist on this host - check the path and try again please.".format(project_path))
|
||||
cmd = [bin_path, 'validate']
|
||||
if no_color:
|
||||
cmd.append('-no-color')
|
||||
if LooseVersion(version) < LooseVersion('0.15.0'):
|
||||
module.run_command([bin_path, 'validate', '-no-color'] + variables_args, check_rc=True, cwd=project_path)
|
||||
module.run_command(cmd + variables_args, check_rc=True, cwd=project_path)
|
||||
else:
|
||||
module.run_command([bin_path, 'validate', '-no-color'], check_rc=True, cwd=project_path)
|
||||
module.run_command(cmd, check_rc=True, cwd=project_path)
|
||||
|
||||
|
||||
def _state_args(state_file):
|
||||
|
@ -312,8 +328,10 @@ def _state_args(state_file):
|
|||
return ['-state', state_file]
|
||||
|
||||
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths, workspace):
|
||||
command = [bin_path, 'init', '-input=false', '-no-color']
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths, workspace, no_color=True):
|
||||
command = [bin_path, 'init', '-input=false']
|
||||
if no_color:
|
||||
command.append('-no-color')
|
||||
if backend_config:
|
||||
for key, val in backend_config.items():
|
||||
command.extend([
|
||||
|
@ -333,9 +351,12 @@ def init_plugins(bin_path, project_path, backend_config, backend_config_files, i
|
|||
rc, out, err = module.run_command(command, check_rc=True, cwd=project_path, environ_update={"TF_WORKSPACE": workspace})
|
||||
|
||||
|
||||
def get_workspace_context(bin_path, project_path):
|
||||
def get_workspace_context(bin_path, project_path, no_color=True):
|
||||
workspace_ctx = {"current": "default", "all": []}
|
||||
command = [bin_path, 'workspace', 'list', '-no-color']
|
||||
command = [bin_path, 'workspace', 'list']
|
||||
if no_color:
|
||||
command.append('-no-color')
|
||||
|
||||
rc, out, err = module.run_command(command, cwd=project_path)
|
||||
if rc != 0:
|
||||
module.warn("Failed to list Terraform workspaces:\n{0}".format(err))
|
||||
|
@ -351,25 +372,27 @@ def get_workspace_context(bin_path, project_path):
|
|||
return workspace_ctx
|
||||
|
||||
|
||||
def _workspace_cmd(bin_path, project_path, action, workspace):
|
||||
command = [bin_path, 'workspace', action, workspace, '-no-color']
|
||||
def _workspace_cmd(bin_path, project_path, action, workspace, no_color=True):
|
||||
command = [bin_path, 'workspace', action, workspace]
|
||||
if no_color:
|
||||
command.append('-no-color')
|
||||
rc, out, err = module.run_command(command, check_rc=True, cwd=project_path)
|
||||
return rc, out, err
|
||||
|
||||
|
||||
def create_workspace(bin_path, project_path, workspace):
|
||||
_workspace_cmd(bin_path, project_path, 'new', workspace)
|
||||
def create_workspace(bin_path, project_path, workspace, no_color=True):
|
||||
_workspace_cmd(bin_path, project_path, 'new', workspace, no_color)
|
||||
|
||||
|
||||
def select_workspace(bin_path, project_path, workspace):
|
||||
_workspace_cmd(bin_path, project_path, 'select', workspace)
|
||||
def select_workspace(bin_path, project_path, workspace, no_color=True):
|
||||
_workspace_cmd(bin_path, project_path, 'select', workspace, no_color)
|
||||
|
||||
|
||||
def remove_workspace(bin_path, project_path, workspace):
|
||||
_workspace_cmd(bin_path, project_path, 'delete', workspace)
|
||||
def remove_workspace(bin_path, project_path, workspace, no_color=True):
|
||||
_workspace_cmd(bin_path, project_path, 'delete', workspace, no_color)
|
||||
|
||||
|
||||
def build_plan(command, project_path, variables_args, state_file, targets, state, args, plan_path=None):
|
||||
def build_plan(command, project_path, variables_args, state_file, targets, state, args, plan_path=None, no_color=True):
|
||||
if plan_path is None:
|
||||
f, plan_path = tempfile.mkstemp(suffix='.tfplan')
|
||||
|
||||
|
@ -391,7 +414,10 @@ def build_plan(command, project_path, variables_args, state_file, targets, state
|
|||
for a in args:
|
||||
plan_command.append(a)
|
||||
|
||||
plan_command.extend(['-input=false', '-no-color', '-detailed-exitcode', '-out', plan_path])
|
||||
plan_options = ['-input=false', '-detailed-exitcode', '-out', plan_path]
|
||||
if no_color:
|
||||
plan_options.insert(0, '-no-color')
|
||||
plan_command.extend(plan_options)
|
||||
|
||||
for t in targets:
|
||||
plan_command.extend(['-target', t])
|
||||
|
@ -495,6 +521,7 @@ def main():
|
|||
check_destroy=dict(type='bool', default=False),
|
||||
parallelism=dict(type='int'),
|
||||
provider_upgrade=dict(type='bool', default=False),
|
||||
no_color=dict(type='bool', default=True),
|
||||
),
|
||||
required_if=[('state', 'planned', ['plan_file'])],
|
||||
supports_check_mode=True,
|
||||
|
@ -518,6 +545,7 @@ def main():
|
|||
overwrite_init = module.params.get('overwrite_init')
|
||||
check_destroy = module.params.get('check_destroy')
|
||||
provider_upgrade = module.params.get('provider_upgrade')
|
||||
no_color = module.params.get('no_color')
|
||||
|
||||
if bin_path is not None:
|
||||
command = [bin_path]
|
||||
|
@ -527,22 +555,30 @@ def main():
|
|||
checked_version = get_version(command[0])
|
||||
|
||||
if LooseVersion(checked_version) < LooseVersion('0.15.0'):
|
||||
DESTROY_ARGS = ('destroy', '-no-color', '-force')
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve=true')
|
||||
if no_color:
|
||||
DESTROY_ARGS = ('destroy', '-no-color', '-force')
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve=true')
|
||||
else:
|
||||
DESTROY_ARGS = ('destroy', '-force')
|
||||
APPLY_ARGS = ('apply', '-input=false', '-auto-approve=true')
|
||||
else:
|
||||
DESTROY_ARGS = ('destroy', '-no-color', '-auto-approve')
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve')
|
||||
if no_color:
|
||||
DESTROY_ARGS = ('destroy', '-no-color', '-auto-approve')
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve')
|
||||
else:
|
||||
DESTROY_ARGS = ('destroy', '-auto-approve')
|
||||
APPLY_ARGS = ('apply', '-input=false', '-auto-approve')
|
||||
|
||||
if force_init:
|
||||
if overwrite_init or not os.path.isfile(os.path.join(project_path, ".terraform", "terraform.tfstate")):
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths, workspace)
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths, workspace, no_color)
|
||||
|
||||
workspace_ctx = get_workspace_context(command[0], project_path)
|
||||
workspace_ctx = get_workspace_context(command[0], project_path, no_color)
|
||||
if workspace_ctx["current"] != workspace:
|
||||
if workspace not in workspace_ctx["all"]:
|
||||
create_workspace(command[0], project_path, workspace)
|
||||
create_workspace(command[0], project_path, workspace, no_color)
|
||||
else:
|
||||
select_workspace(command[0], project_path, workspace)
|
||||
select_workspace(command[0], project_path, workspace, no_color)
|
||||
|
||||
if state == 'present':
|
||||
command.extend(APPLY_ARGS)
|
||||
|
@ -627,7 +663,7 @@ def main():
|
|||
for f in variables_files:
|
||||
variables_args.extend(['-var-file', f])
|
||||
|
||||
preflight_validation(command[0], project_path, checked_version, variables_args)
|
||||
preflight_validation(command[0], project_path, checked_version, variables_args, plan_file, no_color)
|
||||
|
||||
if module.params.get('lock') is not None:
|
||||
if module.params.get('lock'):
|
||||
|
@ -654,7 +690,7 @@ def main():
|
|||
module.fail_json(msg='Could not find plan_file "{0}", check the path and try again.'.format(plan_file))
|
||||
else:
|
||||
plan_file, needs_application, out, err, command = build_plan(command, project_path, variables_args, state_file,
|
||||
module.params.get('targets'), state, APPLY_ARGS, plan_file)
|
||||
module.params.get('targets'), state, APPLY_ARGS, plan_file, no_color)
|
||||
if state == 'present' and check_destroy and '- destroy' in out:
|
||||
module.fail_json(msg="Aborting command because it would destroy some resources. "
|
||||
"Consider switching the 'check_destroy' to false to suppress this error")
|
||||
|
@ -665,13 +701,13 @@ def main():
|
|||
if state == 'absent':
|
||||
plan_absent_args = ['-destroy']
|
||||
plan_file, needs_application, out, err, command = build_plan(command, project_path, variables_args, state_file,
|
||||
module.params.get('targets'), state, plan_absent_args, plan_file)
|
||||
module.params.get('targets'), state, plan_absent_args, plan_file, no_color)
|
||||
diff_command = [command[0], 'show', '-json', plan_file]
|
||||
rc, diff_output, err = module.run_command(diff_command, check_rc=False, cwd=project_path)
|
||||
changed, result_diff = get_diff(diff_output)
|
||||
if rc != 0:
|
||||
if workspace_ctx["current"] != workspace:
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"])
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"], no_color)
|
||||
module.fail_json(msg=err.rstrip(), rc=rc, stdout=out,
|
||||
stdout_lines=out.splitlines(), stderr=err,
|
||||
stderr_lines=err.splitlines(),
|
||||
|
@ -681,7 +717,7 @@ def main():
|
|||
rc, out, err = module.run_command(command, check_rc=False, cwd=project_path)
|
||||
if rc != 0:
|
||||
if workspace_ctx["current"] != workspace:
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"])
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"], no_color)
|
||||
module.fail_json(msg=err.rstrip(), rc=rc, stdout=out,
|
||||
stdout_lines=out.splitlines(), stderr=err,
|
||||
stderr_lines=err.splitlines(),
|
||||
|
@ -690,7 +726,11 @@ def main():
|
|||
if ' 0 added, 0 changed' not in out and not state == "absent" or ' 0 destroyed' not in out:
|
||||
changed = True
|
||||
|
||||
outputs_command = [command[0], 'output', '-no-color', '-json'] + _state_args(state_file)
|
||||
if no_color:
|
||||
outputs_command = [command[0], 'output', '-no-color', '-json'] + _state_args(state_file)
|
||||
else:
|
||||
outputs_command = [command[0], 'output', '-json'] + _state_args(state_file)
|
||||
|
||||
rc, outputs_text, outputs_err = module.run_command(outputs_command, cwd=project_path)
|
||||
outputs = {}
|
||||
if rc == 1:
|
||||
|
@ -705,9 +745,9 @@ def main():
|
|||
|
||||
# Restore the Terraform workspace found when running the module
|
||||
if workspace_ctx["current"] != workspace:
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"])
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"], no_color)
|
||||
if state == 'absent' and workspace != 'default' and purge_workspace is True:
|
||||
remove_workspace(command[0], project_path, workspace)
|
||||
remove_workspace(command[0], project_path, workspace, no_color)
|
||||
|
||||
result = {
|
||||
'state': state,
|
||||
|
|
|
@ -103,7 +103,6 @@ class XdgMime(ModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = xdg_mime_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -190,7 +190,6 @@ class XFConfProperty(StateModuleHelper):
|
|||
required_together=[('value', 'value_type')],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
default_state = 'present'
|
||||
|
||||
|
|
|
@ -142,7 +142,6 @@ class XFConfInfo(ModuleHelper):
|
|||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = xfconf_runner(self.module, check_rc=True)
|
||||
|
|
|
@ -9,7 +9,15 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Include OS-specific variables
|
||||
include_vars: '{{ ansible_os_family }}.yml'
|
||||
include_vars: '{{ lookup("first_found", params) }}'
|
||||
vars:
|
||||
params:
|
||||
files:
|
||||
- '{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml'
|
||||
- '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml'
|
||||
- '{{ ansible_os_family }}.yml'
|
||||
paths:
|
||||
- '{{ role_path }}/vars'
|
||||
|
||||
- name: Install dependencies
|
||||
become: true
|
||||
|
|
6
tests/integration/targets/android_sdk/vars/RedHat-10.yml
Normal file
6
tests/integration/targets/android_sdk/vars/RedHat-10.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
openjdk_pkg: java-21-openjdk-headless
|
6
tests/integration/targets/callback_print_task/aliases
Normal file
6
tests/integration/targets/callback_print_task/aliases
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/posix/3
|
||||
needs/target/callback
|
132
tests/integration/targets/callback_print_task/tasks/main.yml
Normal file
132
tests/integration/targets/callback_print_task/tasks/main.yml
Normal file
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Run tests
|
||||
include_role:
|
||||
name: callback
|
||||
vars:
|
||||
tests:
|
||||
- name: community.general.print_task is not enabled
|
||||
environment:
|
||||
ANSIBLE_NOCOLOR: 'true'
|
||||
ANSIBLE_FORCE_COLOR: 'false'
|
||||
playbook: |
|
||||
- hosts: testhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Sample task
|
||||
debug:
|
||||
msg: This is a test
|
||||
expected_output: [
|
||||
"",
|
||||
"PLAY [testhost] ****************************************************************",
|
||||
"",
|
||||
"TASK [Sample task] *************************************************************",
|
||||
"ok: [testhost] => {",
|
||||
" \"msg\": \"This is a test\"",
|
||||
"}",
|
||||
"",
|
||||
"PLAY RECAP *********************************************************************",
|
||||
"testhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 "
|
||||
]
|
||||
|
||||
- name: community.general.print_task is enabled
|
||||
environment:
|
||||
ANSIBLE_NOCOLOR: 'true'
|
||||
ANSIBLE_FORCE_COLOR: 'false'
|
||||
ANSIBLE_CALLBACKS_ENABLED: 'community.general.print_task'
|
||||
playbook: |
|
||||
- hosts: testhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Sample task
|
||||
debug:
|
||||
msg: This is a test
|
||||
expected_output: [
|
||||
"",
|
||||
"PLAY [testhost] ****************************************************************",
|
||||
"",
|
||||
"TASK [Sample task] *************************************************************",
|
||||
"",
|
||||
"- name: Sample task",
|
||||
" debug:",
|
||||
" msg: This is a test",
|
||||
"",
|
||||
"ok: [testhost] => {",
|
||||
" \"msg\": \"This is a test\"",
|
||||
"}",
|
||||
"",
|
||||
"PLAY RECAP *********************************************************************",
|
||||
"testhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 "
|
||||
]
|
||||
|
||||
- name: Print with msg parameter on the same line
|
||||
environment:
|
||||
ANSIBLE_NOCOLOR: 'true'
|
||||
ANSIBLE_FORCE_COLOR: 'false'
|
||||
ANSIBLE_CALLBACKS_ENABLED: 'community.general.print_task'
|
||||
playbook: |
|
||||
- hosts: testhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Sample task
|
||||
debug: msg="This is a test"
|
||||
expected_output: [
|
||||
"",
|
||||
"PLAY [testhost] ****************************************************************",
|
||||
"",
|
||||
"TASK [Sample task] *************************************************************",
|
||||
"",
|
||||
"- name: Sample task",
|
||||
" debug: msg=\"This is a test\"",
|
||||
"",
|
||||
"ok: [testhost] => {",
|
||||
" \"msg\": \"This is a test\"",
|
||||
"}",
|
||||
"",
|
||||
"PLAY RECAP *********************************************************************",
|
||||
"testhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 "
|
||||
]
|
||||
|
||||
- name: Task with additional parameters
|
||||
environment:
|
||||
ANSIBLE_NOCOLOR: 'true'
|
||||
ANSIBLE_FORCE_COLOR: 'false'
|
||||
ANSIBLE_CALLBACKS_ENABLED: 'community.general.print_task'
|
||||
playbook: |
|
||||
- hosts: testhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Sample task
|
||||
when: True
|
||||
vars:
|
||||
test_var: "Hello World"
|
||||
debug:
|
||||
var: test_var
|
||||
expected_output: [
|
||||
"",
|
||||
"PLAY [testhost] ****************************************************************",
|
||||
"",
|
||||
"TASK [Sample task] *************************************************************",
|
||||
"",
|
||||
"- name: Sample task",
|
||||
" when: true",
|
||||
" vars:",
|
||||
" test_var: Hello World",
|
||||
" debug:",
|
||||
" var: test_var",
|
||||
"",
|
||||
"ok: [testhost] => {",
|
||||
" \"test_var\": \"Hello World\"",
|
||||
"}",
|
||||
"",
|
||||
"PLAY RECAP *********************************************************************",
|
||||
"testhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 "
|
||||
]
|
|
@ -7,3 +7,4 @@ needs/root
|
|||
skip/macos
|
||||
skip/osx
|
||||
skip/freebsd
|
||||
skip/rhel10.0 # FIXME
|
||||
|
|
|
@ -20,3 +20,4 @@ skip/rhel9.2
|
|||
skip/rhel9.3
|
||||
skip/rhel9.4
|
||||
skip/rhel9.5
|
||||
skip/rhel10.0
|
||||
|
|
|
@ -20,3 +20,4 @@ skip/rhel9.2
|
|||
skip/rhel9.3
|
||||
skip/rhel9.4
|
||||
skip/rhel9.5
|
||||
skip/rhel10.0
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- import_tasks: setup_no_value.yml
|
||||
|
||||
- name: testing exclusion between state and list_all parameters
|
||||
git_config:
|
||||
list_all: true
|
||||
state: absent
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert git_config failed
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
- "result.msg == 'parameters are mutually exclusive: list_all|state'"
|
||||
...
|
|
@ -13,7 +13,7 @@
|
|||
register: set_result
|
||||
|
||||
- name: getting value without state
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
@ -24,6 +24,5 @@
|
|||
- 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
|
||||
...
|
||||
|
|
|
@ -14,10 +14,9 @@
|
|||
register: result
|
||||
|
||||
- name: getting value with state=present
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
state: present
|
||||
register: get_result
|
||||
|
||||
- name: assert set changed and value is correct with state=present
|
||||
|
@ -26,6 +25,5 @@
|
|||
- 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
|
||||
...
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
register: result
|
||||
|
||||
- name: getting value with state=present
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "file"
|
||||
file: "{{ remote_tmp_dir }}/gitconfig_file"
|
||||
state: present
|
||||
path: "{{ remote_tmp_dir }}/gitconfig_file"
|
||||
register: get_result
|
||||
|
||||
- name: assert set changed and value is correct with state=present
|
||||
|
@ -28,6 +27,5 @@
|
|||
- 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
|
||||
...
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
- block:
|
||||
- import_tasks: set_value.yml
|
||||
# testing parameters exclusion: state and list_all
|
||||
- import_tasks: exclusion_state_list-all.yml
|
||||
# testing get/set option without state
|
||||
- import_tasks: get_set_no_state.yml
|
||||
# testing get/set option with state=present
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
register: unset_result
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
|
|
@ -31,17 +31,11 @@
|
|||
- 'merge_request.target=foobar'
|
||||
register: set_result2
|
||||
|
||||
- name: getting the multi-value
|
||||
git_config:
|
||||
name: push.pushoption
|
||||
scope: global
|
||||
register: get_single_result
|
||||
|
||||
- name: getting all values for the single option
|
||||
git_config_info:
|
||||
name: push.pushoption
|
||||
scope: global
|
||||
register: get_all_result
|
||||
register: get_result
|
||||
|
||||
- name: replace-all values
|
||||
git_config:
|
||||
|
@ -62,8 +56,8 @@
|
|||
- set_result2.results[1] is not changed
|
||||
- set_result2.results[2] is not changed
|
||||
- set_result3 is changed
|
||||
- get_single_result.config_value == 'merge_request.create'
|
||||
- 'get_all_result.config_values == {"push.pushoption": ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"]}'
|
||||
- get_result.config_value == 'merge_request.create'
|
||||
- 'get_result.config_values == {"push.pushoption": ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"]}'
|
||||
|
||||
- name: assert the diffs are also right
|
||||
assert:
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
register: set_result2
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: core.name
|
||||
scope: global
|
||||
register: get_result
|
||||
|
@ -30,7 +30,6 @@
|
|||
that:
|
||||
- set_result1 is changed
|
||||
- set_result2 is changed
|
||||
- get_result is not changed
|
||||
- get_result.config_value == 'bar'
|
||||
- set_result1.diff.before == "\n"
|
||||
- set_result1.diff.after == "foo\n"
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
register: set_result2
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: core.hooksPath
|
||||
scope: global
|
||||
register: get_result
|
||||
|
@ -32,6 +32,5 @@
|
|||
that:
|
||||
- set_result1 is changed
|
||||
- set_result2 is not changed
|
||||
- get_result is not changed
|
||||
- get_result.config_value == '~/foo/bar'
|
||||
...
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
register: unset_result
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
register: unset_result
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
register: unset_result
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
@ -37,7 +37,7 @@
|
|||
register: unset_result
|
||||
|
||||
- name: getting value
|
||||
git_config:
|
||||
git_config_info:
|
||||
name: "{{ option_name }}"
|
||||
scope: "{{ option_scope }}"
|
||||
register: get_result
|
||||
|
|
|
@ -13,3 +13,4 @@ skip/rhel9.2 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/
|
|||
skip/rhel9.3 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/
|
||||
skip/rhel9.4 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/
|
||||
skip/rhel9.5 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/
|
||||
skip/rhel10.0 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/
|
||||
|
|
|
@ -12,3 +12,4 @@ skip/macos # no iptables/netfilter (Linux specific)
|
|||
skip/aix # no iptables/netfilter (Linux specific)
|
||||
|
||||
skip/ubuntu22.04 # TODO there's a problem here!
|
||||
skip/rhel10.0 # TODO there's a problem here!
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue