mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 10:26:52 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
983b4d70e0 | ||
|
|
821ae9bc41 | ||
|
|
d5efd56dae | ||
|
|
88ef840750 | ||
|
|
08d04a7923 | ||
|
|
9d0b14b239 | ||
|
|
9e83a4cb34 | ||
|
|
dc5f012e52 | ||
|
|
210adc196e | ||
|
|
f30fcec398 | ||
|
|
0a904d60cd | ||
|
|
1ee2bba140 | ||
|
|
0aa9cd0e30 | ||
|
|
7009a768a4 | ||
|
|
2f5552da04 | ||
|
|
145686cfe0 | ||
|
|
08239919de | ||
|
|
1ab1f8f62b | ||
|
|
13c25154b5 | ||
|
|
d22199f82e | ||
|
|
82a07de870 | ||
|
|
895c1fe2a0 | ||
|
|
3e972990cb | ||
|
|
0ac6e44566 | ||
|
|
1308198eae | ||
|
|
d887985b46 | ||
|
|
7d6e7fa5fa | ||
|
|
b61b988b2e | ||
|
|
549c9e69ae | ||
|
|
fe35cff7a4 | ||
|
|
cef57b044b |
6
.github/BOTMETA.yml
vendored
6
.github/BOTMETA.yml
vendored
@@ -562,8 +562,12 @@ files:
|
|||||||
maintainers: paytroff
|
maintainers: paytroff
|
||||||
$modules/gitlab_issue.py:
|
$modules/gitlab_issue.py:
|
||||||
maintainers: zvaraondrej
|
maintainers: zvaraondrej
|
||||||
|
$modules/gitlab_label.py:
|
||||||
|
maintainers: gpongelli
|
||||||
$modules/gitlab_merge_request.py:
|
$modules/gitlab_merge_request.py:
|
||||||
maintainers: zvaraondrej
|
maintainers: zvaraondrej
|
||||||
|
$modules/gitlab_milestone.py:
|
||||||
|
maintainers: gpongelli
|
||||||
$modules/gitlab_project_variable.py:
|
$modules/gitlab_project_variable.py:
|
||||||
maintainers: markuman
|
maintainers: markuman
|
||||||
$modules/gitlab_instance_variable.py:
|
$modules/gitlab_instance_variable.py:
|
||||||
@@ -1490,7 +1494,7 @@ macros:
|
|||||||
team_ansible_core:
|
team_ansible_core:
|
||||||
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
||||||
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
||||||
team_consul: sgargan
|
team_consul: sgargan apollo13
|
||||||
team_cyberark_conjur: jvanderhoof ryanprior
|
team_cyberark_conjur: jvanderhoof ryanprior
|
||||||
team_e_spirit: MatrixCrawler getjack
|
team_e_spirit: MatrixCrawler getjack
|
||||||
team_flatpak: JayKayy oolongbrothers
|
team_flatpak: JayKayy oolongbrothers
|
||||||
|
|||||||
20
.github/workflows/import-galaxy.yml
vendored
Normal file
20
.github/workflows/import-galaxy.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# 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: import-galaxy
|
||||||
|
'on':
|
||||||
|
# Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- stable-*
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
import-galaxy:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
name: Test to import built collection artifact with Galaxy importer
|
||||||
|
uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main
|
||||||
9
.github/workflows/reuse.yml
vendored
9
.github/workflows/reuse.yml
vendored
@@ -26,10 +26,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: REUSE Compliance Check
|
||||||
run: |
|
uses: fsfe/reuse-action@v2
|
||||||
pip install reuse
|
|
||||||
|
|
||||||
- name: Check REUSE compliance
|
|
||||||
run: |
|
|
||||||
reuse lint
|
|
||||||
|
|||||||
@@ -6,6 +6,53 @@ Community General Release Notes
|
|||||||
|
|
||||||
This changelog describes changes after version 7.0.0.
|
This changelog describes changes after version 7.0.0.
|
||||||
|
|
||||||
|
v8.3.0
|
||||||
|
======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Regular bugfix and feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- consul_auth_method, consul_binding_rule, consul_policy, consul_role, consul_session, consul_token - added action group ``community.general.consul`` (https://github.com/ansible-collections/community.general/pull/7897).
|
||||||
|
- consul_policy - added support for diff and check mode (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_policy, consul_role, consul_session - removed dependency on ``requests`` and factored out common parts (https://github.com/ansible-collections/community.general/pull/7826, https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - ``node_identities`` now expects a ``node_name`` option to match the Consul API, the old ``name`` is still supported as alias (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - ``service_identities`` now expects a ``service_name`` option to match the Consul API, the old ``name`` is still supported as alias (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - added support for diff mode (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - added support for templated policies (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- redfish_info - add command ``GetServiceIdentification`` to get service identification (https://github.com/ansible-collections/community.general/issues/7882).
|
||||||
|
- terraform - add support for ``diff_mode`` for terraform resource_changes (https://github.com/ansible-collections/community.general/pull/7896).
|
||||||
|
|
||||||
|
Deprecated Features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- consul_acl - the module has been deprecated and will be removed in community.general 10.0.0. ``consul_token`` and ``consul_policy`` can be used instead (https://github.com/ansible-collections/community.general/pull/7901).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- homebrew - detect already installed formulae and casks using JSON output from ``brew info`` (https://github.com/ansible-collections/community.general/issues/864).
|
||||||
|
- incus connection plugin - treats ``inventory_hostname`` as a variable instead of a literal in remote connections (https://github.com/ansible-collections/community.general/issues/7874).
|
||||||
|
- ipa_otptoken - the module expect ``ipatokendisabled`` as string but the ``ipatokendisabled`` value is returned as a boolean (https://github.com/ansible-collections/community.general/pull/7795).
|
||||||
|
- ldap - previously the order number (if present) was expected to follow an equals sign in the DN. This makes it so the order number string is identified correctly anywhere within the DN (https://github.com/ansible-collections/community.general/issues/7646).
|
||||||
|
- mssql_script - make the module work with Python 2 (https://github.com/ansible-collections/community.general/issues/7818, https://github.com/ansible-collections/community.general/pull/7821).
|
||||||
|
- nmcli - fix ``connection.slave-type`` wired to ``bond`` and not with parameter ``slave_type`` in case of connection type ``wifi`` (https://github.com/ansible-collections/community.general/issues/7389).
|
||||||
|
- proxmox - fix updating a container config if the setting does not already exist (https://github.com/ansible-collections/community.general/pull/7872).
|
||||||
|
|
||||||
|
New Modules
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- consul_acl_bootstrap - Bootstrap ACLs in Consul
|
||||||
|
- consul_auth_method - Manipulate Consul auth methods
|
||||||
|
- consul_binding_rule - Manipulate Consul binding rules
|
||||||
|
- consul_token - Manipulate Consul tokens
|
||||||
|
- gitlab_label - Creates/updates/deletes GitLab Labels belonging to project or group.
|
||||||
|
- gitlab_milestone - Creates/updates/deletes GitLab Milestones belonging to project or group
|
||||||
|
|
||||||
v8.2.0
|
v8.2.0
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|||||||
@@ -1072,3 +1072,76 @@ releases:
|
|||||||
name: github_app_access_token
|
name: github_app_access_token
|
||||||
namespace: null
|
namespace: null
|
||||||
release_date: '2024-01-01'
|
release_date: '2024-01-01'
|
||||||
|
8.3.0:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- homebrew - detect already installed formulae and casks using JSON output from
|
||||||
|
``brew info`` (https://github.com/ansible-collections/community.general/issues/864).
|
||||||
|
- incus connection plugin - treats ``inventory_hostname`` as a variable instead
|
||||||
|
of a literal in remote connections (https://github.com/ansible-collections/community.general/issues/7874).
|
||||||
|
- ipa_otptoken - the module expect ``ipatokendisabled`` as string but the ``ipatokendisabled``
|
||||||
|
value is returned as a boolean (https://github.com/ansible-collections/community.general/pull/7795).
|
||||||
|
- ldap - previously the order number (if present) was expected to follow an
|
||||||
|
equals sign in the DN. This makes it so the order number string is identified
|
||||||
|
correctly anywhere within the DN (https://github.com/ansible-collections/community.general/issues/7646).
|
||||||
|
- mssql_script - make the module work with Python 2 (https://github.com/ansible-collections/community.general/issues/7818,
|
||||||
|
https://github.com/ansible-collections/community.general/pull/7821).
|
||||||
|
- nmcli - fix ``connection.slave-type`` wired to ``bond`` and not with parameter
|
||||||
|
``slave_type`` in case of connection type ``wifi`` (https://github.com/ansible-collections/community.general/issues/7389).
|
||||||
|
- proxmox - fix updating a container config if the setting does not already
|
||||||
|
exist (https://github.com/ansible-collections/community.general/pull/7872).
|
||||||
|
deprecated_features:
|
||||||
|
- consul_acl - the module has been deprecated and will be removed in community.general
|
||||||
|
10.0.0. ``consul_token`` and ``consul_policy`` can be used instead (https://github.com/ansible-collections/community.general/pull/7901).
|
||||||
|
minor_changes:
|
||||||
|
- consul_auth_method, consul_binding_rule, consul_policy, consul_role, consul_session,
|
||||||
|
consul_token - added action group ``community.general.consul`` (https://github.com/ansible-collections/community.general/pull/7897).
|
||||||
|
- consul_policy - added support for diff and check mode (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_policy, consul_role, consul_session - removed dependency on ``requests``
|
||||||
|
and factored out common parts (https://github.com/ansible-collections/community.general/pull/7826,
|
||||||
|
https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - ``node_identities`` now expects a ``node_name`` option to match
|
||||||
|
the Consul API, the old ``name`` is still supported as alias (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - ``service_identities`` now expects a ``service_name`` option
|
||||||
|
to match the Consul API, the old ``name`` is still supported as alias (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - added support for diff mode (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- consul_role - added support for templated policies (https://github.com/ansible-collections/community.general/pull/7878).
|
||||||
|
- redfish_info - add command ``GetServiceIdentification`` to get service identification
|
||||||
|
(https://github.com/ansible-collections/community.general/issues/7882).
|
||||||
|
- terraform - add support for ``diff_mode`` for terraform resource_changes (https://github.com/ansible-collections/community.general/pull/7896).
|
||||||
|
release_summary: Regular bugfix and feature release.
|
||||||
|
fragments:
|
||||||
|
- 7389-nmcli-issue-with-creating-a-wifi-bridge-slave.yml
|
||||||
|
- 7646-fix-order-number-detection-in-dn.yml
|
||||||
|
- 7797-ipa-fix-otp-idempotency.yml
|
||||||
|
- 7821-mssql_script-py2.yml
|
||||||
|
- 7826-consul-modules-refactoring.yaml
|
||||||
|
- 7870-homebrew-cask-installed-detection.yml
|
||||||
|
- 7872-proxmox_fix-update-if-setting-doesnt-exist.yaml
|
||||||
|
- 7874-incus_connection_treats_inventory_hostname_as_literal_in_remotes.yml
|
||||||
|
- 7882-add-redfish-get-service-identification.yml
|
||||||
|
- 7896-add-terraform-diff-mode.yml
|
||||||
|
- 7897-consul-action-group.yaml
|
||||||
|
- 7901-consul-acl-deprecation.yaml
|
||||||
|
- 8.3.0.yml
|
||||||
|
modules:
|
||||||
|
- description: Bootstrap ACLs in Consul
|
||||||
|
name: consul_acl_bootstrap
|
||||||
|
namespace: ''
|
||||||
|
- description: Manipulate Consul auth methods
|
||||||
|
name: consul_auth_method
|
||||||
|
namespace: ''
|
||||||
|
- description: Manipulate Consul binding rules
|
||||||
|
name: consul_binding_rule
|
||||||
|
namespace: ''
|
||||||
|
- description: Manipulate Consul tokens
|
||||||
|
name: consul_token
|
||||||
|
namespace: ''
|
||||||
|
- description: Creates/updates/deletes GitLab Labels belonging to project or group.
|
||||||
|
name: gitlab_label
|
||||||
|
namespace: ''
|
||||||
|
- description: Creates/updates/deletes GitLab Milestones belonging to project
|
||||||
|
or group
|
||||||
|
name: gitlab_milestone
|
||||||
|
namespace: ''
|
||||||
|
release_date: '2024-01-29'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace: community
|
namespace: community
|
||||||
name: general
|
name: general
|
||||||
version: 8.2.0
|
version: 8.3.0
|
||||||
readme: README.md
|
readme: README.md
|
||||||
authors:
|
authors:
|
||||||
- Ansible (https://github.com/ansible)
|
- Ansible (https://github.com/ansible)
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
requires_ansible: '>=2.13.0'
|
requires_ansible: '>=2.13.0'
|
||||||
|
action_groups:
|
||||||
|
consul:
|
||||||
|
- consul_auth_method
|
||||||
|
- consul_binding_rule
|
||||||
|
- consul_policy
|
||||||
|
- consul_role
|
||||||
|
- consul_session
|
||||||
|
- consul_token
|
||||||
plugin_routing:
|
plugin_routing:
|
||||||
connection:
|
connection:
|
||||||
docker:
|
docker:
|
||||||
@@ -22,6 +30,10 @@ plugin_routing:
|
|||||||
nios_next_network:
|
nios_next_network:
|
||||||
redirect: infoblox.nios_modules.nios_next_network
|
redirect: infoblox.nios_modules.nios_next_network
|
||||||
modules:
|
modules:
|
||||||
|
consul_acl:
|
||||||
|
deprecation:
|
||||||
|
removal_version: 10.0.0
|
||||||
|
warning_text: Use community.general.consul_token and/or community.general.consul_policy instead.
|
||||||
rax_cbs_attachments:
|
rax_cbs_attachments:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
@@ -153,9 +165,9 @@ plugin_routing:
|
|||||||
stackdriver:
|
stackdriver:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: >
|
warning_text: This module relies on HTTPS APIs that do not exist anymore,
|
||||||
This module relies on HTTPS APIs that do not exist anymore, and any new development in the
|
and any new development in the direction of providing an alternative should
|
||||||
direction of providing an alternative should happen in the context of the google.cloud collection.
|
happen in the context of the google.cloud collection.
|
||||||
system.aix_devices:
|
system.aix_devices:
|
||||||
redirect: community.general.aix_devices
|
redirect: community.general.aix_devices
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -807,7 +819,8 @@ plugin_routing:
|
|||||||
flowdock:
|
flowdock:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
notification.flowdock:
|
notification.flowdock:
|
||||||
redirect: community.general.flowdock
|
redirect: community.general.flowdock
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4446,7 +4459,8 @@ plugin_routing:
|
|||||||
webfaction_app:
|
webfaction_app:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
cloud.webfaction.webfaction_app:
|
cloud.webfaction.webfaction_app:
|
||||||
redirect: community.general.webfaction_app
|
redirect: community.general.webfaction_app
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4457,7 +4471,8 @@ plugin_routing:
|
|||||||
webfaction_db:
|
webfaction_db:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
cloud.webfaction.webfaction_db:
|
cloud.webfaction.webfaction_db:
|
||||||
redirect: community.general.webfaction_db
|
redirect: community.general.webfaction_db
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4468,7 +4483,8 @@ plugin_routing:
|
|||||||
webfaction_domain:
|
webfaction_domain:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
cloud.webfaction.webfaction_domain:
|
cloud.webfaction.webfaction_domain:
|
||||||
redirect: community.general.webfaction_domain
|
redirect: community.general.webfaction_domain
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4479,7 +4495,8 @@ plugin_routing:
|
|||||||
webfaction_mailbox:
|
webfaction_mailbox:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
cloud.webfaction.webfaction_mailbox:
|
cloud.webfaction.webfaction_mailbox:
|
||||||
redirect: community.general.webfaction_mailbox
|
redirect: community.general.webfaction_mailbox
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4490,7 +4507,8 @@ plugin_routing:
|
|||||||
webfaction_site:
|
webfaction_site:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||||
|
there is no clear path to update.
|
||||||
cloud.webfaction.webfaction_site:
|
cloud.webfaction.webfaction_site:
|
||||||
redirect: community.general.webfaction_site
|
redirect: community.general.webfaction_site
|
||||||
deprecation:
|
deprecation:
|
||||||
@@ -4646,7 +4664,8 @@ plugin_routing:
|
|||||||
rackspace:
|
rackspace:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 9.0.0
|
removal_version: 9.0.0
|
||||||
warning_text: This doc fragment is used by rax modules, that rely on the deprecated package pyrax.
|
warning_text: This doc fragment is used by rax modules, that rely on the deprecated
|
||||||
|
package pyrax.
|
||||||
_gcp:
|
_gcp:
|
||||||
redirect: community.google._gcp
|
redirect: community.google._gcp
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ DOCUMENTATION = """
|
|||||||
- The instance identifier.
|
- The instance identifier.
|
||||||
default: inventory_hostname
|
default: inventory_hostname
|
||||||
vars:
|
vars:
|
||||||
|
- name: inventory_hostname
|
||||||
- name: ansible_host
|
- name: ansible_host
|
||||||
- name: ansible_incus_host
|
- name: ansible_incus_host
|
||||||
executable:
|
executable:
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ __metaclass__ = type
|
|||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
author: Matt Clay (@mattclay) <matt@mystile.com>
|
author: Matt Clay (@mattclay) <matt@mystile.com>
|
||||||
name: lxd
|
name: lxd
|
||||||
short_description: Run tasks in lxc containers via lxc CLI
|
short_description: Run tasks in LXD instances via C(lxc) CLI
|
||||||
description:
|
description:
|
||||||
- Run commands or put/fetch files to an existing lxc container using lxc CLI
|
- Run commands or put/fetch files to an existing instance using C(lxc) CLI.
|
||||||
options:
|
options:
|
||||||
remote_addr:
|
remote_addr:
|
||||||
description:
|
description:
|
||||||
@@ -26,7 +26,7 @@ DOCUMENTATION = '''
|
|||||||
- name: ansible_lxd_host
|
- name: ansible_lxd_host
|
||||||
executable:
|
executable:
|
||||||
description:
|
description:
|
||||||
- shell to use for execution inside container
|
- Shell to use for execution inside instance.
|
||||||
default: /bin/sh
|
default: /bin/sh
|
||||||
vars:
|
vars:
|
||||||
- name: ansible_executable
|
- name: ansible_executable
|
||||||
@@ -71,7 +71,7 @@ class Connection(ConnectionBase):
|
|||||||
raise AnsibleError("lxc command not found in PATH")
|
raise AnsibleError("lxc command not found in PATH")
|
||||||
|
|
||||||
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
|
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
|
||||||
self._display.warning('lxd does not support remote_user, using container default: root')
|
self._display.warning('lxd does not support remote_user, using default: root')
|
||||||
|
|
||||||
def _host(self):
|
def _host(self):
|
||||||
""" translate remote_addr to lxd (short) hostname """
|
""" translate remote_addr to lxd (short) hostname """
|
||||||
|
|||||||
60
plugins/doc_fragments/consul.py
Normal file
60
plugins/doc_fragments/consul.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleDocFragment:
|
||||||
|
# Common parameters for Consul modules
|
||||||
|
DOCUMENTATION = r"""
|
||||||
|
options:
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- Host of the consul agent, defaults to V(localhost).
|
||||||
|
default: localhost
|
||||||
|
type: str
|
||||||
|
port:
|
||||||
|
type: int
|
||||||
|
description:
|
||||||
|
- The port on which the consul agent is running.
|
||||||
|
default: 8500
|
||||||
|
scheme:
|
||||||
|
description:
|
||||||
|
- The protocol scheme on which the consul agent is running.
|
||||||
|
Defaults to V(http) and can be set to V(https) for secure connections.
|
||||||
|
default: http
|
||||||
|
type: str
|
||||||
|
validate_certs:
|
||||||
|
type: bool
|
||||||
|
description:
|
||||||
|
- Whether to verify the TLS certificate of the consul agent.
|
||||||
|
default: true
|
||||||
|
ca_path:
|
||||||
|
description:
|
||||||
|
- The CA bundle to use for https connections
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
TOKEN = r"""
|
||||||
|
options:
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- The token to use for authorization.
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
ACTIONGROUP_CONSUL = r"""
|
||||||
|
options: {}
|
||||||
|
attributes:
|
||||||
|
action_group:
|
||||||
|
description: Use C(group/community.general.consul) in C(module_defaults) to set defaults for this module.
|
||||||
|
support: full
|
||||||
|
membership:
|
||||||
|
- community.general.consul
|
||||||
|
version_added: 8.3.0
|
||||||
|
"""
|
||||||
@@ -470,7 +470,7 @@ class InventoryModule(BaseInventoryPlugin):
|
|||||||
Helper to get the preferred interface provide by neme pattern from 'prefered_instance_network_interface'.
|
Helper to get the preferred interface provide by neme pattern from 'prefered_instance_network_interface'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
str(containe_name): name of instance
|
str(instance_name): name of instance
|
||||||
Kwargs:
|
Kwargs:
|
||||||
None
|
None
|
||||||
Raises:
|
Raises:
|
||||||
@@ -495,7 +495,7 @@ class InventoryModule(BaseInventoryPlugin):
|
|||||||
Helper to get the VLAN_ID from the instance
|
Helper to get the VLAN_ID from the instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
str(containe_name): name of instance
|
str(instance_name): name of instance
|
||||||
Kwargs:
|
Kwargs:
|
||||||
None
|
None
|
||||||
Raises:
|
Raises:
|
||||||
|
|||||||
@@ -5,25 +5,317 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
||||||
|
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||||
|
from ansible.module_utils.urls import open_url
|
||||||
|
|
||||||
|
|
||||||
def get_consul_url(configuration):
|
def get_consul_url(configuration):
|
||||||
return '%s://%s:%s/v1' % (configuration.scheme,
|
return "%s://%s:%s/v1" % (
|
||||||
configuration.host, configuration.port)
|
configuration.scheme,
|
||||||
|
configuration.host,
|
||||||
|
configuration.port,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_auth_headers(configuration):
|
def get_auth_headers(configuration):
|
||||||
if configuration.token is None:
|
if configuration.token is None:
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
return {'X-Consul-Token': configuration.token}
|
return {"X-Consul-Token": configuration.token}
|
||||||
|
|
||||||
|
|
||||||
class RequestError(Exception):
|
class RequestError(Exception):
|
||||||
pass
|
def __init__(self, status, response_data=None):
|
||||||
|
self.status = status
|
||||||
|
self.response_data = response_data
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.response_data is None:
|
||||||
|
# self.status is already the message (backwards compat)
|
||||||
|
return self.status
|
||||||
|
return "HTTP %d: %s" % (self.status, self.response_data)
|
||||||
|
|
||||||
|
|
||||||
def handle_consul_response_error(response):
|
def handle_consul_response_error(response):
|
||||||
if 400 <= response.status_code < 600:
|
if 400 <= response.status_code < 600:
|
||||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
raise RequestError("%d %s" % (response.status_code, response.content))
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_ARGUMENTS_SPEC = dict(
|
||||||
|
host=dict(default="localhost"),
|
||||||
|
port=dict(type="int", default=8500),
|
||||||
|
scheme=dict(default="http"),
|
||||||
|
validate_certs=dict(type="bool", default=True),
|
||||||
|
token=dict(no_log=True),
|
||||||
|
ca_path=dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def camel_case_key(key):
|
||||||
|
parts = []
|
||||||
|
for part in key.split("_"):
|
||||||
|
if part in {"id", "ttl", "jwks", "jwt", "oidc", "iam", "sts"}:
|
||||||
|
parts.append(part.upper())
|
||||||
|
else:
|
||||||
|
parts.append(part.capitalize())
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
STATE_PARAMETER = "state"
|
||||||
|
STATE_PRESENT = "present"
|
||||||
|
STATE_ABSENT = "absent"
|
||||||
|
|
||||||
|
OPERATION_READ = "read"
|
||||||
|
OPERATION_CREATE = "create"
|
||||||
|
OPERATION_UPDATE = "update"
|
||||||
|
OPERATION_DELETE = "remove"
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_params(params, arg_spec):
|
||||||
|
final_params = {}
|
||||||
|
for k, v in params.items():
|
||||||
|
if k not in arg_spec: # Alias
|
||||||
|
continue
|
||||||
|
spec = arg_spec[k]
|
||||||
|
if (
|
||||||
|
spec.get("type") == "list"
|
||||||
|
and spec.get("elements") == "dict"
|
||||||
|
and spec.get("options")
|
||||||
|
and v
|
||||||
|
):
|
||||||
|
v = [_normalize_params(d, spec["options"]) for d in v]
|
||||||
|
elif spec.get("type") == "dict" and spec.get("options") and v:
|
||||||
|
v = _normalize_params(v, spec["options"])
|
||||||
|
final_params[k] = v
|
||||||
|
return final_params
|
||||||
|
|
||||||
|
|
||||||
|
class _ConsulModule:
|
||||||
|
"""Base class for Consul modules.
|
||||||
|
|
||||||
|
This class is considered private, till the API is fully fleshed out.
|
||||||
|
As such backwards incompatible changes can occur even in bugfix releases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
api_endpoint = None # type: str
|
||||||
|
unique_identifier = None # type: str
|
||||||
|
result_key = None # type: str
|
||||||
|
create_only_fields = set()
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
def __init__(self, module):
|
||||||
|
self._module = module
|
||||||
|
self.params = _normalize_params(module.params, module.argument_spec)
|
||||||
|
self.api_params = {
|
||||||
|
k: camel_case_key(k)
|
||||||
|
for k in self.params
|
||||||
|
if k not in STATE_PARAMETER and k not in AUTH_ARGUMENTS_SPEC
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
obj = self.read_object()
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
diff = {}
|
||||||
|
if self.params[STATE_PARAMETER] == STATE_PRESENT:
|
||||||
|
obj_from_module = self.module_to_obj(obj is not None)
|
||||||
|
if obj is None:
|
||||||
|
operation = OPERATION_CREATE
|
||||||
|
new_obj = self.create_object(obj_from_module)
|
||||||
|
diff = {"before": {}, "after": new_obj}
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
operation = OPERATION_UPDATE
|
||||||
|
if self._needs_update(obj, obj_from_module):
|
||||||
|
new_obj = self.update_object(obj, obj_from_module)
|
||||||
|
diff = {"before": obj, "after": new_obj}
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
new_obj = obj
|
||||||
|
elif self.params[STATE_PARAMETER] == STATE_ABSENT:
|
||||||
|
operation = OPERATION_DELETE
|
||||||
|
if obj is not None:
|
||||||
|
self.delete_object(obj)
|
||||||
|
changed = True
|
||||||
|
diff = {"before": obj, "after": {}}
|
||||||
|
else:
|
||||||
|
diff = {"before": {}, "after": {}}
|
||||||
|
new_obj = None
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unknown state supplied.")
|
||||||
|
|
||||||
|
result = {"changed": changed}
|
||||||
|
if changed:
|
||||||
|
result["operation"] = operation
|
||||||
|
if self._module._diff:
|
||||||
|
result["diff"] = diff
|
||||||
|
if self.result_key:
|
||||||
|
result[self.result_key] = new_obj
|
||||||
|
self._module.exit_json(**result)
|
||||||
|
|
||||||
|
def module_to_obj(self, is_update):
|
||||||
|
obj = {}
|
||||||
|
for k, v in self.params.items():
|
||||||
|
result = self.map_param(k, v, is_update)
|
||||||
|
if result:
|
||||||
|
obj[result[0]] = result[1]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def map_param(self, k, v, is_update):
|
||||||
|
def helper(item):
|
||||||
|
return {camel_case_key(k): v for k, v in item.items()}
|
||||||
|
|
||||||
|
def needs_camel_case(k):
|
||||||
|
spec = self._module.argument_spec[k]
|
||||||
|
return (
|
||||||
|
spec.get("type") == "list"
|
||||||
|
and spec.get("elements") == "dict"
|
||||||
|
and spec.get("options")
|
||||||
|
) or (spec.get("type") == "dict" and spec.get("options"))
|
||||||
|
|
||||||
|
if k in self.api_params and v is not None:
|
||||||
|
if isinstance(v, dict) and needs_camel_case(k):
|
||||||
|
v = helper(v)
|
||||||
|
elif isinstance(v, (list, tuple)) and needs_camel_case(k):
|
||||||
|
v = [helper(i) for i in v]
|
||||||
|
if is_update and k in self.create_only_fields:
|
||||||
|
return
|
||||||
|
return camel_case_key(k), v
|
||||||
|
|
||||||
|
def _needs_update(self, api_obj, module_obj):
|
||||||
|
api_obj = copy.deepcopy(api_obj)
|
||||||
|
module_obj = copy.deepcopy(module_obj)
|
||||||
|
return self.needs_update(api_obj, module_obj)
|
||||||
|
|
||||||
|
def needs_update(self, api_obj, module_obj):
|
||||||
|
for k, v in module_obj.items():
|
||||||
|
if k not in api_obj:
|
||||||
|
return True
|
||||||
|
if api_obj[k] != v:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def prepare_object(self, existing, obj):
|
||||||
|
operational_attributes = {"CreateIndex", "CreateTime", "Hash", "ModifyIndex"}
|
||||||
|
existing = {
|
||||||
|
k: v for k, v in existing.items() if k not in operational_attributes
|
||||||
|
}
|
||||||
|
for k, v in obj.items():
|
||||||
|
existing[k] = v
|
||||||
|
return existing
|
||||||
|
|
||||||
|
def endpoint_url(self, operation, identifier=None):
|
||||||
|
if operation == OPERATION_CREATE:
|
||||||
|
return self.api_endpoint
|
||||||
|
elif identifier:
|
||||||
|
return "/".join([self.api_endpoint, identifier])
|
||||||
|
raise RuntimeError("invalid arguments passed")
|
||||||
|
|
||||||
|
def read_object(self):
|
||||||
|
url = self.endpoint_url(OPERATION_READ, self.params.get(self.unique_identifier))
|
||||||
|
try:
|
||||||
|
return self.get(url)
|
||||||
|
except RequestError as e:
|
||||||
|
if e.status == 404:
|
||||||
|
return
|
||||||
|
elif e.status == 403 and b"ACL not found" in e.response_data:
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
|
def create_object(self, obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return obj
|
||||||
|
else:
|
||||||
|
return self.put(self.api_endpoint, data=self.prepare_object({}, obj))
|
||||||
|
|
||||||
|
def update_object(self, existing, obj):
|
||||||
|
url = self.endpoint_url(
|
||||||
|
OPERATION_UPDATE, existing.get(camel_case_key(self.unique_identifier))
|
||||||
|
)
|
||||||
|
merged_object = self.prepare_object(existing, obj)
|
||||||
|
if self._module.check_mode:
|
||||||
|
return merged_object
|
||||||
|
else:
|
||||||
|
return self.put(url, data=merged_object)
|
||||||
|
|
||||||
|
def delete_object(self, obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
url = self.endpoint_url(
|
||||||
|
OPERATION_DELETE, obj.get(camel_case_key(self.unique_identifier))
|
||||||
|
)
|
||||||
|
return self.delete(url)
|
||||||
|
|
||||||
|
def _request(self, method, url_parts, data=None, params=None):
|
||||||
|
module_params = self.params
|
||||||
|
|
||||||
|
if not isinstance(url_parts, (tuple, list)):
|
||||||
|
url_parts = [url_parts]
|
||||||
|
if params:
|
||||||
|
# Remove values that are None
|
||||||
|
params = {k: v for k, v in params.items() if v is not None}
|
||||||
|
|
||||||
|
ca_path = module_params.get("ca_path")
|
||||||
|
base_url = "%s://%s:%s/v1" % (
|
||||||
|
module_params["scheme"],
|
||||||
|
module_params["host"],
|
||||||
|
module_params["port"],
|
||||||
|
)
|
||||||
|
url = "/".join([base_url] + list(url_parts))
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
token = self.params.get("token")
|
||||||
|
if token:
|
||||||
|
headers["X-Consul-Token"] = token
|
||||||
|
|
||||||
|
try:
|
||||||
|
if data:
|
||||||
|
data = json.dumps(data)
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
if params:
|
||||||
|
url = "%s?%s" % (url, urlencode(params))
|
||||||
|
response = open_url(
|
||||||
|
url,
|
||||||
|
method=method,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
validate_certs=module_params["validate_certs"],
|
||||||
|
ca_path=ca_path,
|
||||||
|
)
|
||||||
|
response_data = response.read()
|
||||||
|
status = (
|
||||||
|
response.status if hasattr(response, "status") else response.getcode()
|
||||||
|
)
|
||||||
|
|
||||||
|
except urllib_error.URLError as e:
|
||||||
|
if isinstance(e, urllib_error.HTTPError):
|
||||||
|
status = e.code
|
||||||
|
response_data = e.fp.read()
|
||||||
|
else:
|
||||||
|
self._module.fail_json(
|
||||||
|
msg="Could not connect to consul agent at %s:%s, error was %s"
|
||||||
|
% (module_params["host"], module_params["port"], str(e))
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
if 400 <= status < 600:
|
||||||
|
raise RequestError(status, response_data)
|
||||||
|
|
||||||
|
return json.loads(response_data)
|
||||||
|
|
||||||
|
def get(self, url_parts, **kwargs):
|
||||||
|
return self._request("GET", url_parts, **kwargs)
|
||||||
|
|
||||||
|
def put(self, url_parts, **kwargs):
|
||||||
|
return self._request("PUT", url_parts, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, url_parts, **kwargs):
|
||||||
|
return self._request("DELETE", url_parts, **kwargs)
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ def find_project(gitlab_instance, identifier):
|
|||||||
|
|
||||||
def find_group(gitlab_instance, identifier):
|
def find_group(gitlab_instance, identifier):
|
||||||
try:
|
try:
|
||||||
project = gitlab_instance.groups.get(identifier)
|
group = gitlab_instance.groups.get(identifier)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return project
|
return group
|
||||||
|
|
||||||
|
|
||||||
def ensure_gitlab_package(module):
|
def ensure_gitlab_package(module):
|
||||||
|
|||||||
@@ -139,5 +139,7 @@ class LdapGeneric(object):
|
|||||||
|
|
||||||
def _xorder_dn(self):
|
def _xorder_dn(self):
|
||||||
# match X_ORDERed DNs
|
# match X_ORDERed DNs
|
||||||
regex = r"\w+=\{\d+\}.+"
|
regex = r".+\{\d+\}.+"
|
||||||
return re.match(regex, self.module.params['dn']) is not None
|
explode_dn = ldap.dn.explode_dn(self.module.params['dn'])
|
||||||
|
|
||||||
|
return re.match(regex, explode_dn[0]) is not None
|
||||||
|
|||||||
@@ -3365,7 +3365,8 @@ class RedfishUtils(object):
|
|||||||
inventory = {}
|
inventory = {}
|
||||||
# Get these entries, but does not fail if not found
|
# Get these entries, but does not fail if not found
|
||||||
properties = ['Id', 'FirmwareVersion', 'ManagerType', 'Manufacturer', 'Model',
|
properties = ['Id', 'FirmwareVersion', 'ManagerType', 'Manufacturer', 'Model',
|
||||||
'PartNumber', 'PowerState', 'SerialNumber', 'Status', 'UUID']
|
'PartNumber', 'PowerState', 'SerialNumber', 'ServiceIdentification',
|
||||||
|
'Status', 'UUID']
|
||||||
|
|
||||||
response = self.get_request(self.root_uri + manager_uri)
|
response = self.get_request(self.root_uri + manager_uri)
|
||||||
if response['ret'] is False:
|
if response['ret'] is False:
|
||||||
@@ -3383,6 +3384,30 @@ class RedfishUtils(object):
|
|||||||
def get_multi_manager_inventory(self):
|
def get_multi_manager_inventory(self):
|
||||||
return self.aggregate_managers(self.get_manager_inventory)
|
return self.aggregate_managers(self.get_manager_inventory)
|
||||||
|
|
||||||
|
def get_service_identification(self, manager):
|
||||||
|
result = {}
|
||||||
|
if manager is None:
|
||||||
|
if len(self.manager_uris) == 1:
|
||||||
|
manager = self.manager_uris[0].split('/')[-1]
|
||||||
|
elif len(self.manager_uris) > 1:
|
||||||
|
entries = self.get_multi_manager_inventory()['entries']
|
||||||
|
managers = [m[0]['manager_uri'] for m in entries if m[1].get('ServiceIdentification')]
|
||||||
|
if len(managers) == 1:
|
||||||
|
manager = managers[0].split('/')[-1]
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg=[
|
||||||
|
"Multiple managers with ServiceIdentification were found: %s" % str(managers),
|
||||||
|
"Please specify by using the 'manager' parameter in your playbook"])
|
||||||
|
elif len(self.manager_uris) == 0:
|
||||||
|
self.module.fail_json(msg="No manager identities were found")
|
||||||
|
response = self.get_request(self.root_uri + '/redfish/v1/Managers/' + manager, override_headers=None)
|
||||||
|
try:
|
||||||
|
result['service_identification'] = response['data']['ServiceIdentification']
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg="Service ID not found for manager %s" % manager)
|
||||||
|
result['ret'] = True
|
||||||
|
return result
|
||||||
|
|
||||||
def set_session_service(self, sessions_config):
|
def set_session_service(self, sessions_config):
|
||||||
if sessions_config is None:
|
if sessions_config is None:
|
||||||
return {'ret': False, 'msg':
|
return {'ret': False, 'msg':
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ attributes:
|
|||||||
support: none
|
support: none
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: none
|
support: none
|
||||||
|
deprecated:
|
||||||
|
removed_in: 10.0.0
|
||||||
|
why: The legacy ACL system was removed from Consul.
|
||||||
|
alternative: Use M(community.general.consul_token) and/or M(community.general.consul_policy) instead.
|
||||||
options:
|
options:
|
||||||
mgmt_token:
|
mgmt_token:
|
||||||
description:
|
description:
|
||||||
|
|||||||
108
plugins/modules/consul_acl_bootstrap.py
Normal file
108
plugins/modules/consul_acl_bootstrap.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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 = """
|
||||||
|
module: consul_acl_bootstrap
|
||||||
|
short_description: Bootstrap ACLs in Consul
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- Allows bootstrapping of ACLs in a Consul cluster, see
|
||||||
|
U(https://developer.hashicorp.com/consul/api-docs/acl#bootstrap-acls) for details.
|
||||||
|
author:
|
||||||
|
- Florian Apolloner (@apollo13)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.attributes
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: none
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Whether the token should be present or absent.
|
||||||
|
choices: ['present', 'bootstrapped']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
bootstrap_secret:
|
||||||
|
description:
|
||||||
|
- The secret to be used as secret ID for the initial token.
|
||||||
|
- Needs to be an UUID.
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Bootstrap the ACL system
|
||||||
|
community.general.consul_acl_bootstrap:
|
||||||
|
bootstrap_secret: 22eaeed1-bdbd-4651-724e-42ae6c43e387
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
result:
|
||||||
|
description:
|
||||||
|
- The bootstrap result as returned by the consul HTTP API.
|
||||||
|
- "B(Note:) If O(bootstrap_secret) has been specified the C(SecretID) and
|
||||||
|
C(ID) will not contain the secret but C(VALUE_SPECIFIED_IN_NO_LOG_PARAMETER).
|
||||||
|
If you pass O(bootstrap_secret), make sure your playbook/role does not depend
|
||||||
|
on this return value!"
|
||||||
|
returned: changed
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
AccessorID: 834a5881-10a9-a45b-f63c-490e28743557
|
||||||
|
CreateIndex: 25
|
||||||
|
CreateTime: '2024-01-21T20:26:27.114612038+01:00'
|
||||||
|
Description: Bootstrap Token (Global Management)
|
||||||
|
Hash: X2AgaFhnQGRhSSF/h0m6qpX1wj/HJWbyXcxkEM/5GrY=
|
||||||
|
ID: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
|
||||||
|
Local: false
|
||||||
|
ModifyIndex: 25
|
||||||
|
Policies:
|
||||||
|
- ID: 00000000-0000-0000-0000-000000000001
|
||||||
|
Name: global-management
|
||||||
|
SecretID: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
RequestError,
|
||||||
|
_ConsulModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
_ARGUMENT_SPEC = {
|
||||||
|
"state": dict(type="str", choices=["present", "bootstrapped"], default="present"),
|
||||||
|
"bootstrap_secret": dict(type="str", no_log=True),
|
||||||
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
_ARGUMENT_SPEC.pop("token")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(_ARGUMENT_SPEC)
|
||||||
|
consul_module = _ConsulModule(module)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if "bootstrap_secret" in module.params:
|
||||||
|
data["BootstrapSecret"] = module.params["bootstrap_secret"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = consul_module.put("acl/bootstrap", data=data)
|
||||||
|
except RequestError as e:
|
||||||
|
if e.status == 403 and b"ACL bootstrap no longer allowed" in e.response_data:
|
||||||
|
return module.exit_json(changed=False)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return module.exit_json(changed=True, result=response)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
207
plugins/modules/consul_auth_method.py
Normal file
207
plugins/modules/consul_auth_method.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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 = """
|
||||||
|
module: consul_auth_method
|
||||||
|
short_description: Manipulate Consul auth methods
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- Allows the addition, modification and deletion of auth methods in a consul
|
||||||
|
cluster via the agent. For more details on using and configuring ACLs,
|
||||||
|
see U(https://www.consul.io/docs/guides/acl.html).
|
||||||
|
author:
|
||||||
|
- Florian Apolloner (@apollo13)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
|
- community.general.consul.token
|
||||||
|
- community.general.attributes
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: partial
|
||||||
|
details:
|
||||||
|
- In check mode the diff will miss operational attributes.
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Whether the token should be present or absent.
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Specifies a name for the ACL auth method.
|
||||||
|
- The name can contain alphanumeric characters, dashes C(-), and underscores C(_).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
type:
|
||||||
|
description:
|
||||||
|
- The type of auth method being configured.
|
||||||
|
- This field is immutable.
|
||||||
|
- Required when the auth method is created.
|
||||||
|
type: str
|
||||||
|
choices: ['kubernetes', 'jwt', 'oidc', 'aws-iam']
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Free form human readable description of the auth method.
|
||||||
|
type: str
|
||||||
|
display_name:
|
||||||
|
description:
|
||||||
|
- An optional name to use instead of O(name) when displaying information about this auth method.
|
||||||
|
type: str
|
||||||
|
max_token_ttl:
|
||||||
|
description:
|
||||||
|
- This specifies the maximum life of any token created by this auth method.
|
||||||
|
- Can be specified in the form of V(60s) or V(5m) (that is, 60 seconds or 5 minutes, respectively).
|
||||||
|
type: str
|
||||||
|
token_locality:
|
||||||
|
description:
|
||||||
|
- Defines the kind of token that this auth method should produce.
|
||||||
|
type: str
|
||||||
|
choices: ['local', 'global']
|
||||||
|
config:
|
||||||
|
description:
|
||||||
|
- The raw configuration to use for the chosen auth method.
|
||||||
|
- Contents will vary depending upon the type chosen.
|
||||||
|
- Required when the auth method is created.
|
||||||
|
type: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Create an auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
type: jwt
|
||||||
|
config:
|
||||||
|
jwt_validation_pubkeys:
|
||||||
|
- |
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
|
||||||
|
- name: Delete auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
state: absent
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
auth_method:
|
||||||
|
description: The auth method as returned by the consul HTTP API.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
Config:
|
||||||
|
JWTValidationPubkeys:
|
||||||
|
- |-
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
CreateIndex: 416
|
||||||
|
ModifyIndex: 487
|
||||||
|
Name: test
|
||||||
|
Type: jwt
|
||||||
|
operation:
|
||||||
|
description: The operation performed.
|
||||||
|
returned: changed
|
||||||
|
type: str
|
||||||
|
sample: update
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
_ConsulModule,
|
||||||
|
camel_case_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_ttl(ttl):
|
||||||
|
matches = re.findall(r"(\d+)(:h|m|s)", ttl)
|
||||||
|
ttl = 0
|
||||||
|
for value, unit in matches:
|
||||||
|
value = int(value)
|
||||||
|
if unit == "m":
|
||||||
|
value *= 60
|
||||||
|
elif unit == "h":
|
||||||
|
value *= 60 * 60
|
||||||
|
ttl += value
|
||||||
|
|
||||||
|
new_ttl = ""
|
||||||
|
hours, remainder = divmod(ttl, 3600)
|
||||||
|
if hours:
|
||||||
|
new_ttl += "{0}h".format(hours)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
if minutes:
|
||||||
|
new_ttl += "{0}m".format(minutes)
|
||||||
|
if seconds:
|
||||||
|
new_ttl += "{0}s".format(seconds)
|
||||||
|
return new_ttl
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulAuthMethodModule(_ConsulModule):
|
||||||
|
api_endpoint = "acl/auth-method"
|
||||||
|
result_key = "auth_method"
|
||||||
|
unique_identifier = "name"
|
||||||
|
|
||||||
|
def map_param(self, k, v, is_update):
|
||||||
|
if k == "config" and v:
|
||||||
|
v = {camel_case_key(k2): v2 for k2, v2 in v.items()}
|
||||||
|
return super(ConsulAuthMethodModule, self).map_param(k, v, is_update)
|
||||||
|
|
||||||
|
def needs_update(self, api_obj, module_obj):
|
||||||
|
if "MaxTokenTTL" in module_obj:
|
||||||
|
module_obj["MaxTokenTTL"] = normalize_ttl(module_obj["MaxTokenTTL"])
|
||||||
|
return super(ConsulAuthMethodModule, self).needs_update(api_obj, module_obj)
|
||||||
|
|
||||||
|
|
||||||
|
_ARGUMENT_SPEC = {
|
||||||
|
"name": dict(type="str", required=True),
|
||||||
|
"type": dict(type="str", choices=["kubernetes", "jwt", "oidc", "aws-iam"]),
|
||||||
|
"description": dict(type="str"),
|
||||||
|
"display_name": dict(type="str"),
|
||||||
|
"max_token_ttl": dict(type="str", no_log=False),
|
||||||
|
"token_locality": dict(type="str", choices=["local", "global"]),
|
||||||
|
"config": dict(type="dict"),
|
||||||
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
_ARGUMENT_SPEC,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
consul_module = ConsulAuthMethodModule(module)
|
||||||
|
consul_module.execute()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
183
plugins/modules/consul_binding_rule.py
Normal file
183
plugins/modules/consul_binding_rule.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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 = """
|
||||||
|
module: consul_binding_rule
|
||||||
|
short_description: Manipulate Consul binding rules
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- Allows the addition, modification and deletion of binding rules in a consul
|
||||||
|
cluster via the agent. For more details on using and configuring binding rules,
|
||||||
|
see U(https://developer.hashicorp.com/consul/api-docs/acl/binding-rules).
|
||||||
|
author:
|
||||||
|
- Florian Apolloner (@apollo13)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
|
- community.general.consul.token
|
||||||
|
- community.general.attributes
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: partial
|
||||||
|
details:
|
||||||
|
- In check mode the diff will miss operational attributes.
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Whether the binding rule should be present or absent.
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Specifies a name for the binding rule.
|
||||||
|
- 'Note: This is used to identify the binding rule. But since the API does not support a name, it is prefixed to the description.'
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Free form human readable description of the binding rule.
|
||||||
|
type: str
|
||||||
|
auth_method:
|
||||||
|
description:
|
||||||
|
- The name of the auth method that this rule applies to.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
description:
|
||||||
|
- Specifies the expression used to match this rule against valid identities returned from an auth method validation.
|
||||||
|
- If empty this binding rule matches all valid identities returned from the auth method.
|
||||||
|
type: str
|
||||||
|
bind_type:
|
||||||
|
description:
|
||||||
|
- Specifies the way the binding rule affects a token created at login.
|
||||||
|
type: str
|
||||||
|
choices: [service, node, role, templated-policy]
|
||||||
|
bind_name:
|
||||||
|
description:
|
||||||
|
- The name to bind to a token at login-time.
|
||||||
|
- What it binds to can be adjusted with different values of the O(bind_type) parameter.
|
||||||
|
type: str
|
||||||
|
bind_vars:
|
||||||
|
description:
|
||||||
|
- Specifies the templated policy variables when O(bind_type) is set to V(templated-policy).
|
||||||
|
type: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Create a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: my_name
|
||||||
|
description: example rule
|
||||||
|
auth_method: minikube
|
||||||
|
bind_type: service
|
||||||
|
bind_name: "{{ serviceaccount.name }}"
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
|
||||||
|
- name: Remove a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: my_name
|
||||||
|
auth_method: minikube
|
||||||
|
state: absent
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
binding_rule:
|
||||||
|
description: The binding rule as returned by the consul HTTP API.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
Description: "my_name: example rule"
|
||||||
|
AuthMethod: minikube
|
||||||
|
Selector: serviceaccount.namespace==default
|
||||||
|
BindType: service
|
||||||
|
BindName: "{{ serviceaccount.name }}"
|
||||||
|
CreateIndex: 30
|
||||||
|
ID: 59c8a237-e481-4239-9202-45f117950c5f
|
||||||
|
ModifyIndex: 33
|
||||||
|
operation:
|
||||||
|
description: The operation performed.
|
||||||
|
returned: changed
|
||||||
|
type: str
|
||||||
|
sample: update
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
RequestError,
|
||||||
|
_ConsulModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulBindingRuleModule(_ConsulModule):
|
||||||
|
api_endpoint = "acl/binding-rule"
|
||||||
|
result_key = "binding_rule"
|
||||||
|
unique_identifier = "id"
|
||||||
|
|
||||||
|
def read_object(self):
|
||||||
|
url = "acl/binding-rules?authmethod={0}".format(self.params["auth_method"])
|
||||||
|
try:
|
||||||
|
results = self.get(url)
|
||||||
|
for result in results:
|
||||||
|
if result.get("Description").startswith(
|
||||||
|
"{0}: ".format(self.params["name"])
|
||||||
|
):
|
||||||
|
return result
|
||||||
|
except RequestError as e:
|
||||||
|
if e.status == 404:
|
||||||
|
return
|
||||||
|
elif e.status == 403 and b"ACL not found" in e.response_data:
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
|
def module_to_obj(self, is_update):
|
||||||
|
obj = super(ConsulBindingRuleModule, self).module_to_obj(is_update)
|
||||||
|
del obj["Name"]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def prepare_object(self, existing, obj):
|
||||||
|
final = super(ConsulBindingRuleModule, self).prepare_object(existing, obj)
|
||||||
|
name = self.params["name"]
|
||||||
|
description = final.pop("Description", "").split(": ", 1)[-1]
|
||||||
|
final["Description"] = "{0}: {1}".format(name, description)
|
||||||
|
return final
|
||||||
|
|
||||||
|
|
||||||
|
_ARGUMENT_SPEC = {
|
||||||
|
"name": dict(type="str", required=True),
|
||||||
|
"description": dict(type="str"),
|
||||||
|
"auth_method": dict(type="str", required=True),
|
||||||
|
"selector": dict(type="str"),
|
||||||
|
"bind_type": dict(
|
||||||
|
type="str", choices=["service", "node", "role", "templated-policy"]
|
||||||
|
),
|
||||||
|
"bind_name": dict(type="str"),
|
||||||
|
"bind_vars": dict(type="dict"),
|
||||||
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
_ARGUMENT_SPEC,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
consul_module = ConsulBindingRuleModule(module)
|
||||||
|
consul_module.execute()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -6,9 +6,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = """
|
||||||
module: consul_policy
|
module: consul_policy
|
||||||
short_description: Manipulate Consul policies
|
short_description: Manipulate Consul policies
|
||||||
version_added: 7.2.0
|
version_added: 7.2.0
|
||||||
@@ -19,24 +20,29 @@ description:
|
|||||||
author:
|
author:
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
|
- community.general.consul.token
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
support: none
|
support: full
|
||||||
|
version_added: 8.3.0
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: none
|
support: partial
|
||||||
|
version_added: 8.3.0
|
||||||
|
details:
|
||||||
|
- In check mode the diff will miss operational attributes.
|
||||||
options:
|
options:
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Whether the policy should be present or absent.
|
- Whether the policy should be present or absent.
|
||||||
required: false
|
|
||||||
choices: ['present', 'absent']
|
choices: ['present', 'absent']
|
||||||
default: present
|
default: present
|
||||||
type: str
|
type: str
|
||||||
valid_datacenters:
|
valid_datacenters:
|
||||||
description:
|
description:
|
||||||
- Valid datacenters for the policy. All if list is empty.
|
- Valid datacenters for the policy. All if list is empty.
|
||||||
default: []
|
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
name:
|
name:
|
||||||
@@ -48,45 +54,12 @@ options:
|
|||||||
description:
|
description:
|
||||||
description:
|
description:
|
||||||
- Description of the policy.
|
- Description of the policy.
|
||||||
required: false
|
|
||||||
type: str
|
type: str
|
||||||
default: ''
|
|
||||||
rules:
|
rules:
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
- Rule document that should be associated with the current policy.
|
- Rule document that should be associated with the current policy.
|
||||||
required: false
|
"""
|
||||||
host:
|
|
||||||
description:
|
|
||||||
- Host of the consul agent, defaults to localhost.
|
|
||||||
required: false
|
|
||||||
default: localhost
|
|
||||||
type: str
|
|
||||||
port:
|
|
||||||
type: int
|
|
||||||
description:
|
|
||||||
- The port on which the consul agent is running.
|
|
||||||
required: false
|
|
||||||
default: 8500
|
|
||||||
scheme:
|
|
||||||
description:
|
|
||||||
- The protocol scheme on which the consul agent is running.
|
|
||||||
required: false
|
|
||||||
default: http
|
|
||||||
type: str
|
|
||||||
token:
|
|
||||||
description:
|
|
||||||
- A management token is required to manipulate the policies.
|
|
||||||
type: str
|
|
||||||
validate_certs:
|
|
||||||
type: bool
|
|
||||||
description:
|
|
||||||
- Whether to verify the TLS certificate of the consul agent or not.
|
|
||||||
required: false
|
|
||||||
default: true
|
|
||||||
requirements:
|
|
||||||
- requests
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Create a policy with rules
|
- name: Create a policy with rules
|
||||||
@@ -127,246 +100,64 @@ EXAMPLES = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
|
policy:
|
||||||
|
description: The policy as returned by the consul HTTP API.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
CreateIndex: 632
|
||||||
|
Description: Testing
|
||||||
|
Hash: rj5PeDHddHslkpW7Ij4OD6N4bbSXiecXFmiw2SYXg2A=
|
||||||
|
Name: foo-access
|
||||||
|
Rules: |-
|
||||||
|
key "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
key "private/foo" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
operation:
|
operation:
|
||||||
description: The operation performed on the policy.
|
description: The operation performed.
|
||||||
returned: changed
|
returned: changed
|
||||||
type: str
|
type: str
|
||||||
sample: update
|
sample: update
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
try:
|
AUTH_ARGUMENTS_SPEC,
|
||||||
from requests.exceptions import ConnectionError
|
OPERATION_READ,
|
||||||
import requests
|
_ConsulModule,
|
||||||
has_requests = True
|
)
|
||||||
except ImportError:
|
|
||||||
has_requests = False
|
|
||||||
|
|
||||||
|
|
||||||
TOKEN_PARAMETER_NAME = "token"
|
|
||||||
HOST_PARAMETER_NAME = "host"
|
|
||||||
SCHEME_PARAMETER_NAME = "scheme"
|
|
||||||
VALIDATE_CERTS_PARAMETER_NAME = "validate_certs"
|
|
||||||
NAME_PARAMETER_NAME = "name"
|
|
||||||
DESCRIPTION_PARAMETER_NAME = "description"
|
|
||||||
PORT_PARAMETER_NAME = "port"
|
|
||||||
RULES_PARAMETER_NAME = "rules"
|
|
||||||
VALID_DATACENTERS_PARAMETER_NAME = "valid_datacenters"
|
|
||||||
STATE_PARAMETER_NAME = "state"
|
|
||||||
|
|
||||||
|
|
||||||
PRESENT_STATE_VALUE = "present"
|
|
||||||
ABSENT_STATE_VALUE = "absent"
|
|
||||||
|
|
||||||
REMOVE_OPERATION = "remove"
|
|
||||||
UPDATE_OPERATION = "update"
|
|
||||||
CREATE_OPERATION = "create"
|
|
||||||
|
|
||||||
_ARGUMENT_SPEC = {
|
_ARGUMENT_SPEC = {
|
||||||
NAME_PARAMETER_NAME: dict(required=True),
|
"name": dict(required=True),
|
||||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=''),
|
"description": dict(required=False, type="str"),
|
||||||
PORT_PARAMETER_NAME: dict(default=8500, type='int'),
|
"rules": dict(type="str"),
|
||||||
RULES_PARAMETER_NAME: dict(type='str'),
|
"valid_datacenters": dict(type="list", elements="str"),
|
||||||
VALID_DATACENTERS_PARAMETER_NAME: dict(type='list', elements='str', default=[]),
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
HOST_PARAMETER_NAME: dict(default='localhost'),
|
|
||||||
SCHEME_PARAMETER_NAME: dict(default='http'),
|
|
||||||
TOKEN_PARAMETER_NAME: dict(no_log=True),
|
|
||||||
VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True),
|
|
||||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]),
|
|
||||||
}
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
|
|
||||||
def get_consul_url(configuration):
|
class ConsulPolicyModule(_ConsulModule):
|
||||||
return '%s://%s:%s/v1' % (configuration.scheme,
|
api_endpoint = "acl/policy"
|
||||||
configuration.host, configuration.port)
|
result_key = "policy"
|
||||||
|
unique_identifier = "id"
|
||||||
|
|
||||||
|
def endpoint_url(self, operation, identifier=None):
|
||||||
def get_auth_headers(configuration):
|
if operation == OPERATION_READ:
|
||||||
if configuration.token is None:
|
return [self.api_endpoint, "name", self.params["name"]]
|
||||||
return {}
|
return super(ConsulPolicyModule, self).endpoint_url(operation, identifier)
|
||||||
else:
|
|
||||||
return {'X-Consul-Token': configuration.token}
|
|
||||||
|
|
||||||
|
|
||||||
class RequestError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def handle_consul_response_error(response):
|
|
||||||
if 400 <= response.status_code < 600:
|
|
||||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
|
||||||
|
|
||||||
|
|
||||||
def update_policy(policy, configuration):
|
|
||||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), policy['ID'])
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.put(url, headers=headers, json={
|
|
||||||
'Name': configuration.name, # should be equal at this point.
|
|
||||||
'Description': configuration.description,
|
|
||||||
'Rules': configuration.rules,
|
|
||||||
'Datacenters': configuration.valid_datacenters
|
|
||||||
}, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
updated_policy = response.json()
|
|
||||||
|
|
||||||
changed = (
|
|
||||||
policy.get('Rules', "") != updated_policy.get('Rules', "") or
|
|
||||||
policy.get('Description', "") != updated_policy.get('Description', "") or
|
|
||||||
policy.get('Datacenters', []) != updated_policy.get('Datacenters', [])
|
|
||||||
)
|
|
||||||
|
|
||||||
return Output(changed=changed, operation=UPDATE_OPERATION, policy=updated_policy)
|
|
||||||
|
|
||||||
|
|
||||||
def create_policy(configuration):
|
|
||||||
url = '%s/acl/policy' % get_consul_url(configuration)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.put(url, headers=headers, json={
|
|
||||||
'Name': configuration.name,
|
|
||||||
'Description': configuration.description,
|
|
||||||
'Rules': configuration.rules,
|
|
||||||
'Datacenters': configuration.valid_datacenters
|
|
||||||
}, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
created_policy = response.json()
|
|
||||||
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION, policy=created_policy)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_policy(configuration):
|
|
||||||
policies = get_policies(configuration)
|
|
||||||
|
|
||||||
if configuration.name in policies:
|
|
||||||
|
|
||||||
policy_id = policies[configuration.name]['ID']
|
|
||||||
policy = get_policy(policy_id, configuration)
|
|
||||||
|
|
||||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration),
|
|
||||||
policy['ID'])
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.delete(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
changed = False
|
|
||||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def get_policies(configuration):
|
|
||||||
url = '%s/acl/policies' % get_consul_url(configuration)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
policies = response.json()
|
|
||||||
existing_policies_mapped_by_name = dict(
|
|
||||||
(policy['Name'], policy) for policy in policies if policy['Name'] is not None)
|
|
||||||
return existing_policies_mapped_by_name
|
|
||||||
|
|
||||||
|
|
||||||
def get_policy(id, configuration):
|
|
||||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), id)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def set_policy(configuration):
|
|
||||||
policies = get_policies(configuration)
|
|
||||||
|
|
||||||
if configuration.name in policies:
|
|
||||||
index_policy_object = policies[configuration.name]
|
|
||||||
policy_id = policies[configuration.name]['ID']
|
|
||||||
rest_policy_object = get_policy(policy_id, configuration)
|
|
||||||
# merge dicts as some keys are only available in the partial policy
|
|
||||||
policy = index_policy_object.copy()
|
|
||||||
policy.update(rest_policy_object)
|
|
||||||
return update_policy(policy, configuration)
|
|
||||||
else:
|
|
||||||
return create_policy(configuration)
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration:
|
|
||||||
"""
|
|
||||||
Configuration for this module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, token=None, host=None, scheme=None, validate_certs=None, name=None, description=None, port=None,
|
|
||||||
rules=None, valid_datacenters=None, state=None):
|
|
||||||
self.token = token # type: str
|
|
||||||
self.host = host # type: str
|
|
||||||
self.scheme = scheme # type: str
|
|
||||||
self.validate_certs = validate_certs # type: bool
|
|
||||||
self.name = name # type: str
|
|
||||||
self.description = description # type: str
|
|
||||||
self.port = port # type: int
|
|
||||||
self.rules = rules # type: str
|
|
||||||
self.valid_datacenters = valid_datacenters # type: str
|
|
||||||
self.state = state # type: str
|
|
||||||
|
|
||||||
|
|
||||||
class Output:
|
|
||||||
"""
|
|
||||||
Output of an action of this module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, changed=None, operation=None, policy=None):
|
|
||||||
self.changed = changed # type: bool
|
|
||||||
self.operation = operation # type: str
|
|
||||||
self.policy = policy # type: dict
|
|
||||||
|
|
||||||
|
|
||||||
def check_dependencies():
|
|
||||||
"""
|
|
||||||
Checks that the required dependencies have been imported.
|
|
||||||
:exception ImportError: if it is detected that any of the required dependencies have not been imported
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not has_requests:
|
|
||||||
raise ImportError(
|
|
||||||
"requests required for this module. See https://pypi.org/project/requests/")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
module = AnsibleModule(
|
||||||
Main method.
|
_ARGUMENT_SPEC,
|
||||||
"""
|
supports_check_mode=True,
|
||||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=False)
|
|
||||||
|
|
||||||
try:
|
|
||||||
check_dependencies()
|
|
||||||
except ImportError as e:
|
|
||||||
module.fail_json(msg=str(e))
|
|
||||||
|
|
||||||
configuration = Configuration(
|
|
||||||
token=module.params.get(TOKEN_PARAMETER_NAME),
|
|
||||||
host=module.params.get(HOST_PARAMETER_NAME),
|
|
||||||
scheme=module.params.get(SCHEME_PARAMETER_NAME),
|
|
||||||
validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME),
|
|
||||||
name=module.params.get(NAME_PARAMETER_NAME),
|
|
||||||
description=module.params.get(DESCRIPTION_PARAMETER_NAME),
|
|
||||||
port=module.params.get(PORT_PARAMETER_NAME),
|
|
||||||
rules=module.params.get(RULES_PARAMETER_NAME),
|
|
||||||
valid_datacenters=module.params.get(VALID_DATACENTERS_PARAMETER_NAME),
|
|
||||||
state=module.params.get(STATE_PARAMETER_NAME),
|
|
||||||
)
|
)
|
||||||
|
consul_module = ConsulPolicyModule(module)
|
||||||
try:
|
consul_module.execute()
|
||||||
if configuration.state == PRESENT_STATE_VALUE:
|
|
||||||
output = set_policy(configuration)
|
|
||||||
else:
|
|
||||||
output = remove_policy(configuration)
|
|
||||||
except ConnectionError as e:
|
|
||||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
|
||||||
configuration.host, configuration.port, str(e)))
|
|
||||||
raise
|
|
||||||
|
|
||||||
return_values = dict(changed=output.changed, operation=output.operation, policy=output.policy)
|
|
||||||
module.exit_json(**return_values)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = """
|
||||||
module: consul_role
|
module: consul_role
|
||||||
short_description: Manipulate Consul roles
|
short_description: Manipulate Consul roles
|
||||||
version_added: 7.5.0
|
version_added: 7.5.0
|
||||||
@@ -19,12 +20,18 @@ description:
|
|||||||
author:
|
author:
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.token
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
support: full
|
support: full
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: none
|
support: partial
|
||||||
|
details:
|
||||||
|
- In check mode the diff will miss operational attributes.
|
||||||
|
version_added: 8.3.0
|
||||||
options:
|
options:
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
@@ -34,7 +41,6 @@ options:
|
|||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- whether the role should be present or absent.
|
- whether the role should be present or absent.
|
||||||
required: false
|
|
||||||
choices: ['present', 'absent']
|
choices: ['present', 'absent']
|
||||||
default: present
|
default: present
|
||||||
type: str
|
type: str
|
||||||
@@ -42,7 +48,6 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Description of the role.
|
- Description of the role.
|
||||||
- If not specified, the assigned description will not be changed.
|
- If not specified, the assigned description will not be changed.
|
||||||
required: false
|
|
||||||
type: str
|
type: str
|
||||||
policies:
|
policies:
|
||||||
type: list
|
type: list
|
||||||
@@ -51,7 +56,6 @@ options:
|
|||||||
- List of policies to attach to the role. Each policy is a dict.
|
- List of policies to attach to the role. Each policy is a dict.
|
||||||
- If the parameter is left blank, any policies currently assigned will not be changed.
|
- If the parameter is left blank, any policies currently assigned will not be changed.
|
||||||
- Any empty array (V([])) will clear any policies previously set.
|
- Any empty array (V([])) will clear any policies previously set.
|
||||||
required: false
|
|
||||||
suboptions:
|
suboptions:
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
@@ -63,6 +67,23 @@ options:
|
|||||||
- The ID of the policy to attach to this role; see M(community.general.consul_policy) for more info.
|
- The ID of the policy to attach to this role; see M(community.general.consul_policy) for more info.
|
||||||
- Either this or O(policies[].name) must be specified.
|
- Either this or O(policies[].name) must be specified.
|
||||||
type: str
|
type: str
|
||||||
|
templated_policies:
|
||||||
|
description:
|
||||||
|
- The list of templated policies that should be applied to the role.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
version_added: 8.3.0
|
||||||
|
suboptions:
|
||||||
|
template_name:
|
||||||
|
description:
|
||||||
|
- The templated policy name.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
template_variables:
|
||||||
|
description:
|
||||||
|
- The templated policy variables.
|
||||||
|
- Not all templated policies require variables.
|
||||||
|
type: dict
|
||||||
service_identities:
|
service_identities:
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
@@ -70,15 +91,18 @@ options:
|
|||||||
- List of service identities to attach to the role.
|
- List of service identities to attach to the role.
|
||||||
- If not specified, any service identities currently assigned will not be changed.
|
- If not specified, any service identities currently assigned will not be changed.
|
||||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||||
required: false
|
|
||||||
suboptions:
|
suboptions:
|
||||||
name:
|
service_name:
|
||||||
description:
|
description:
|
||||||
- The name of the node.
|
- The name of the node.
|
||||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||||
- May only contain lowercase alphanumeric characters as well as - and _.
|
- May only contain lowercase alphanumeric characters as well as - and _.
|
||||||
|
- This suboption has been renamed from O(service_identities[].name) to O(service_identities[].service_name)
|
||||||
|
in community.general 8.3.0. The old name can still be used.
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
|
aliases:
|
||||||
|
- name
|
||||||
datacenters:
|
datacenters:
|
||||||
description:
|
description:
|
||||||
- The datacenters the policies will be effective.
|
- The datacenters the policies will be effective.
|
||||||
@@ -87,7 +111,6 @@ options:
|
|||||||
- including those which do not yet exist but may in the future.
|
- including those which do not yet exist but may in the future.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
required: true
|
|
||||||
node_identities:
|
node_identities:
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
@@ -95,52 +118,25 @@ options:
|
|||||||
- List of node identities to attach to the role.
|
- List of node identities to attach to the role.
|
||||||
- If not specified, any node identities currently assigned will not be changed.
|
- If not specified, any node identities currently assigned will not be changed.
|
||||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||||
required: false
|
|
||||||
suboptions:
|
suboptions:
|
||||||
name:
|
node_name:
|
||||||
description:
|
description:
|
||||||
- The name of the node.
|
- The name of the node.
|
||||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||||
- May only contain lowercase alphanumeric characters as well as - and _.
|
- May only contain lowercase alphanumeric characters as well as - and _.
|
||||||
|
- This suboption has been renamed from O(node_identities[].name) to O(node_identities[].node_name)
|
||||||
|
in community.general 8.3.0. The old name can still be used.
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
|
aliases:
|
||||||
|
- name
|
||||||
datacenter:
|
datacenter:
|
||||||
description:
|
description:
|
||||||
- The nodes datacenter.
|
- The nodes datacenter.
|
||||||
- This will result in effective policy only being valid in this datacenter.
|
- This will result in effective policy only being valid in this datacenter.
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
host:
|
"""
|
||||||
description:
|
|
||||||
- Host of the consul agent, defaults to V(localhost).
|
|
||||||
required: false
|
|
||||||
default: localhost
|
|
||||||
type: str
|
|
||||||
port:
|
|
||||||
type: int
|
|
||||||
description:
|
|
||||||
- The port on which the consul agent is running.
|
|
||||||
required: false
|
|
||||||
default: 8500
|
|
||||||
scheme:
|
|
||||||
description:
|
|
||||||
- The protocol scheme on which the consul agent is running.
|
|
||||||
required: false
|
|
||||||
default: http
|
|
||||||
type: str
|
|
||||||
token:
|
|
||||||
description:
|
|
||||||
- A management token is required to manipulate the roles.
|
|
||||||
type: str
|
|
||||||
validate_certs:
|
|
||||||
type: bool
|
|
||||||
description:
|
|
||||||
- Whether to verify the TLS certificate of the consul agent.
|
|
||||||
required: false
|
|
||||||
default: true
|
|
||||||
requirements:
|
|
||||||
- requests
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Create a role with 2 policies
|
- name: Create a role with 2 policies
|
||||||
@@ -204,440 +200,81 @@ operation:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
|
||||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
get_consul_url, get_auth_headers, handle_consul_response_error)
|
AUTH_ARGUMENTS_SPEC,
|
||||||
import traceback
|
OPERATION_READ,
|
||||||
|
_ConsulModule,
|
||||||
REQUESTS_IMP_ERR = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
from requests.exceptions import ConnectionError
|
|
||||||
import requests
|
|
||||||
HAS_REQUESTS = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_REQUESTS = False
|
|
||||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
|
||||||
|
|
||||||
TOKEN_PARAMETER_NAME = "token"
|
|
||||||
HOST_PARAMETER_NAME = "host"
|
|
||||||
SCHEME_PARAMETER_NAME = "scheme"
|
|
||||||
VALIDATE_CERTS_PARAMETER_NAME = "validate_certs"
|
|
||||||
NAME_PARAMETER_NAME = "name"
|
|
||||||
DESCRIPTION_PARAMETER_NAME = "description"
|
|
||||||
PORT_PARAMETER_NAME = "port"
|
|
||||||
POLICIES_PARAMETER_NAME = "policies"
|
|
||||||
SERVICE_IDENTITIES_PARAMETER_NAME = "service_identities"
|
|
||||||
NODE_IDENTITIES_PARAMETER_NAME = "node_identities"
|
|
||||||
STATE_PARAMETER_NAME = "state"
|
|
||||||
|
|
||||||
PRESENT_STATE_VALUE = "present"
|
|
||||||
ABSENT_STATE_VALUE = "absent"
|
|
||||||
|
|
||||||
REMOVE_OPERATION = "remove"
|
|
||||||
UPDATE_OPERATION = "update"
|
|
||||||
CREATE_OPERATION = "create"
|
|
||||||
|
|
||||||
POLICY_RULE_SPEC = dict(
|
|
||||||
name=dict(type='str'),
|
|
||||||
id=dict(type='str'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NODE_ID_RULE_SPEC = dict(
|
|
||||||
name=dict(type='str', required=True),
|
class ConsulRoleModule(_ConsulModule):
|
||||||
datacenter=dict(type='str', required=True),
|
api_endpoint = "acl/role"
|
||||||
|
result_key = "role"
|
||||||
|
unique_identifier = "id"
|
||||||
|
|
||||||
|
def endpoint_url(self, operation, identifier=None):
|
||||||
|
if operation == OPERATION_READ:
|
||||||
|
return [self.api_endpoint, "name", self.params["name"]]
|
||||||
|
return super(ConsulRoleModule, self).endpoint_url(operation, identifier)
|
||||||
|
|
||||||
|
|
||||||
|
NAME_ID_SPEC = dict(
|
||||||
|
name=dict(type="str"),
|
||||||
|
id=dict(type="str"),
|
||||||
)
|
)
|
||||||
|
|
||||||
SERVICE_ID_RULE_SPEC = dict(
|
NODE_ID_SPEC = dict(
|
||||||
name=dict(type='str', required=True),
|
node_name=dict(type="str", required=True, aliases=["name"]),
|
||||||
datacenters=dict(type='list', elements='str', required=True),
|
datacenter=dict(type="str", required=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
SERVICE_ID_SPEC = dict(
|
||||||
|
service_name=dict(type="str", required=True, aliases=["name"]),
|
||||||
|
datacenters=dict(type="list", elements="str"),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEMPLATE_POLICY_SPEC = dict(
|
||||||
|
template_name=dict(type="str", required=True),
|
||||||
|
template_variables=dict(type="dict"),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ARGUMENT_SPEC = {
|
_ARGUMENT_SPEC = {
|
||||||
TOKEN_PARAMETER_NAME: dict(no_log=True),
|
"name": dict(type="str", required=True),
|
||||||
PORT_PARAMETER_NAME: dict(default=8500, type='int'),
|
"description": dict(type="str"),
|
||||||
HOST_PARAMETER_NAME: dict(default='localhost'),
|
"policies": dict(
|
||||||
SCHEME_PARAMETER_NAME: dict(default='http'),
|
type="list",
|
||||||
VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True),
|
elements="dict",
|
||||||
NAME_PARAMETER_NAME: dict(required=True),
|
options=NAME_ID_SPEC,
|
||||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=None),
|
mutually_exclusive=[("name", "id")],
|
||||||
POLICIES_PARAMETER_NAME: dict(type='list', elements='dict', options=POLICY_RULE_SPEC,
|
required_one_of=[("name", "id")],
|
||||||
mutually_exclusive=[('name', 'id')], required_one_of=[('name', 'id')], default=None),
|
),
|
||||||
SERVICE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=SERVICE_ID_RULE_SPEC, default=None),
|
"templated_policies": dict(
|
||||||
NODE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=NODE_ID_RULE_SPEC, default=None),
|
type="list",
|
||||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]),
|
elements="dict",
|
||||||
|
options=TEMPLATE_POLICY_SPEC,
|
||||||
|
),
|
||||||
|
"node_identities": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=NODE_ID_SPEC,
|
||||||
|
),
|
||||||
|
"service_identities": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=SERVICE_ID_SPEC,
|
||||||
|
),
|
||||||
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
}
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
def compare_consul_api_role_policy_objects(first, second):
|
|
||||||
# compare two lists of dictionaries, ignoring the ID element
|
|
||||||
for x in first:
|
|
||||||
x.pop('ID', None)
|
|
||||||
|
|
||||||
for x in second:
|
|
||||||
x.pop('ID', None)
|
|
||||||
|
|
||||||
return first == second
|
|
||||||
|
|
||||||
|
|
||||||
def update_role(role, configuration):
|
|
||||||
url = '%s/acl/role/%s' % (get_consul_url(configuration),
|
|
||||||
role['ID'])
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
|
|
||||||
update_role_data = {
|
|
||||||
'Name': configuration.name,
|
|
||||||
'Description': configuration.description,
|
|
||||||
}
|
|
||||||
|
|
||||||
# check if the user omitted the description, policies, service identities, or node identities
|
|
||||||
|
|
||||||
description_specified = configuration.description is not None
|
|
||||||
|
|
||||||
policy_specified = True
|
|
||||||
if len(configuration.policies) == 1 and configuration.policies[0] is None:
|
|
||||||
policy_specified = False
|
|
||||||
|
|
||||||
service_id_specified = True
|
|
||||||
if len(configuration.service_identities) == 1 and configuration.service_identities[0] is None:
|
|
||||||
service_id_specified = False
|
|
||||||
|
|
||||||
node_id_specified = True
|
|
||||||
if len(configuration.node_identities) == 1 and configuration.node_identities[0] is None:
|
|
||||||
node_id_specified = False
|
|
||||||
|
|
||||||
if description_specified:
|
|
||||||
update_role_data["Description"] = configuration.description
|
|
||||||
|
|
||||||
if policy_specified:
|
|
||||||
update_role_data["Policies"] = [x.to_dict() for x in configuration.policies]
|
|
||||||
|
|
||||||
if configuration.version >= ConsulVersion("1.5.0") and service_id_specified:
|
|
||||||
update_role_data["ServiceIdentities"] = [
|
|
||||||
x.to_dict() for x in configuration.service_identities]
|
|
||||||
|
|
||||||
if configuration.version >= ConsulVersion("1.8.0") and node_id_specified:
|
|
||||||
update_role_data["NodeIdentities"] = [
|
|
||||||
x.to_dict() for x in configuration.node_identities]
|
|
||||||
|
|
||||||
if configuration.check_mode:
|
|
||||||
description_changed = False
|
|
||||||
if description_specified:
|
|
||||||
description_changed = role.get('Description') != update_role_data["Description"]
|
|
||||||
else:
|
|
||||||
update_role_data["Description"] = role.get("Description")
|
|
||||||
|
|
||||||
policies_changed = False
|
|
||||||
if policy_specified:
|
|
||||||
policies_changed = not (
|
|
||||||
compare_consul_api_role_policy_objects(role.get('Policies', []), update_role_data.get('Policies', [])))
|
|
||||||
else:
|
|
||||||
if role.get('Policies') is not None:
|
|
||||||
update_role_data["Policies"] = role.get('Policies')
|
|
||||||
|
|
||||||
service_ids_changed = False
|
|
||||||
if service_id_specified:
|
|
||||||
service_ids_changed = role.get('ServiceIdentities') != update_role_data.get('ServiceIdentities')
|
|
||||||
else:
|
|
||||||
if role.get('ServiceIdentities') is not None:
|
|
||||||
update_role_data["ServiceIdentities"] = role.get('ServiceIdentities')
|
|
||||||
|
|
||||||
node_ids_changed = False
|
|
||||||
if node_id_specified:
|
|
||||||
node_ids_changed = role.get('NodeIdentities') != update_role_data.get('NodeIdentities')
|
|
||||||
else:
|
|
||||||
if role.get('NodeIdentities'):
|
|
||||||
update_role_data["NodeIdentities"] = role.get('NodeIdentities')
|
|
||||||
|
|
||||||
changed = (
|
|
||||||
description_changed or
|
|
||||||
policies_changed or
|
|
||||||
service_ids_changed or
|
|
||||||
node_ids_changed
|
|
||||||
)
|
|
||||||
return Output(changed=changed, operation=UPDATE_OPERATION, role=update_role_data)
|
|
||||||
else:
|
|
||||||
# if description, policies, service or node id are not specified; we need to get the existing value and apply it
|
|
||||||
if not description_specified and role.get('Description') is not None:
|
|
||||||
update_role_data["Description"] = role.get('Description')
|
|
||||||
|
|
||||||
if not policy_specified and role.get('Policies') is not None:
|
|
||||||
update_role_data["Policies"] = role.get('Policies')
|
|
||||||
|
|
||||||
if not service_id_specified and role.get('ServiceIdentities') is not None:
|
|
||||||
update_role_data["ServiceIdentities"] = role.get('ServiceIdentities')
|
|
||||||
|
|
||||||
if not node_id_specified and role.get('NodeIdentities') is not None:
|
|
||||||
update_role_data["NodeIdentities"] = role.get('NodeIdentities')
|
|
||||||
|
|
||||||
response = requests.put(url, headers=headers, json=update_role_data, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
resulting_role = response.json()
|
|
||||||
changed = (
|
|
||||||
role['Description'] != resulting_role['Description'] or
|
|
||||||
role.get('Policies', None) != resulting_role.get('Policies', None) or
|
|
||||||
role.get('ServiceIdentities', None) != resulting_role.get('ServiceIdentities', None) or
|
|
||||||
role.get('NodeIdentities', None) != resulting_role.get('NodeIdentities', None)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Output(changed=changed, operation=UPDATE_OPERATION, role=resulting_role)
|
|
||||||
|
|
||||||
|
|
||||||
def create_role(configuration):
|
|
||||||
url = '%s/acl/role' % get_consul_url(configuration)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
|
|
||||||
# check if the user omitted policies, service identities, or node identities
|
|
||||||
policy_specified = True
|
|
||||||
if len(configuration.policies) == 1 and configuration.policies[0] is None:
|
|
||||||
policy_specified = False
|
|
||||||
|
|
||||||
service_id_specified = True
|
|
||||||
if len(configuration.service_identities) == 1 and configuration.service_identities[0] is None:
|
|
||||||
service_id_specified = False
|
|
||||||
|
|
||||||
node_id_specified = True
|
|
||||||
if len(configuration.node_identities) == 1 and configuration.node_identities[0] is None:
|
|
||||||
node_id_specified = False
|
|
||||||
|
|
||||||
# get rid of None item so we can set an empty list for policies, service identities and node identities
|
|
||||||
if not policy_specified:
|
|
||||||
configuration.policies.pop()
|
|
||||||
|
|
||||||
if not service_id_specified:
|
|
||||||
configuration.service_identities.pop()
|
|
||||||
|
|
||||||
if not node_id_specified:
|
|
||||||
configuration.node_identities.pop()
|
|
||||||
|
|
||||||
create_role_data = {
|
|
||||||
'Name': configuration.name,
|
|
||||||
'Description': configuration.description,
|
|
||||||
'Policies': [x.to_dict() for x in configuration.policies],
|
|
||||||
}
|
|
||||||
if configuration.version >= ConsulVersion("1.5.0"):
|
|
||||||
create_role_data["ServiceIdentities"] = [x.to_dict() for x in configuration.service_identities]
|
|
||||||
|
|
||||||
if configuration.version >= ConsulVersion("1.8.0"):
|
|
||||||
create_role_data["NodeIdentities"] = [x.to_dict() for x in configuration.node_identities]
|
|
||||||
|
|
||||||
if not configuration.check_mode:
|
|
||||||
response = requests.put(url, headers=headers, json=create_role_data, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
resulting_role = response.json()
|
|
||||||
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION, role=resulting_role)
|
|
||||||
else:
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_role(configuration):
|
|
||||||
roles = get_roles(configuration)
|
|
||||||
|
|
||||||
if configuration.name in roles:
|
|
||||||
|
|
||||||
role_id = roles[configuration.name]['ID']
|
|
||||||
|
|
||||||
if not configuration.check_mode:
|
|
||||||
url = '%s/acl/role/%s' % (get_consul_url(configuration), role_id)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.delete(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
changed = False
|
|
||||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def get_roles(configuration):
|
|
||||||
url = '%s/acl/roles' % get_consul_url(configuration)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
roles = response.json()
|
|
||||||
existing_roles_mapped_by_id = dict((role['Name'], role) for role in roles if role['Name'] is not None)
|
|
||||||
return existing_roles_mapped_by_id
|
|
||||||
|
|
||||||
|
|
||||||
def get_consul_version(configuration):
|
|
||||||
url = '%s/agent/self' % get_consul_url(configuration)
|
|
||||||
headers = get_auth_headers(configuration)
|
|
||||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
config = response.json()["Config"]
|
|
||||||
return ConsulVersion(config["Version"])
|
|
||||||
|
|
||||||
|
|
||||||
def set_role(configuration):
|
|
||||||
roles = get_roles(configuration)
|
|
||||||
|
|
||||||
if configuration.name in roles:
|
|
||||||
role = roles[configuration.name]
|
|
||||||
return update_role(role, configuration)
|
|
||||||
else:
|
|
||||||
return create_role(configuration)
|
|
||||||
|
|
||||||
|
|
||||||
class ConsulVersion:
|
|
||||||
def __init__(self, version_string):
|
|
||||||
split = version_string.split('.')
|
|
||||||
self.major = split[0]
|
|
||||||
self.minor = split[1]
|
|
||||||
self.patch = split[2]
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
return int(self.major + self.minor +
|
|
||||||
self.patch) >= int(other.major + other.minor + other.patch)
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
return int(self.major + self.minor +
|
|
||||||
self.patch) <= int(other.major + other.minor + other.patch)
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceIdentity:
|
|
||||||
def __init__(self, input):
|
|
||||||
if not isinstance(input, dict) or 'name' not in input:
|
|
||||||
raise ValueError(
|
|
||||||
"Each element of service_identities must be a dict with the keys name and optionally datacenters")
|
|
||||||
self.name = input["name"]
|
|
||||||
self.datacenters = input["datacenters"] if "datacenters" in input else None
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"ServiceName": self.name,
|
|
||||||
"Datacenters": self.datacenters
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NodeIdentity:
|
|
||||||
def __init__(self, input):
|
|
||||||
if not isinstance(input, dict) or 'name' not in input:
|
|
||||||
raise ValueError(
|
|
||||||
"Each element of node_identities must be a dict with the keys name and optionally datacenter")
|
|
||||||
self.name = input["name"]
|
|
||||||
self.datacenter = input["datacenter"] if "datacenter" in input else None
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"NodeName": self.name,
|
|
||||||
"Datacenter": self.datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RoleLink:
|
|
||||||
def __init__(self, dict):
|
|
||||||
self.id = dict.get("id", None)
|
|
||||||
self.name = dict.get("name", None)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"ID": self.id,
|
|
||||||
"Name": self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PolicyLink:
|
|
||||||
def __init__(self, dict):
|
|
||||||
self.id = dict.get("id", None)
|
|
||||||
self.name = dict.get("name", None)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"ID": self.id,
|
|
||||||
"Name": self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration:
|
|
||||||
"""
|
|
||||||
Configuration for this module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, token=None, host=None, scheme=None, validate_certs=None, name=None, description=None, port=None,
|
|
||||||
policies=None, service_identities=None, node_identities=None, state=None, check_mode=None):
|
|
||||||
self.token = token # type: str
|
|
||||||
self.host = host # type: str
|
|
||||||
self.port = port # type: int
|
|
||||||
self.scheme = scheme # type: str
|
|
||||||
self.validate_certs = validate_certs # type: bool
|
|
||||||
self.name = name # type: str
|
|
||||||
self.description = description # type: str
|
|
||||||
if policies is not None:
|
|
||||||
self.policies = [PolicyLink(p) for p in policies] # type: list(PolicyLink)
|
|
||||||
else:
|
|
||||||
self.policies = [None]
|
|
||||||
if service_identities is not None:
|
|
||||||
self.service_identities = [ServiceIdentity(s) for s in service_identities] # type: list(ServiceIdentity)
|
|
||||||
else:
|
|
||||||
self.service_identities = [None]
|
|
||||||
if node_identities is not None:
|
|
||||||
self.node_identities = [NodeIdentity(n) for n in node_identities] # type: list(NodeIdentity)
|
|
||||||
else:
|
|
||||||
self.node_identities = [None]
|
|
||||||
self.state = state # type: str
|
|
||||||
self.check_mode = check_mode # type: bool
|
|
||||||
|
|
||||||
|
|
||||||
class Output:
|
|
||||||
"""
|
|
||||||
Output of an action of this module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, changed=None, operation=None, role=None):
|
|
||||||
self.changed = changed # type: bool
|
|
||||||
self.operation = operation # type: str
|
|
||||||
self.role = role # type: dict
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
module = AnsibleModule(
|
||||||
Main method.
|
_ARGUMENT_SPEC,
|
||||||
"""
|
supports_check_mode=True,
|
||||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=True)
|
)
|
||||||
|
consul_module = ConsulRoleModule(module)
|
||||||
if not HAS_REQUESTS:
|
consul_module.execute()
|
||||||
module.fail_json(msg=missing_required_lib("requests"),
|
|
||||||
exception=REQUESTS_IMP_ERR)
|
|
||||||
|
|
||||||
try:
|
|
||||||
configuration = Configuration(
|
|
||||||
token=module.params.get(TOKEN_PARAMETER_NAME),
|
|
||||||
host=module.params.get(HOST_PARAMETER_NAME),
|
|
||||||
port=module.params.get(PORT_PARAMETER_NAME),
|
|
||||||
scheme=module.params.get(SCHEME_PARAMETER_NAME),
|
|
||||||
validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME),
|
|
||||||
name=module.params.get(NAME_PARAMETER_NAME),
|
|
||||||
description=module.params.get(DESCRIPTION_PARAMETER_NAME),
|
|
||||||
policies=module.params.get(POLICIES_PARAMETER_NAME),
|
|
||||||
service_identities=module.params.get(SERVICE_IDENTITIES_PARAMETER_NAME),
|
|
||||||
node_identities=module.params.get(NODE_IDENTITIES_PARAMETER_NAME),
|
|
||||||
state=module.params.get(STATE_PARAMETER_NAME),
|
|
||||||
check_mode=module.check_mode
|
|
||||||
|
|
||||||
)
|
|
||||||
except ValueError as err:
|
|
||||||
module.fail_json(msg='Configuration error: %s' % str(err))
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
version = get_consul_version(configuration)
|
|
||||||
configuration.version = version
|
|
||||||
|
|
||||||
if configuration.state == PRESENT_STATE_VALUE:
|
|
||||||
output = set_role(configuration)
|
|
||||||
else:
|
|
||||||
output = remove_role(configuration)
|
|
||||||
except ConnectionError as e:
|
|
||||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
|
||||||
configuration.host, configuration.port, str(e)))
|
|
||||||
raise
|
|
||||||
|
|
||||||
return_values = dict(changed=output.changed, operation=output.operation, role=output.role)
|
|
||||||
module.exit_json(**return_values)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ description:
|
|||||||
cluster. These sessions can then be used in conjunction with key value pairs
|
cluster. These sessions can then be used in conjunction with key value pairs
|
||||||
to implement distributed locks. In depth documentation for working with
|
to implement distributed locks. In depth documentation for working with
|
||||||
sessions can be found at http://www.consul.io/docs/internals/sessions.html
|
sessions can be found at http://www.consul.io/docs/internals/sessions.html
|
||||||
requirements:
|
|
||||||
- requests
|
|
||||||
author:
|
author:
|
||||||
- Steve Gargan (@sgargan)
|
- Steve Gargan (@sgargan)
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
|
- community.general.consul.token
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
@@ -76,26 +77,6 @@ options:
|
|||||||
the associated lock delay has expired.
|
the associated lock delay has expired.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
host:
|
|
||||||
description:
|
|
||||||
- The host of the consul agent defaults to localhost.
|
|
||||||
type: str
|
|
||||||
default: localhost
|
|
||||||
port:
|
|
||||||
description:
|
|
||||||
- The port on which the consul agent is running.
|
|
||||||
type: int
|
|
||||||
default: 8500
|
|
||||||
scheme:
|
|
||||||
description:
|
|
||||||
- The protocol scheme on which the consul agent is running.
|
|
||||||
type: str
|
|
||||||
default: http
|
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- Whether to verify the TLS certificate of the consul agent.
|
|
||||||
type: bool
|
|
||||||
default: true
|
|
||||||
behavior:
|
behavior:
|
||||||
description:
|
description:
|
||||||
- The optional behavior that can be attached to the session when it
|
- The optional behavior that can be attached to the session when it
|
||||||
@@ -109,10 +90,6 @@ options:
|
|||||||
type: int
|
type: int
|
||||||
version_added: 5.4.0
|
version_added: 5.4.0
|
||||||
token:
|
token:
|
||||||
description:
|
|
||||||
- The token key identifying an ACL rule set that controls access to
|
|
||||||
the key value pair.
|
|
||||||
type: str
|
|
||||||
version_added: 5.6.0
|
version_added: 5.6.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -148,95 +125,49 @@ EXAMPLES = '''
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
try:
|
AUTH_ARGUMENTS_SPEC, _ConsulModule
|
||||||
import requests
|
)
|
||||||
from requests.exceptions import ConnectionError
|
|
||||||
has_requests = True
|
|
||||||
except ImportError:
|
|
||||||
has_requests = False
|
|
||||||
|
|
||||||
|
|
||||||
def execute(module):
|
def execute(module, consul_module):
|
||||||
|
|
||||||
state = module.params.get('state')
|
state = module.params.get('state')
|
||||||
|
|
||||||
if state in ['info', 'list', 'node']:
|
if state in ['info', 'list', 'node']:
|
||||||
lookup_sessions(module)
|
lookup_sessions(module, consul_module)
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
update_session(module)
|
update_session(module, consul_module)
|
||||||
else:
|
else:
|
||||||
remove_session(module)
|
remove_session(module, consul_module)
|
||||||
|
|
||||||
|
|
||||||
class RequestError(Exception):
|
def list_sessions(consul_module, datacenter):
|
||||||
pass
|
return consul_module.get(
|
||||||
|
'session/list',
|
||||||
|
params={'dc': datacenter})
|
||||||
|
|
||||||
|
|
||||||
def handle_consul_response_error(response):
|
def list_sessions_for_node(consul_module, node, datacenter):
|
||||||
if 400 <= response.status_code < 600:
|
return consul_module.get(
|
||||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
('session', 'node', node),
|
||||||
|
params={'dc': datacenter})
|
||||||
|
|
||||||
|
|
||||||
def get_consul_url(module):
|
def get_session_info(consul_module, session_id, datacenter):
|
||||||
return '%s://%s:%s/v1' % (module.params.get('scheme'),
|
return consul_module.get(
|
||||||
module.params.get('host'), module.params.get('port'))
|
('session', 'info', session_id),
|
||||||
|
params={'dc': datacenter})
|
||||||
|
|
||||||
|
|
||||||
def get_auth_headers(module):
|
def lookup_sessions(module, consul_module):
|
||||||
if 'token' in module.params and module.params.get('token') is not None:
|
|
||||||
return {'X-Consul-Token': module.params.get('token')}
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def list_sessions(module, datacenter):
|
|
||||||
url = '%s/session/list' % get_consul_url(module)
|
|
||||||
headers = get_auth_headers(module)
|
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
params={
|
|
||||||
'dc': datacenter},
|
|
||||||
verify=module.params.get('validate_certs'))
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def list_sessions_for_node(module, node, datacenter):
|
|
||||||
url = '%s/session/node/%s' % (get_consul_url(module), node)
|
|
||||||
headers = get_auth_headers(module)
|
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
params={
|
|
||||||
'dc': datacenter},
|
|
||||||
verify=module.params.get('validate_certs'))
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def get_session_info(module, session_id, datacenter):
|
|
||||||
url = '%s/session/info/%s' % (get_consul_url(module), session_id)
|
|
||||||
headers = get_auth_headers(module)
|
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
params={
|
|
||||||
'dc': datacenter},
|
|
||||||
verify=module.params.get('validate_certs'))
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_sessions(module):
|
|
||||||
|
|
||||||
datacenter = module.params.get('datacenter')
|
datacenter = module.params.get('datacenter')
|
||||||
|
|
||||||
state = module.params.get('state')
|
state = module.params.get('state')
|
||||||
try:
|
try:
|
||||||
if state == 'list':
|
if state == 'list':
|
||||||
sessions_list = list_sessions(module, datacenter)
|
sessions_list = list_sessions(consul_module, datacenter)
|
||||||
# Ditch the index, this can be grabbed from the results
|
# Ditch the index, this can be grabbed from the results
|
||||||
if sessions_list and len(sessions_list) >= 2:
|
if sessions_list and len(sessions_list) >= 2:
|
||||||
sessions_list = sessions_list[1]
|
sessions_list = sessions_list[1]
|
||||||
@@ -244,14 +175,14 @@ def lookup_sessions(module):
|
|||||||
sessions=sessions_list)
|
sessions=sessions_list)
|
||||||
elif state == 'node':
|
elif state == 'node':
|
||||||
node = module.params.get('node')
|
node = module.params.get('node')
|
||||||
sessions = list_sessions_for_node(module, node, datacenter)
|
sessions = list_sessions_for_node(consul_module, node, datacenter)
|
||||||
module.exit_json(changed=True,
|
module.exit_json(changed=True,
|
||||||
node=node,
|
node=node,
|
||||||
sessions=sessions)
|
sessions=sessions)
|
||||||
elif state == 'info':
|
elif state == 'info':
|
||||||
session_id = module.params.get('id')
|
session_id = module.params.get('id')
|
||||||
|
|
||||||
session_by_id = get_session_info(module, session_id, datacenter)
|
session_by_id = get_session_info(consul_module, session_id, datacenter)
|
||||||
module.exit_json(changed=True,
|
module.exit_json(changed=True,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
sessions=session_by_id)
|
sessions=session_by_id)
|
||||||
@@ -260,10 +191,8 @@ def lookup_sessions(module):
|
|||||||
module.fail_json(msg="Could not retrieve session info %s" % e)
|
module.fail_json(msg="Could not retrieve session info %s" % e)
|
||||||
|
|
||||||
|
|
||||||
def create_session(module, name, behavior, ttl, node,
|
def create_session(consul_module, name, behavior, ttl, node,
|
||||||
lock_delay, datacenter, checks):
|
lock_delay, datacenter, checks):
|
||||||
url = '%s/session/create' % get_consul_url(module)
|
|
||||||
headers = get_auth_headers(module)
|
|
||||||
create_data = {
|
create_data = {
|
||||||
"LockDelay": lock_delay,
|
"LockDelay": lock_delay,
|
||||||
"Node": node,
|
"Node": node,
|
||||||
@@ -273,19 +202,15 @@ def create_session(module, name, behavior, ttl, node,
|
|||||||
}
|
}
|
||||||
if ttl is not None:
|
if ttl is not None:
|
||||||
create_data["TTL"] = "%ss" % str(ttl) # TTL is in seconds
|
create_data["TTL"] = "%ss" % str(ttl) # TTL is in seconds
|
||||||
response = requests.put(
|
create_session_response_dict = consul_module.put(
|
||||||
url,
|
'session/create',
|
||||||
headers=headers,
|
|
||||||
params={
|
params={
|
||||||
'dc': datacenter},
|
'dc': datacenter},
|
||||||
json=create_data,
|
data=create_data)
|
||||||
verify=module.params.get('validate_certs'))
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
create_session_response_dict = response.json()
|
|
||||||
return create_session_response_dict["ID"]
|
return create_session_response_dict["ID"]
|
||||||
|
|
||||||
|
|
||||||
def update_session(module):
|
def update_session(module, consul_module):
|
||||||
|
|
||||||
name = module.params.get('name')
|
name = module.params.get('name')
|
||||||
delay = module.params.get('delay')
|
delay = module.params.get('delay')
|
||||||
@@ -296,7 +221,7 @@ def update_session(module):
|
|||||||
ttl = module.params.get('ttl')
|
ttl = module.params.get('ttl')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session = create_session(module,
|
session = create_session(consul_module,
|
||||||
name=name,
|
name=name,
|
||||||
behavior=behavior,
|
behavior=behavior,
|
||||||
ttl=ttl,
|
ttl=ttl,
|
||||||
@@ -317,22 +242,15 @@ def update_session(module):
|
|||||||
module.fail_json(msg="Could not create/update session %s" % e)
|
module.fail_json(msg="Could not create/update session %s" % e)
|
||||||
|
|
||||||
|
|
||||||
def destroy_session(module, session_id):
|
def destroy_session(consul_module, session_id):
|
||||||
url = '%s/session/destroy/%s' % (get_consul_url(module), session_id)
|
return consul_module.put(('session', 'destroy', session_id))
|
||||||
headers = get_auth_headers(module)
|
|
||||||
response = requests.put(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
verify=module.params.get('validate_certs'))
|
|
||||||
handle_consul_response_error(response)
|
|
||||||
return response.content == "true"
|
|
||||||
|
|
||||||
|
|
||||||
def remove_session(module):
|
def remove_session(module, consul_module):
|
||||||
session_id = module.params.get('id')
|
session_id = module.params.get('id')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
destroy_session(module, session_id)
|
destroy_session(consul_module, session_id)
|
||||||
|
|
||||||
module.exit_json(changed=True,
|
module.exit_json(changed=True,
|
||||||
session_id=session_id)
|
session_id=session_id)
|
||||||
@@ -341,12 +259,6 @@ def remove_session(module):
|
|||||||
session_id, e))
|
session_id, e))
|
||||||
|
|
||||||
|
|
||||||
def test_dependencies(module):
|
|
||||||
if not has_requests:
|
|
||||||
raise ImportError(
|
|
||||||
"requests required for this module. See https://pypi.org/project/requests/")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
checks=dict(type='list', elements='str'),
|
checks=dict(type='list', elements='str'),
|
||||||
@@ -358,10 +270,6 @@ def main():
|
|||||||
'release',
|
'release',
|
||||||
'delete']),
|
'delete']),
|
||||||
ttl=dict(type='int'),
|
ttl=dict(type='int'),
|
||||||
host=dict(type='str', default='localhost'),
|
|
||||||
port=dict(type='int', default=8500),
|
|
||||||
scheme=dict(type='str', default='http'),
|
|
||||||
validate_certs=dict(type='bool', default=True),
|
|
||||||
id=dict(type='str'),
|
id=dict(type='str'),
|
||||||
name=dict(type='str'),
|
name=dict(type='str'),
|
||||||
node=dict(type='str'),
|
node=dict(type='str'),
|
||||||
@@ -375,7 +283,7 @@ def main():
|
|||||||
'node',
|
'node',
|
||||||
'present']),
|
'present']),
|
||||||
datacenter=dict(type='str'),
|
datacenter=dict(type='str'),
|
||||||
token=dict(type='str', no_log=True),
|
**AUTH_ARGUMENTS_SPEC
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
@@ -387,14 +295,10 @@ def main():
|
|||||||
],
|
],
|
||||||
supports_check_mode=False
|
supports_check_mode=False
|
||||||
)
|
)
|
||||||
|
consul_module = _ConsulModule(module)
|
||||||
test_dependencies(module)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
execute(module)
|
execute(module, consul_module)
|
||||||
except ConnectionError as e:
|
|
||||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
|
||||||
module.params.get('host'), module.params.get('port'), e))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg=str(e))
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
|||||||
325
plugins/modules/consul_token.py
Normal file
325
plugins/modules/consul_token.py
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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 = """
|
||||||
|
module: consul_token
|
||||||
|
short_description: Manipulate Consul tokens
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- Allows the addition, modification and deletion of tokens in a consul
|
||||||
|
cluster via the agent. For more details on using and configuring ACLs,
|
||||||
|
see U(https://www.consul.io/docs/guides/acl.html).
|
||||||
|
author:
|
||||||
|
- Florian Apolloner (@apollo13)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.consul
|
||||||
|
- community.general.consul.token
|
||||||
|
- community.general.consul.actiongroup_consul
|
||||||
|
- community.general.attributes
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: partial
|
||||||
|
details:
|
||||||
|
- In check mode the diff will miss operational attributes.
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Whether the token should be present or absent.
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
accessor_id:
|
||||||
|
description:
|
||||||
|
- Specifies a UUID to use as the token's Accessor ID.
|
||||||
|
If not specified a UUID will be generated for this field.
|
||||||
|
type: str
|
||||||
|
secret_id:
|
||||||
|
description:
|
||||||
|
- Specifies a UUID to use as the token's Secret ID.
|
||||||
|
If not specified a UUID will be generated for this field.
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Free form human readable description of the token.
|
||||||
|
type: str
|
||||||
|
policies:
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
description:
|
||||||
|
- List of policies to attach to the token. Each policy is a dict.
|
||||||
|
- If the parameter is left blank, any policies currently assigned will not be changed.
|
||||||
|
- Any empty array (V([])) will clear any policies previously set.
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of the policy to attach to this token; see M(community.general.consul_policy) for more info.
|
||||||
|
- Either this or O(policies[].id) must be specified.
|
||||||
|
type: str
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- The ID of the policy to attach to this token; see M(community.general.consul_policy) for more info.
|
||||||
|
- Either this or O(policies[].name) must be specified.
|
||||||
|
type: str
|
||||||
|
roles:
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
description:
|
||||||
|
- List of roles to attach to the token. Each role is a dict.
|
||||||
|
- If the parameter is left blank, any roles currently assigned will not be changed.
|
||||||
|
- Any empty array (V([])) will clear any roles previously set.
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of the role to attach to this token; see M(community.general.consul_role) for more info.
|
||||||
|
- Either this or O(roles[].id) must be specified.
|
||||||
|
type: str
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- The ID of the role to attach to this token; see M(community.general.consul_role) for more info.
|
||||||
|
- Either this or O(roles[].name) must be specified.
|
||||||
|
type: str
|
||||||
|
templated_policies:
|
||||||
|
description:
|
||||||
|
- The list of templated policies that should be applied to the role.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
suboptions:
|
||||||
|
template_name:
|
||||||
|
description:
|
||||||
|
- The templated policy name.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
template_variables:
|
||||||
|
description:
|
||||||
|
- The templated policy variables.
|
||||||
|
- Not all templated policies require variables.
|
||||||
|
type: dict
|
||||||
|
service_identities:
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
description:
|
||||||
|
- List of service identities to attach to the token.
|
||||||
|
- If not specified, any service identities currently assigned will not be changed.
|
||||||
|
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||||
|
suboptions:
|
||||||
|
service_name:
|
||||||
|
description:
|
||||||
|
- The name of the service.
|
||||||
|
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||||
|
- May only contain lowercase alphanumeric characters as well as V(-) and V(_).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
datacenters:
|
||||||
|
description:
|
||||||
|
- The datacenters the token will be effective.
|
||||||
|
- If an empty array (V([])) is specified, the token will valid in all datacenters.
|
||||||
|
- including those which do not yet exist but may in the future.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
node_identities:
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
description:
|
||||||
|
- List of node identities to attach to the token.
|
||||||
|
- If not specified, any node identities currently assigned will not be changed.
|
||||||
|
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||||
|
suboptions:
|
||||||
|
node_name:
|
||||||
|
description:
|
||||||
|
- The name of the node.
|
||||||
|
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||||
|
- May only contain lowercase alphanumeric characters as well as V(-) and V(_).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
datacenter:
|
||||||
|
description:
|
||||||
|
- The nodes datacenter.
|
||||||
|
- This will result in effective token only being valid in this datacenter.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
local:
|
||||||
|
description:
|
||||||
|
- If true, indicates that the token should not be replicated globally
|
||||||
|
and instead be local to the current datacenter.
|
||||||
|
type: bool
|
||||||
|
expiration_ttl:
|
||||||
|
description:
|
||||||
|
- This is a convenience field and if set will initialize the C(expiration_time).
|
||||||
|
Can be specified in the form of V(60s) or V(5m) (that is, 60 seconds or 5 minutes,
|
||||||
|
respectively). Ingored when the token is updated!
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Create / Update a token by accessor_id
|
||||||
|
community.general.consul_token:
|
||||||
|
state: present
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
token: 8adddd91-0bd6-d41d-ae1a-3b49cfa9a0e8
|
||||||
|
roles:
|
||||||
|
- name: role1
|
||||||
|
- name: role2
|
||||||
|
service_identities:
|
||||||
|
- service_name: service1
|
||||||
|
datacenters: [dc1, dc2]
|
||||||
|
node_identities:
|
||||||
|
- node_name: node1
|
||||||
|
datacenter: dc1
|
||||||
|
expiration_ttl: 50m
|
||||||
|
|
||||||
|
- name: Delete a token
|
||||||
|
community.general.consul_token:
|
||||||
|
state: absent
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
token: 8adddd91-0bd6-d41d-ae1a-3b49cfa9a0e8
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
token:
|
||||||
|
description: The token as returned by the consul HTTP API.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
AccessorID: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
CreateIndex: 632
|
||||||
|
CreateTime: "2024-01-14T21:53:01.402749174+01:00"
|
||||||
|
Description: Testing
|
||||||
|
Hash: rj5PeDHddHslkpW7Ij4OD6N4bbSXiecXFmiw2SYXg2A=
|
||||||
|
Local: false
|
||||||
|
ModifyIndex: 633
|
||||||
|
SecretID: bd380fba-da17-7cee-8576-8d6427c6c930
|
||||||
|
ServiceIdentities: [{"ServiceName": "test"}]
|
||||||
|
operation:
|
||||||
|
description: The operation performed.
|
||||||
|
returned: changed
|
||||||
|
type: str
|
||||||
|
sample: update
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
_ConsulModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_link_obj(api_obj, module_obj, key):
|
||||||
|
api_objs = api_obj.get(key)
|
||||||
|
module_objs = module_obj.get(key)
|
||||||
|
if api_objs is None or module_objs is None:
|
||||||
|
return
|
||||||
|
name_to_id = {i["Name"]: i["ID"] for i in api_objs}
|
||||||
|
id_to_name = {i["ID"]: i["Name"] for i in api_objs}
|
||||||
|
|
||||||
|
for obj in module_objs:
|
||||||
|
identifier = obj.get("ID")
|
||||||
|
name = obj.get("Name)")
|
||||||
|
if identifier and not name and identifier in id_to_name:
|
||||||
|
obj["Name"] = id_to_name[identifier]
|
||||||
|
if not identifier and name and name in name_to_id:
|
||||||
|
obj["ID"] = name_to_id[name]
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulTokenModule(_ConsulModule):
|
||||||
|
api_endpoint = "acl/token"
|
||||||
|
result_key = "token"
|
||||||
|
unique_identifier = "accessor_id"
|
||||||
|
|
||||||
|
create_only_fields = {"expiration_ttl"}
|
||||||
|
|
||||||
|
def needs_update(self, api_obj, module_obj):
|
||||||
|
# SecretID is usually not supplied
|
||||||
|
if "SecretID" not in module_obj and "SecretID" in api_obj:
|
||||||
|
del api_obj["SecretID"]
|
||||||
|
normalize_link_obj(api_obj, module_obj, "Roles")
|
||||||
|
normalize_link_obj(api_obj, module_obj, "Policies")
|
||||||
|
# ExpirationTTL is only supported on create, not for update
|
||||||
|
# it writes to ExpirationTime, so we need to remove that as well
|
||||||
|
if "ExpirationTTL" in module_obj:
|
||||||
|
del module_obj["ExpirationTTL"]
|
||||||
|
return super(ConsulTokenModule, self).needs_update(api_obj, module_obj)
|
||||||
|
|
||||||
|
|
||||||
|
NAME_ID_SPEC = dict(
|
||||||
|
name=dict(type="str"),
|
||||||
|
id=dict(type="str"),
|
||||||
|
)
|
||||||
|
|
||||||
|
NODE_ID_SPEC = dict(
|
||||||
|
node_name=dict(type="str", required=True),
|
||||||
|
datacenter=dict(type="str", required=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
SERVICE_ID_SPEC = dict(
|
||||||
|
service_name=dict(type="str", required=True),
|
||||||
|
datacenters=dict(type="list", elements="str"),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEMPLATE_POLICY_SPEC = dict(
|
||||||
|
template_name=dict(type="str", required=True),
|
||||||
|
template_variables=dict(type="dict"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_ARGUMENT_SPEC = {
|
||||||
|
"description": dict(),
|
||||||
|
"accessor_id": dict(),
|
||||||
|
"secret_id": dict(no_log=True),
|
||||||
|
"roles": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=NAME_ID_SPEC,
|
||||||
|
mutually_exclusive=[("name", "id")],
|
||||||
|
required_one_of=[("name", "id")],
|
||||||
|
),
|
||||||
|
"policies": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=NAME_ID_SPEC,
|
||||||
|
mutually_exclusive=[("name", "id")],
|
||||||
|
required_one_of=[("name", "id")],
|
||||||
|
),
|
||||||
|
"templated_policies": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=TEMPLATE_POLICY_SPEC,
|
||||||
|
),
|
||||||
|
"node_identities": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=NODE_ID_SPEC,
|
||||||
|
),
|
||||||
|
"service_identities": dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=SERVICE_ID_SPEC,
|
||||||
|
),
|
||||||
|
"local": dict(type="bool"),
|
||||||
|
"expiration_ttl": dict(type="str"),
|
||||||
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
|
}
|
||||||
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
_ARGUMENT_SPEC,
|
||||||
|
required_if=[("state", "absent", ["accessor_id"])],
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
consul_module = ConsulTokenModule(module)
|
||||||
|
consul_module.execute()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
500
plugins/modules/gitlab_label.py
Normal file
500
plugins/modules/gitlab_label.py
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2023, Gabriele Pongelli (gabriele.pongelli@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
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
module: gitlab_label
|
||||||
|
short_description: Creates/updates/deletes GitLab Labels belonging to project or group.
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- When a label does not exist, it will be created.
|
||||||
|
- When a label does exist, its value will be updated when the values are different.
|
||||||
|
- Labels can be purged.
|
||||||
|
author:
|
||||||
|
- "Gabriele Pongelli (@gpongelli)"
|
||||||
|
requirements:
|
||||||
|
- python-gitlab python module
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.auth_basic
|
||||||
|
- community.general.gitlab
|
||||||
|
- community.general.attributes
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Create or delete project or group label.
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
choices: ["present", "absent"]
|
||||||
|
purge:
|
||||||
|
description:
|
||||||
|
- When set to V(true), delete all labels which are not mentioned in the task.
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
required: false
|
||||||
|
project:
|
||||||
|
description:
|
||||||
|
- The path and name of the project. Either this or O(group) is required.
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
|
group:
|
||||||
|
description:
|
||||||
|
- The path of the group. Either this or O(project) is required.
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
|
labels:
|
||||||
|
description:
|
||||||
|
- A list of dictionaries that represents gitlab project's or group's labels.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
required: false
|
||||||
|
default: []
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of the label.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
color:
|
||||||
|
description:
|
||||||
|
- The color of the label.
|
||||||
|
- Required when O(state=present).
|
||||||
|
type: str
|
||||||
|
priority:
|
||||||
|
description:
|
||||||
|
- Integer value to give priority to the label.
|
||||||
|
type: int
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Label's description.
|
||||||
|
type: str
|
||||||
|
default: null
|
||||||
|
new_name:
|
||||||
|
description:
|
||||||
|
- Optional field to change label's name.
|
||||||
|
type: str
|
||||||
|
default: null
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# same project's task can be executed for group
|
||||||
|
- name: Create one Label
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
color: "#123456"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create many group labels
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
group: "group1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
color: "#123456"
|
||||||
|
description: this is a label
|
||||||
|
priority: 20
|
||||||
|
- name: label_two
|
||||||
|
color: "#554422"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create many project labels
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
color: "#123456"
|
||||||
|
description: this is a label
|
||||||
|
priority: 20
|
||||||
|
- name: label_two
|
||||||
|
color: "#554422"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Set or update some labels
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
color: "#224488"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add label in check mode
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
color: "#224488"
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Delete Label
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Change Label name
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
labels:
|
||||||
|
- name: label_one
|
||||||
|
new_name: label_two
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Purge all labels
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Delete many labels
|
||||||
|
community.general.gitlab_label:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
state: absent
|
||||||
|
labels:
|
||||||
|
- name: label-abc123
|
||||||
|
- name: label-two
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
labels:
|
||||||
|
description: Four lists of the labels which were added, updated, removed or exist.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
added:
|
||||||
|
description: A list of labels which were created.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['abcd', 'label-one']
|
||||||
|
untouched:
|
||||||
|
description: A list of labels which exist.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-label']
|
||||||
|
removed:
|
||||||
|
description: A list of labels which were deleted.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-label']
|
||||||
|
updated:
|
||||||
|
description: A list pre-existing labels whose values have been set.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-label']
|
||||||
|
labels_obj:
|
||||||
|
description: API object.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.api import basic_auth_argument_spec
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||||
|
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project, gitlab
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GitlabLabels(object):
|
||||||
|
|
||||||
|
def __init__(self, module, gitlab_instance, group_id, project_id):
|
||||||
|
self._gitlab = gitlab_instance
|
||||||
|
self.gitlab_object = group_id if group_id else project_id
|
||||||
|
self.is_group_label = True if group_id else False
|
||||||
|
self._module = module
|
||||||
|
|
||||||
|
def list_all_labels(self):
|
||||||
|
page_nb = 1
|
||||||
|
labels = []
|
||||||
|
vars_page = self.gitlab_object.labels.list(page=page_nb)
|
||||||
|
while len(vars_page) > 0:
|
||||||
|
labels += vars_page
|
||||||
|
page_nb += 1
|
||||||
|
vars_page = self.gitlab_object.labels.list(page=page_nb)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def create_label(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
var = {
|
||||||
|
"name": var_obj.get('name'),
|
||||||
|
"color": var_obj.get('color'),
|
||||||
|
}
|
||||||
|
|
||||||
|
if var_obj.get('description') is not None:
|
||||||
|
var["description"] = var_obj.get('description')
|
||||||
|
|
||||||
|
if var_obj.get('priority') is not None:
|
||||||
|
var["priority"] = var_obj.get('priority')
|
||||||
|
|
||||||
|
_obj = self.gitlab_object.labels.create(var)
|
||||||
|
return True, _obj.asdict()
|
||||||
|
|
||||||
|
def update_label(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
_label = self.gitlab_object.labels.get(var_obj.get('name'))
|
||||||
|
|
||||||
|
if var_obj.get('new_name') is not None:
|
||||||
|
_label.new_name = var_obj.get('new_name')
|
||||||
|
|
||||||
|
if var_obj.get('description') is not None:
|
||||||
|
_label.description = var_obj.get('description')
|
||||||
|
if var_obj.get('priority') is not None:
|
||||||
|
_label.priority = var_obj.get('priority')
|
||||||
|
|
||||||
|
# save returns None
|
||||||
|
_label.save()
|
||||||
|
return True, _label.asdict()
|
||||||
|
|
||||||
|
def delete_label(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
_label = self.gitlab_object.labels.get(var_obj.get('name'))
|
||||||
|
# delete returns None
|
||||||
|
_label.delete()
|
||||||
|
return True, _label.asdict()
|
||||||
|
|
||||||
|
|
||||||
|
def compare(requested_labels, existing_labels, state):
|
||||||
|
# we need to do this, because it was determined in a previous version - more or less buggy
|
||||||
|
# basically it is not necessary and might result in more/other bugs!
|
||||||
|
# but it is required and only relevant for check mode!!
|
||||||
|
# logic represents state 'present' when not purge. all other can be derived from that
|
||||||
|
# untouched => equal in both
|
||||||
|
# updated => name and scope are equal
|
||||||
|
# added => name and scope does not exist
|
||||||
|
untouched = list()
|
||||||
|
updated = list()
|
||||||
|
added = list()
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
_existing_labels = list()
|
||||||
|
for item in existing_labels:
|
||||||
|
_existing_labels.append({'name': item.get('name')})
|
||||||
|
|
||||||
|
for var in requested_labels:
|
||||||
|
if var in existing_labels:
|
||||||
|
untouched.append(var)
|
||||||
|
else:
|
||||||
|
compare_item = {'name': var.get('name')}
|
||||||
|
if compare_item in _existing_labels:
|
||||||
|
updated.append(var)
|
||||||
|
else:
|
||||||
|
added.append(var)
|
||||||
|
|
||||||
|
return untouched, updated, added
|
||||||
|
|
||||||
|
|
||||||
|
def native_python_main(this_gitlab, purge, requested_labels, state, module):
|
||||||
|
change = False
|
||||||
|
return_value = dict(added=[], updated=[], removed=[], untouched=[])
|
||||||
|
return_obj = dict(added=[], updated=[], removed=[])
|
||||||
|
|
||||||
|
labels_before = [x.asdict() for x in this_gitlab.list_all_labels()]
|
||||||
|
|
||||||
|
# filter out and enrich before compare
|
||||||
|
for item in requested_labels:
|
||||||
|
# add defaults when not present
|
||||||
|
if item.get('description') is None:
|
||||||
|
item['description'] = ""
|
||||||
|
if item.get('new_name') is None:
|
||||||
|
item['new_name'] = None
|
||||||
|
if item.get('priority') is None:
|
||||||
|
item['priority'] = None
|
||||||
|
|
||||||
|
# group label does not have priority, removing for comparison
|
||||||
|
if this_gitlab.is_group_label:
|
||||||
|
item.pop('priority')
|
||||||
|
|
||||||
|
for item in labels_before:
|
||||||
|
# remove field only from server
|
||||||
|
item.pop('id')
|
||||||
|
item.pop('description_html')
|
||||||
|
item.pop('text_color')
|
||||||
|
item.pop('subscribed')
|
||||||
|
# field present only when it's a project's label
|
||||||
|
if 'is_project_label' in item:
|
||||||
|
item.pop('is_project_label')
|
||||||
|
item['new_name'] = None
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
add_or_update = [x for x in requested_labels if x not in labels_before]
|
||||||
|
for item in add_or_update:
|
||||||
|
try:
|
||||||
|
_rv, _obj = this_gitlab.create_label(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['added'].append(item)
|
||||||
|
return_obj['added'].append(_obj)
|
||||||
|
except Exception:
|
||||||
|
# create raises exception with following error message when label already exists
|
||||||
|
_rv, _obj = this_gitlab.update_label(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['updated'].append(item)
|
||||||
|
return_obj['updated'].append(_obj)
|
||||||
|
|
||||||
|
if purge:
|
||||||
|
# re-fetch
|
||||||
|
_labels = this_gitlab.list_all_labels()
|
||||||
|
|
||||||
|
for item in labels_before:
|
||||||
|
_rv, _obj = this_gitlab.delete_label(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
|
||||||
|
elif state == 'absent':
|
||||||
|
if not purge:
|
||||||
|
_label_names_requested = [x['name'] for x in requested_labels]
|
||||||
|
remove_requested = [x for x in labels_before if x['name'] in _label_names_requested]
|
||||||
|
for item in remove_requested:
|
||||||
|
_rv, _obj = this_gitlab.delete_label(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
else:
|
||||||
|
for item in labels_before:
|
||||||
|
_rv, _obj = this_gitlab.delete_label(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
_untouched, _updated, _added = compare(requested_labels, labels_before, state)
|
||||||
|
return_value = dict(added=_added, updated=_updated, removed=return_value['removed'], untouched=_untouched)
|
||||||
|
|
||||||
|
if any(return_value[x] for x in ['added', 'removed', 'updated']):
|
||||||
|
change = True
|
||||||
|
|
||||||
|
labels_after = [x.asdict() for x in this_gitlab.list_all_labels()]
|
||||||
|
|
||||||
|
return change, return_value, labels_before, labels_after, return_obj
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = basic_auth_argument_spec()
|
||||||
|
argument_spec.update(auth_argument_spec())
|
||||||
|
argument_spec.update(
|
||||||
|
project=dict(type='str', required=False, default=None),
|
||||||
|
group=dict(type='str', required=False, default=None),
|
||||||
|
purge=dict(type='bool', required=False, default=False),
|
||||||
|
labels=dict(type='list', elements='dict', required=False, default=list(),
|
||||||
|
options=dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
color=dict(type='str', required=False),
|
||||||
|
description=dict(type='str', required=False),
|
||||||
|
priority=dict(type='int', required=False),
|
||||||
|
new_name=dict(type='str', required=False),)
|
||||||
|
),
|
||||||
|
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
mutually_exclusive=[
|
||||||
|
['api_username', 'api_token'],
|
||||||
|
['api_username', 'api_oauth_token'],
|
||||||
|
['api_username', 'api_job_token'],
|
||||||
|
['api_token', 'api_oauth_token'],
|
||||||
|
['api_token', 'api_job_token'],
|
||||||
|
['project', 'group'],
|
||||||
|
],
|
||||||
|
required_together=[
|
||||||
|
['api_username', 'api_password'],
|
||||||
|
],
|
||||||
|
required_one_of=[
|
||||||
|
['api_username', 'api_token', 'api_oauth_token', 'api_job_token'],
|
||||||
|
['project', 'group']
|
||||||
|
],
|
||||||
|
supports_check_mode=True
|
||||||
|
)
|
||||||
|
ensure_gitlab_package(module)
|
||||||
|
|
||||||
|
gitlab_project = module.params['project']
|
||||||
|
gitlab_group = module.params['group']
|
||||||
|
purge = module.params['purge']
|
||||||
|
label_list = module.params['labels']
|
||||||
|
state = module.params['state']
|
||||||
|
|
||||||
|
gitlab_version = gitlab.__version__
|
||||||
|
_min_gitlab = '3.2.0'
|
||||||
|
if LooseVersion(gitlab_version) < LooseVersion(_min_gitlab):
|
||||||
|
module.fail_json(msg="community.general.gitlab_label requires python-gitlab Python module >= %s "
|
||||||
|
"(installed version: [%s]). Please upgrade "
|
||||||
|
"python-gitlab to version %s or above." % (_min_gitlab, gitlab_version, _min_gitlab))
|
||||||
|
|
||||||
|
gitlab_instance = gitlab_authentication(module)
|
||||||
|
|
||||||
|
# find_project can return None, but the other must exist
|
||||||
|
gitlab_project_id = find_project(gitlab_instance, gitlab_project)
|
||||||
|
|
||||||
|
# find_group can return None, but the other must exist
|
||||||
|
gitlab_group_id = find_group(gitlab_instance, gitlab_group)
|
||||||
|
|
||||||
|
# if both not found, module must exist
|
||||||
|
if not gitlab_project_id and not gitlab_group_id:
|
||||||
|
if gitlab_project and not gitlab_project_id:
|
||||||
|
module.fail_json(msg="project '%s' not found." % gitlab_project)
|
||||||
|
if gitlab_group and not gitlab_group_id:
|
||||||
|
module.fail_json(msg="group '%s' not found." % gitlab_group)
|
||||||
|
|
||||||
|
this_gitlab = GitlabLabels(module=module, gitlab_instance=gitlab_instance, group_id=gitlab_group_id,
|
||||||
|
project_id=gitlab_project_id)
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
_existing_labels = [x.asdict()['name'] for x in this_gitlab.list_all_labels()]
|
||||||
|
|
||||||
|
# color is mandatory when creating label, but it's optional when changing name or updating other fields
|
||||||
|
if any(x['color'] is None and x['new_name'] is None and x['name'] not in _existing_labels for x in label_list):
|
||||||
|
module.fail_json(msg='color parameter is required for new labels')
|
||||||
|
|
||||||
|
change, raw_return_value, before, after, _obj = native_python_main(this_gitlab, purge, label_list, state, module)
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
raw_return_value['untouched'] = [x for x in before if x in after]
|
||||||
|
|
||||||
|
added = [x.get('name') for x in raw_return_value['added']]
|
||||||
|
updated = [x.get('name') for x in raw_return_value['updated']]
|
||||||
|
removed = [x.get('name') for x in raw_return_value['removed']]
|
||||||
|
untouched = [x.get('name') for x in raw_return_value['untouched']]
|
||||||
|
return_value = dict(added=added, updated=updated, removed=removed, untouched=untouched)
|
||||||
|
|
||||||
|
module.exit_json(changed=change, labels=return_value, labels_obj=_obj)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
496
plugins/modules/gitlab_milestone.py
Normal file
496
plugins/modules/gitlab_milestone.py
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2023, Gabriele Pongelli (gabriele.pongelli@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
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
module: gitlab_milestone
|
||||||
|
short_description: Creates/updates/deletes GitLab Milestones belonging to project or group
|
||||||
|
version_added: 8.3.0
|
||||||
|
description:
|
||||||
|
- When a milestone does not exist, it will be created.
|
||||||
|
- When a milestone does exist, its value will be updated when the values are different.
|
||||||
|
- Milestones can be purged.
|
||||||
|
author:
|
||||||
|
- "Gabriele Pongelli (@gpongelli)"
|
||||||
|
requirements:
|
||||||
|
- python-gitlab python module
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.auth_basic
|
||||||
|
- community.general.gitlab
|
||||||
|
- community.general.attributes
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Create or delete milestone.
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
choices: ["present", "absent"]
|
||||||
|
purge:
|
||||||
|
description:
|
||||||
|
- When set to V(true), delete all milestone which are not mentioned in the task.
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
required: false
|
||||||
|
project:
|
||||||
|
description:
|
||||||
|
- The path and name of the project. Either this or O(group) is required.
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
|
group:
|
||||||
|
description:
|
||||||
|
- The path of the group. Either this or O(project) is required.
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
|
milestones:
|
||||||
|
description:
|
||||||
|
- A list of dictionaries that represents gitlab project's or group's milestones.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
required: false
|
||||||
|
default: []
|
||||||
|
suboptions:
|
||||||
|
title:
|
||||||
|
description:
|
||||||
|
- The name of the milestone.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
due_date:
|
||||||
|
description:
|
||||||
|
- Milestone due date in YYYY-MM-DD format.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
start_date:
|
||||||
|
description:
|
||||||
|
- Milestone start date in YYYY-MM-DD format.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Milestone's description.
|
||||||
|
type: str
|
||||||
|
default: null
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# same project's task can be executed for group
|
||||||
|
- name: Create one milestone
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
start_date: "2024-01-04"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create many group milestones
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
group: "group1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
start_date: "2024-01-04"
|
||||||
|
description: this is a milestone
|
||||||
|
due_date: "2024-02-04"
|
||||||
|
- title: milestone_two
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create many project milestones
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
start_date: "2024-01-04"
|
||||||
|
description: this is a milestone
|
||||||
|
due_date: "2024-02-04"
|
||||||
|
- title: milestone_two
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Set or update some milestones
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
start_date: "2024-05-04"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add milestone in check mode
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
start_date: "2024-05-04"
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Delete milestone
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
milestones:
|
||||||
|
- title: milestone_one
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Purge all milestones
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Delete many milestones
|
||||||
|
community.general.gitlab_milestone:
|
||||||
|
api_url: https://gitlab.com
|
||||||
|
api_token: secret_access_token
|
||||||
|
project: "group1/project1"
|
||||||
|
state: absent
|
||||||
|
milestones:
|
||||||
|
- title: milestone-abc123
|
||||||
|
- title: milestone-two
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
milestones:
|
||||||
|
description: Four lists of the milestones which were added, updated, removed or exist.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
added:
|
||||||
|
description: A list of milestones which were created.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['abcd', 'milestone-one']
|
||||||
|
untouched:
|
||||||
|
description: A list of milestones which exist.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-milestone']
|
||||||
|
removed:
|
||||||
|
description: A list of milestones which were deleted.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-milestone']
|
||||||
|
updated:
|
||||||
|
description: A list pre-existing milestones whose values have been set.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['defg', 'new-milestone']
|
||||||
|
milestones_obj:
|
||||||
|
description: API object.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.api import basic_auth_argument_spec
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||||
|
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project, gitlab
|
||||||
|
)
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class GitlabMilestones(object):
|
||||||
|
|
||||||
|
def __init__(self, module, gitlab_instance, group_id, project_id):
|
||||||
|
self._gitlab = gitlab_instance
|
||||||
|
self.gitlab_object = group_id if group_id else project_id
|
||||||
|
self.is_group_milestone = True if group_id else False
|
||||||
|
self._module = module
|
||||||
|
|
||||||
|
def list_all_milestones(self):
|
||||||
|
page_nb = 1
|
||||||
|
milestones = []
|
||||||
|
vars_page = self.gitlab_object.milestones.list(page=page_nb)
|
||||||
|
while len(vars_page) > 0:
|
||||||
|
milestones += vars_page
|
||||||
|
page_nb += 1
|
||||||
|
vars_page = self.gitlab_object.milestones.list(page=page_nb)
|
||||||
|
return milestones
|
||||||
|
|
||||||
|
def create_milestone(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
var = {
|
||||||
|
"title": var_obj.get('title'),
|
||||||
|
}
|
||||||
|
|
||||||
|
if var_obj.get('description') is not None:
|
||||||
|
var["description"] = var_obj.get('description')
|
||||||
|
|
||||||
|
if var_obj.get('start_date') is not None:
|
||||||
|
var["start_date"] = self.check_date(var_obj.get('start_date'))
|
||||||
|
|
||||||
|
if var_obj.get('due_date') is not None:
|
||||||
|
var["due_date"] = self.check_date(var_obj.get('due_date'))
|
||||||
|
|
||||||
|
_obj = self.gitlab_object.milestones.create(var)
|
||||||
|
return True, _obj.asdict()
|
||||||
|
|
||||||
|
def update_milestone(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
_milestone = self.gitlab_object.milestones.get(self.get_milestone_id(var_obj.get('title')))
|
||||||
|
|
||||||
|
if var_obj.get('description') is not None:
|
||||||
|
_milestone.description = var_obj.get('description')
|
||||||
|
|
||||||
|
if var_obj.get('start_date') is not None:
|
||||||
|
_milestone.start_date = var_obj.get('start_date')
|
||||||
|
|
||||||
|
if var_obj.get('due_date') is not None:
|
||||||
|
_milestone.due_date = var_obj.get('due_date')
|
||||||
|
|
||||||
|
# save returns None
|
||||||
|
_milestone.save()
|
||||||
|
return True, _milestone.asdict()
|
||||||
|
|
||||||
|
def get_milestone_id(self, _title):
|
||||||
|
_milestone_list = self.gitlab_object.milestones.list()
|
||||||
|
_found = list(filter(lambda x: x.title == _title, _milestone_list))
|
||||||
|
if _found:
|
||||||
|
return _found[0].id
|
||||||
|
else:
|
||||||
|
self._module.fail_json(msg="milestone '%s' not found." % _title)
|
||||||
|
|
||||||
|
def check_date(self, _date):
|
||||||
|
try:
|
||||||
|
datetime.strptime(_date, '%Y-%m-%d')
|
||||||
|
except ValueError:
|
||||||
|
self._module.fail_json(msg="milestone's date '%s' not in correct format." % _date)
|
||||||
|
return _date
|
||||||
|
|
||||||
|
def delete_milestone(self, var_obj):
|
||||||
|
if self._module.check_mode:
|
||||||
|
return True, True
|
||||||
|
_milestone = self.gitlab_object.milestones.get(self.get_milestone_id(var_obj.get('title')))
|
||||||
|
# delete returns None
|
||||||
|
_milestone.delete()
|
||||||
|
return True, _milestone.asdict()
|
||||||
|
|
||||||
|
|
||||||
|
def compare(requested_milestones, existing_milestones, state):
|
||||||
|
# we need to do this, because it was determined in a previous version - more or less buggy
|
||||||
|
# basically it is not necessary and might result in more/other bugs!
|
||||||
|
# but it is required and only relevant for check mode!!
|
||||||
|
# logic represents state 'present' when not purge. all other can be derived from that
|
||||||
|
# untouched => equal in both
|
||||||
|
# updated => title are equal
|
||||||
|
# added => title does not exist
|
||||||
|
untouched = list()
|
||||||
|
updated = list()
|
||||||
|
added = list()
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
_existing_milestones = list()
|
||||||
|
for item in existing_milestones:
|
||||||
|
_existing_milestones.append({'title': item.get('title')})
|
||||||
|
|
||||||
|
for var in requested_milestones:
|
||||||
|
if var in existing_milestones:
|
||||||
|
untouched.append(var)
|
||||||
|
else:
|
||||||
|
compare_item = {'title': var.get('title')}
|
||||||
|
if compare_item in _existing_milestones:
|
||||||
|
updated.append(var)
|
||||||
|
else:
|
||||||
|
added.append(var)
|
||||||
|
|
||||||
|
return untouched, updated, added
|
||||||
|
|
||||||
|
|
||||||
|
def native_python_main(this_gitlab, purge, requested_milestones, state, module):
|
||||||
|
change = False
|
||||||
|
return_value = dict(added=[], updated=[], removed=[], untouched=[])
|
||||||
|
return_obj = dict(added=[], updated=[], removed=[])
|
||||||
|
|
||||||
|
milestones_before = [x.asdict() for x in this_gitlab.list_all_milestones()]
|
||||||
|
|
||||||
|
# filter out and enrich before compare
|
||||||
|
for item in requested_milestones:
|
||||||
|
# add defaults when not present
|
||||||
|
if item.get('description') is None:
|
||||||
|
item['description'] = ""
|
||||||
|
if item.get('due_date') is None:
|
||||||
|
item['due_date'] = None
|
||||||
|
if item.get('start_date') is None:
|
||||||
|
item['start_date'] = None
|
||||||
|
|
||||||
|
for item in milestones_before:
|
||||||
|
# remove field only from server
|
||||||
|
item.pop('id')
|
||||||
|
item.pop('iid')
|
||||||
|
item.pop('created_at')
|
||||||
|
item.pop('expired')
|
||||||
|
item.pop('state')
|
||||||
|
item.pop('updated_at')
|
||||||
|
item.pop('web_url')
|
||||||
|
# group milestone has group_id, while project has project_id
|
||||||
|
if 'group_id' in item:
|
||||||
|
item.pop('group_id')
|
||||||
|
if 'project_id' in item:
|
||||||
|
item.pop('project_id')
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
add_or_update = [x for x in requested_milestones if x not in milestones_before]
|
||||||
|
for item in add_or_update:
|
||||||
|
try:
|
||||||
|
_rv, _obj = this_gitlab.create_milestone(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['added'].append(item)
|
||||||
|
return_obj['added'].append(_obj)
|
||||||
|
except Exception:
|
||||||
|
# create raises exception with following error message when milestone already exists
|
||||||
|
_rv, _obj = this_gitlab.update_milestone(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['updated'].append(item)
|
||||||
|
return_obj['updated'].append(_obj)
|
||||||
|
|
||||||
|
if purge:
|
||||||
|
# re-fetch
|
||||||
|
_milestones = this_gitlab.list_all_milestones()
|
||||||
|
|
||||||
|
for item in milestones_before:
|
||||||
|
_rv, _obj = this_gitlab.delete_milestone(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
|
||||||
|
elif state == 'absent':
|
||||||
|
if not purge:
|
||||||
|
_milestone_titles_requested = [x['title'] for x in requested_milestones]
|
||||||
|
remove_requested = [x for x in milestones_before if x['title'] in _milestone_titles_requested]
|
||||||
|
for item in remove_requested:
|
||||||
|
_rv, _obj = this_gitlab.delete_milestone(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
else:
|
||||||
|
for item in milestones_before:
|
||||||
|
_rv, _obj = this_gitlab.delete_milestone(item)
|
||||||
|
if _rv:
|
||||||
|
return_value['removed'].append(item)
|
||||||
|
return_obj['removed'].append(_obj)
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
_untouched, _updated, _added = compare(requested_milestones, milestones_before, state)
|
||||||
|
return_value = dict(added=_added, updated=_updated, removed=return_value['removed'], untouched=_untouched)
|
||||||
|
|
||||||
|
if any(return_value[x] for x in ['added', 'removed', 'updated']):
|
||||||
|
change = True
|
||||||
|
|
||||||
|
milestones_after = [x.asdict() for x in this_gitlab.list_all_milestones()]
|
||||||
|
|
||||||
|
return change, return_value, milestones_before, milestones_after, return_obj
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = basic_auth_argument_spec()
|
||||||
|
argument_spec.update(auth_argument_spec())
|
||||||
|
argument_spec.update(
|
||||||
|
project=dict(type='str', required=False, default=None),
|
||||||
|
group=dict(type='str', required=False, default=None),
|
||||||
|
purge=dict(type='bool', required=False, default=False),
|
||||||
|
milestones=dict(type='list', elements='dict', required=False, default=list(),
|
||||||
|
options=dict(
|
||||||
|
title=dict(type='str', required=True),
|
||||||
|
description=dict(type='str', required=False),
|
||||||
|
due_date=dict(type='str', required=False),
|
||||||
|
start_date=dict(type='str', required=False),)
|
||||||
|
),
|
||||||
|
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
mutually_exclusive=[
|
||||||
|
['api_username', 'api_token'],
|
||||||
|
['api_username', 'api_oauth_token'],
|
||||||
|
['api_username', 'api_job_token'],
|
||||||
|
['api_token', 'api_oauth_token'],
|
||||||
|
['api_token', 'api_job_token'],
|
||||||
|
['project', 'group'],
|
||||||
|
],
|
||||||
|
required_together=[
|
||||||
|
['api_username', 'api_password'],
|
||||||
|
],
|
||||||
|
required_one_of=[
|
||||||
|
['api_username', 'api_token', 'api_oauth_token', 'api_job_token'],
|
||||||
|
['project', 'group']
|
||||||
|
],
|
||||||
|
supports_check_mode=True
|
||||||
|
)
|
||||||
|
ensure_gitlab_package(module)
|
||||||
|
|
||||||
|
gitlab_project = module.params['project']
|
||||||
|
gitlab_group = module.params['group']
|
||||||
|
purge = module.params['purge']
|
||||||
|
milestone_list = module.params['milestones']
|
||||||
|
state = module.params['state']
|
||||||
|
|
||||||
|
gitlab_version = gitlab.__version__
|
||||||
|
_min_gitlab = '3.2.0'
|
||||||
|
if LooseVersion(gitlab_version) < LooseVersion(_min_gitlab):
|
||||||
|
module.fail_json(msg="community.general.gitlab_milestone requires python-gitlab Python module >= %s "
|
||||||
|
"(installed version: [%s]). Please upgrade "
|
||||||
|
"python-gitlab to version %s or above." % (_min_gitlab, gitlab_version, _min_gitlab))
|
||||||
|
|
||||||
|
gitlab_instance = gitlab_authentication(module)
|
||||||
|
|
||||||
|
# find_project can return None, but the other must exist
|
||||||
|
gitlab_project_id = find_project(gitlab_instance, gitlab_project)
|
||||||
|
|
||||||
|
# find_group can return None, but the other must exist
|
||||||
|
gitlab_group_id = find_group(gitlab_instance, gitlab_group)
|
||||||
|
|
||||||
|
# if both not found, module must exist
|
||||||
|
if not gitlab_project_id and not gitlab_group_id:
|
||||||
|
if gitlab_project and not gitlab_project_id:
|
||||||
|
module.fail_json(msg="project '%s' not found." % gitlab_project)
|
||||||
|
if gitlab_group and not gitlab_group_id:
|
||||||
|
module.fail_json(msg="group '%s' not found." % gitlab_group)
|
||||||
|
|
||||||
|
this_gitlab = GitlabMilestones(module=module, gitlab_instance=gitlab_instance, group_id=gitlab_group_id,
|
||||||
|
project_id=gitlab_project_id)
|
||||||
|
|
||||||
|
change, raw_return_value, before, after, _obj = native_python_main(this_gitlab, purge, milestone_list, state,
|
||||||
|
module)
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
raw_return_value['untouched'] = [x for x in before if x in after]
|
||||||
|
|
||||||
|
added = [x.get('title') for x in raw_return_value['added']]
|
||||||
|
updated = [x.get('title') for x in raw_return_value['updated']]
|
||||||
|
removed = [x.get('title') for x in raw_return_value['removed']]
|
||||||
|
untouched = [x.get('title') for x in raw_return_value['untouched']]
|
||||||
|
return_value = dict(added=added, updated=updated, removed=removed, untouched=untouched)
|
||||||
|
|
||||||
|
module.exit_json(changed=change, milestones=return_value, milestones_obj=_obj)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -165,6 +165,7 @@ changed_pkgs:
|
|||||||
version_added: '0.2.0'
|
version_added: '0.2.0'
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -184,6 +185,10 @@ def _create_regex_group_complement(s):
|
|||||||
chars = filter(None, (line.split('#')[0].strip() for line in lines))
|
chars = filter(None, (line.split('#')[0].strip() for line in lines))
|
||||||
group = r'[^' + r''.join(chars) + r']'
|
group = r'[^' + r''.join(chars) + r']'
|
||||||
return re.compile(group)
|
return re.compile(group)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_package_in_json(json_output, package_type):
|
||||||
|
return bool(json_output.get(package_type, []) and json_output[package_type][0].get("installed"))
|
||||||
# /utils ------------------------------------------------------------------ }}}
|
# /utils ------------------------------------------------------------------ }}}
|
||||||
|
|
||||||
|
|
||||||
@@ -479,17 +484,13 @@ class Homebrew(object):
|
|||||||
cmd = [
|
cmd = [
|
||||||
"{brew_path}".format(brew_path=self.brew_path),
|
"{brew_path}".format(brew_path=self.brew_path),
|
||||||
"info",
|
"info",
|
||||||
|
"--json=v2",
|
||||||
self.current_package,
|
self.current_package,
|
||||||
]
|
]
|
||||||
rc, out, err = self.module.run_command(cmd)
|
rc, out, err = self.module.run_command(cmd)
|
||||||
for line in out.split('\n'):
|
data = json.loads(out)
|
||||||
if (
|
|
||||||
re.search(r'Built from source', line)
|
|
||||||
or re.search(r'Poured from bottle', line)
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return _check_package_in_json(data, "formulae") or _check_package_in_json(data, "casks")
|
||||||
|
|
||||||
def _current_package_is_outdated(self):
|
def _current_package_is_outdated(self):
|
||||||
if not self.valid_package(self.current_package):
|
if not self.valid_package(self.current_package):
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ def get_otptoken_dict(ansible_to_ipa, uniqueid=None, newuniqueid=None, otptype=N
|
|||||||
if owner is not None:
|
if owner is not None:
|
||||||
otptoken[ansible_to_ipa['owner']] = owner
|
otptoken[ansible_to_ipa['owner']] = owner
|
||||||
if enabled is not None:
|
if enabled is not None:
|
||||||
otptoken[ansible_to_ipa['enabled']] = 'FALSE' if enabled else 'TRUE'
|
otptoken[ansible_to_ipa['enabled']] = False if enabled else True
|
||||||
if notbefore is not None:
|
if notbefore is not None:
|
||||||
otptoken[ansible_to_ipa['notbefore']] = notbefore + 'Z'
|
otptoken[ansible_to_ipa['notbefore']] = notbefore + 'Z'
|
||||||
if notafter is not None:
|
if notafter is not None:
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ def run_module():
|
|||||||
# Process the script into batches
|
# Process the script into batches
|
||||||
queries = []
|
queries = []
|
||||||
current_batch = []
|
current_batch = []
|
||||||
for statement in script.splitlines(keepends=True):
|
for statement in script.splitlines(True):
|
||||||
# Ignore the Byte Order Mark, if found
|
# Ignore the Byte Order Mark, if found
|
||||||
if statement.strip() == '\uFEFF':
|
if statement.strip() == '\uFEFF':
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1832,7 +1832,7 @@ class Nmcli(object):
|
|||||||
elif self.type == 'wifi':
|
elif self.type == 'wifi':
|
||||||
options.update({
|
options.update({
|
||||||
'802-11-wireless.ssid': self.ssid,
|
'802-11-wireless.ssid': self.ssid,
|
||||||
'connection.slave-type': 'bond' if self.master else None,
|
'connection.slave-type': ('bond' if self.slave_type is None else self.slave_type) if self.master else None,
|
||||||
})
|
})
|
||||||
if self.wifi:
|
if self.wifi:
|
||||||
for name, value in self.wifi.items():
|
for name, value in self.wifi.items():
|
||||||
|
|||||||
@@ -88,11 +88,13 @@ options:
|
|||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
- name: Import a key via local file
|
- name: Import a key via local file
|
||||||
community.general.pacman_key:
|
community.general.pacman_key:
|
||||||
|
id: 01234567890ABCDE01234567890ABCDE12345678
|
||||||
data: "{{ lookup('file', 'keyfile.asc') }}"
|
data: "{{ lookup('file', 'keyfile.asc') }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Import a key via remote file
|
- name: Import a key via remote file
|
||||||
community.general.pacman_key:
|
community.general.pacman_key:
|
||||||
|
id: 01234567890ABCDE01234567890ABCDE12345678
|
||||||
file: /tmp/keyfile.asc
|
file: /tmp/keyfile.asc
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
|||||||
@@ -562,6 +562,10 @@ class ProxmoxLxcAnsible(ProxmoxAnsible):
|
|||||||
# compare the requested config against the current
|
# compare the requested config against the current
|
||||||
update_config = False
|
update_config = False
|
||||||
for (arg, value) in kwargs.items():
|
for (arg, value) in kwargs.items():
|
||||||
|
# if the arg isn't in the current config, it needs to be updated
|
||||||
|
if arg not in current_config:
|
||||||
|
update_config = True
|
||||||
|
break
|
||||||
# some values are lists, the order isn't always the same, so split them and compare by key
|
# some values are lists, the order isn't always the same, so split them and compare by key
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
current_values = current_config[arg].split(",")
|
current_values = current_config[arg].split(",")
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ options:
|
|||||||
- Security token for authenticating to OOB controller.
|
- Security token for authenticating to OOB controller.
|
||||||
type: str
|
type: str
|
||||||
version_added: 2.3.0
|
version_added: 2.3.0
|
||||||
|
manager:
|
||||||
|
description:
|
||||||
|
- Name of manager on OOB controller to target.
|
||||||
|
type: str
|
||||||
|
version_added: '8.3.0'
|
||||||
timeout:
|
timeout:
|
||||||
description:
|
description:
|
||||||
- Timeout in seconds for HTTP requests to OOB controller.
|
- Timeout in seconds for HTTP requests to OOB controller.
|
||||||
@@ -248,6 +253,15 @@ EXAMPLES = '''
|
|||||||
username: "{{ username }}"
|
username: "{{ username }}"
|
||||||
password: "{{ password }}"
|
password: "{{ password }}"
|
||||||
|
|
||||||
|
- name: Get service identification
|
||||||
|
community.general.redfish_info:
|
||||||
|
category: Manager
|
||||||
|
command: GetServiceIdentification
|
||||||
|
manager: "{{ manager }}"
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
|
||||||
- name: Get software inventory
|
- name: Get software inventory
|
||||||
community.general.redfish_info:
|
community.general.redfish_info:
|
||||||
category: Update
|
category: Update
|
||||||
@@ -369,7 +383,7 @@ CATEGORY_COMMANDS_ALL = {
|
|||||||
"Update": ["GetFirmwareInventory", "GetFirmwareUpdateCapabilities", "GetSoftwareInventory",
|
"Update": ["GetFirmwareInventory", "GetFirmwareUpdateCapabilities", "GetSoftwareInventory",
|
||||||
"GetUpdateStatus"],
|
"GetUpdateStatus"],
|
||||||
"Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols",
|
"Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols",
|
||||||
"GetHealthReport", "GetHostInterfaces", "GetManagerInventory"],
|
"GetHealthReport", "GetHostInterfaces", "GetManagerInventory", "GetServiceIdentification"],
|
||||||
}
|
}
|
||||||
|
|
||||||
CATEGORY_COMMANDS_DEFAULT = {
|
CATEGORY_COMMANDS_DEFAULT = {
|
||||||
@@ -395,6 +409,7 @@ def main():
|
|||||||
auth_token=dict(no_log=True),
|
auth_token=dict(no_log=True),
|
||||||
timeout=dict(type='int'),
|
timeout=dict(type='int'),
|
||||||
update_handle=dict(),
|
update_handle=dict(),
|
||||||
|
manager=dict(),
|
||||||
),
|
),
|
||||||
required_together=[
|
required_together=[
|
||||||
('username', 'password'),
|
('username', 'password'),
|
||||||
@@ -429,6 +444,9 @@ def main():
|
|||||||
# update handle
|
# update handle
|
||||||
update_handle = module.params['update_handle']
|
update_handle = module.params['update_handle']
|
||||||
|
|
||||||
|
# manager
|
||||||
|
manager = module.params['manager']
|
||||||
|
|
||||||
# Build root URI
|
# Build root URI
|
||||||
root_uri = "https://" + module.params['baseuri']
|
root_uri = "https://" + module.params['baseuri']
|
||||||
rf_utils = RedfishUtils(creds, root_uri, timeout, module)
|
rf_utils = RedfishUtils(creds, root_uri, timeout, module)
|
||||||
@@ -579,6 +597,8 @@ def main():
|
|||||||
result["host_interfaces"] = rf_utils.get_hostinterfaces()
|
result["host_interfaces"] = rf_utils.get_hostinterfaces()
|
||||||
elif command == "GetManagerInventory":
|
elif command == "GetManagerInventory":
|
||||||
result["manager"] = rf_utils.get_multi_manager_inventory()
|
result["manager"] = rf_utils.get_multi_manager_inventory()
|
||||||
|
elif command == "GetServiceIdentification":
|
||||||
|
result["service_id"] = rf_utils.get_service_identification(manager)
|
||||||
|
|
||||||
# Return data back
|
# Return data back
|
||||||
module.exit_json(redfish_facts=result)
|
module.exit_json(redfish_facts=result)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ attributes:
|
|||||||
check_mode:
|
check_mode:
|
||||||
support: full
|
support: full
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: none
|
support: full
|
||||||
|
version_added: 8.3.0
|
||||||
options:
|
options:
|
||||||
state:
|
state:
|
||||||
choices: ['planned', 'present', 'absent']
|
choices: ['planned', 'present', 'absent']
|
||||||
@@ -428,6 +429,42 @@ def build_plan(command, project_path, variables_args, state_file, targets, state
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def get_diff(diff_output):
|
||||||
|
def get_tf_resource_address(e):
|
||||||
|
return e['resource']
|
||||||
|
|
||||||
|
diff_json_output = json.loads(diff_output)
|
||||||
|
tf_reosource_changes = diff_json_output['resource_changes']
|
||||||
|
diff_after = []
|
||||||
|
diff_before = []
|
||||||
|
changed = False
|
||||||
|
for item in tf_reosource_changes:
|
||||||
|
item_change = item['change']
|
||||||
|
tf_before_state = {'resource': item['address'], 'change': item['change']['before']}
|
||||||
|
tf_after_state = {'resource': item['address'], 'change': item['change']['after']}
|
||||||
|
|
||||||
|
if item_change['actions'] == ['update'] or item_change['actions'] == ['delete', 'create']:
|
||||||
|
diff_before.append(tf_before_state)
|
||||||
|
diff_after.append(tf_after_state)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if item_change['actions'] == ['delete']:
|
||||||
|
diff_before.append(tf_before_state)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if item_change['actions'] == ['create']:
|
||||||
|
diff_after.append(tf_after_state)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
diff_before.sort(key=get_tf_resource_address)
|
||||||
|
diff_after.sort(key=get_tf_resource_address)
|
||||||
|
|
||||||
|
return changed, dict(
|
||||||
|
before=({'data': diff_before}),
|
||||||
|
after=({'data': diff_after}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global module
|
global module
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
@@ -619,6 +656,19 @@ def main():
|
|||||||
"Consider switching the 'check_destroy' to false to suppress this error")
|
"Consider switching the 'check_destroy' to false to suppress this error")
|
||||||
command.append(plan_file)
|
command.append(plan_file)
|
||||||
|
|
||||||
|
result_diff = dict()
|
||||||
|
if module._diff or module.check_mode:
|
||||||
|
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"])
|
||||||
|
module.fail_json(msg=err.rstrip(), rc=rc, stdout=out,
|
||||||
|
stdout_lines=out.splitlines(), stderr=err,
|
||||||
|
stderr_lines=err.splitlines(),
|
||||||
|
cmd=' '.join(command))
|
||||||
|
|
||||||
if needs_application and not module.check_mode and state != 'planned':
|
if needs_application and not module.check_mode and state != 'planned':
|
||||||
rc, out, err = module.run_command(command, check_rc=False, cwd=project_path)
|
rc, out, err = module.run_command(command, check_rc=False, cwd=project_path)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
@@ -651,7 +701,18 @@ def main():
|
|||||||
if state == 'absent' and workspace != 'default' and purge_workspace is True:
|
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)
|
||||||
|
|
||||||
module.exit_json(changed=changed, state=state, workspace=workspace, outputs=outputs, stdout=out, stderr=err, command=' '.join(command))
|
result = {
|
||||||
|
'state': state,
|
||||||
|
'workspace': workspace,
|
||||||
|
'outputs': outputs,
|
||||||
|
'stdout': out,
|
||||||
|
'stderr': err,
|
||||||
|
'command': ' '.join(command),
|
||||||
|
'changed': changed,
|
||||||
|
'diff': result_diff,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
8
tests/galaxy-importer.cfg
Normal file
8
tests/galaxy-importer.cfg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
[galaxy-importer]
|
||||||
|
# This is only needed to make Zuul's third-party-check happy.
|
||||||
|
# It is not needed by anything else.
|
||||||
|
run_ansible_doc=false
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: /bin/echo
|
src: /bin/echo
|
||||||
dest: "{{ item.copy_to }}/echo"
|
dest: "{{ item.copy_to }}/echo"
|
||||||
mode: "755"
|
mode: "0755"
|
||||||
|
remote_src: true
|
||||||
when: item.copy_to is defined
|
when: item.copy_to is defined
|
||||||
|
|
||||||
- name: test cmd_echo module ({{ item.name }})
|
- name: test cmd_echo module ({{ item.name }})
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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: Create an auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
type: jwt
|
||||||
|
config:
|
||||||
|
jwt_validation_pubkeys:
|
||||||
|
- |
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.auth_method.Type == 'jwt'
|
||||||
|
- result.operation == 'create'
|
||||||
|
|
||||||
|
- name: Update auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
max_token_ttl: 30m80s
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.auth_method.Type == 'jwt'
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
|
- name: Update auth method (noop)
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
max_token_ttl: 30m80s
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.auth_method.Type == 'jwt'
|
||||||
|
- result.operation is not defined
|
||||||
|
|
||||||
|
- name: Delete auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
||||||
|
|
||||||
|
- name: Delete auth method (noop)
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.operation is not defined
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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: Create an auth method
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
type: jwt
|
||||||
|
config:
|
||||||
|
jwt_validation_pubkeys:
|
||||||
|
- |
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
|
||||||
|
- name: Create a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: test-binding
|
||||||
|
description: my description
|
||||||
|
auth_method: test
|
||||||
|
bind_type: service
|
||||||
|
bind_name: yolo
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.binding_rule.AuthMethod == 'test'
|
||||||
|
- result.binding.Description == 'test-binding: my description'
|
||||||
|
- result.operation == 'create'
|
||||||
|
|
||||||
|
- name: Update a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: test-binding
|
||||||
|
auth_method: test
|
||||||
|
bind_name: yolo2
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.binding.Description == 'test-binding: my description'
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
|
- name: Update a binding rule (noop)
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: test-binding
|
||||||
|
auth_method: test
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.binding.Description == 'test-binding: my description'
|
||||||
|
- result.operation is not defined
|
||||||
|
|
||||||
|
- name: Delete a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: test-binding
|
||||||
|
auth_method: test
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
||||||
76
tests/integration/targets/consul/tasks/consul_general.yml
Normal file
76
tests/integration/targets/consul/tasks/consul_general.yml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
# 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: ensure unknown scheme fails
|
||||||
|
consul_session:
|
||||||
|
state: info
|
||||||
|
id: dummy
|
||||||
|
scheme: non_existent
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
|
||||||
|
- name: ensure SSL certificate is checked
|
||||||
|
consul_session:
|
||||||
|
state: info
|
||||||
|
id: dummy
|
||||||
|
port: 8501
|
||||||
|
scheme: https
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: previous task should fail since certificate is not known
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- "'certificate verify failed' in result.msg"
|
||||||
|
|
||||||
|
- name: ensure SSL certificate isn't checked when validate_certs is disabled
|
||||||
|
consul_session:
|
||||||
|
state: info
|
||||||
|
id: dummy
|
||||||
|
port: 8501
|
||||||
|
scheme: https
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
validate_certs: false
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: previous task should succeed since certificate isn't checked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: ensure a secure connection is possible
|
||||||
|
consul_session:
|
||||||
|
state: info
|
||||||
|
id: dummy
|
||||||
|
port: 8501
|
||||||
|
scheme: https
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
ca_path: '{{ remote_dir }}/cert.pem'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: ensure connection errors are handled properly
|
||||||
|
consul_session:
|
||||||
|
state: info
|
||||||
|
id: dummy
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
port: 1234
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg.startswith('Could not connect to consul agent at localhost:1234, error was')
|
||||||
57
tests/integration/targets/consul/tasks/consul_kv.yml
Normal file
57
tests/integration/targets/consul/tasks/consul_kv.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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: Create a key
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
value: somevalue
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.data.Value == 'somevalue'
|
||||||
|
|
||||||
|
#- name: Test the lookup
|
||||||
|
# assert:
|
||||||
|
# that:
|
||||||
|
# - lookup('community.general.consul_kv', 'somekey', token=consul_management_token) == 'somevalue'
|
||||||
|
|
||||||
|
- name: Update a key with the same data
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
value: somevalue
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.data.Value == 'somevalue'
|
||||||
|
|
||||||
|
- name: Remove a key from the store
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
state: absent
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.data.Value == 'somevalue'
|
||||||
|
|
||||||
|
- name: Remove a non-existant key from the store
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
state: absent
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- not result.data
|
||||||
@@ -13,13 +13,14 @@
|
|||||||
key "private/foo" {
|
key "private/foo" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['policy']['Name'] == 'foo-access'
|
- result.policy.Name == 'foo-access'
|
||||||
|
- result.operation == 'create'
|
||||||
|
|
||||||
- name: Update the rules associated to a policy
|
- name: Update the rules associated to a policy
|
||||||
consul_policy:
|
consul_policy:
|
||||||
name: foo-access
|
name: foo-access
|
||||||
@@ -33,11 +34,13 @@
|
|||||||
event "bbq" {
|
event "bbq" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
- name: Update reports not changed when updating again without changes
|
- name: Update reports not changed when updating again without changes
|
||||||
consul_policy:
|
consul_policy:
|
||||||
name: foo-access
|
name: foo-access
|
||||||
@@ -51,17 +54,19 @@
|
|||||||
event "bbq" {
|
event "bbq" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is not changed
|
- result is not changed
|
||||||
|
- result.operation is not defined
|
||||||
|
|
||||||
- name: Remove a policy
|
- name: Remove a policy
|
||||||
consul_policy:
|
consul_policy:
|
||||||
name: foo-access
|
name: foo-access
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
state: absent
|
state: absent
|
||||||
register: result
|
register: result
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
key "private/foo" {
|
key "private/foo" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: policy_result
|
register: policy_result
|
||||||
|
|
||||||
- name: Create another policy with rules
|
- name: Create another policy with rules
|
||||||
@@ -26,7 +25,6 @@
|
|||||||
key "private/bar" {
|
key "private/bar" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: policy_result
|
register: policy_result
|
||||||
|
|
||||||
- name: Create a role with policy
|
- name: Create a role with policy
|
||||||
@@ -34,40 +32,40 @@
|
|||||||
name: foo-role-with-policy
|
name: foo-role-with-policy
|
||||||
policies:
|
policies:
|
||||||
- name: "foo-access-for-role"
|
- name: "foo-access-for-role"
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['Name'] == 'foo-role-with-policy'
|
- result.role.Name == 'foo-role-with-policy'
|
||||||
|
- result.operation == 'create'
|
||||||
|
|
||||||
- name: Update policy description, in check mode
|
- name: Update policy description, in check mode
|
||||||
consul_role:
|
consul_role:
|
||||||
name: foo-role-with-policy
|
name: foo-role-with-policy
|
||||||
description: "Testing updating description"
|
description: "Testing updating description"
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
check_mode: yes
|
check_mode: yes
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['Description'] == "Testing updating description"
|
- result.role.Description == "Testing updating description"
|
||||||
- result['role']['Policies'][0]['Name'] == 'foo-access-for-role'
|
- result.role.Policies.0.Name == 'foo-access-for-role'
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
- name: Update policy to add the description
|
- name: Update policy to add the description
|
||||||
consul_role:
|
consul_role:
|
||||||
name: foo-role-with-policy
|
name: foo-role-with-policy
|
||||||
description: "Role for testing policies"
|
description: "Role for testing policies"
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['Description'] == "Role for testing policies"
|
- result.role.Description == "Role for testing policies"
|
||||||
- result['role']['Policies'][0]['Name'] == 'foo-access-for-role'
|
- result.role.Policies.0.Name == 'foo-access-for-role'
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
- name: Update the role with another policy, also testing leaving description blank
|
- name: Update the role with another policy, also testing leaving description blank
|
||||||
consul_role:
|
consul_role:
|
||||||
@@ -75,19 +73,18 @@
|
|||||||
policies:
|
policies:
|
||||||
- name: "foo-access-for-role"
|
- name: "foo-access-for-role"
|
||||||
- name: "bar-access-for-role"
|
- name: "bar-access-for-role"
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['Policies'][0]['Name'] == 'foo-access-for-role'
|
- result.role.Policies.0.Name == 'foo-access-for-role'
|
||||||
- result['role']['Policies'][1]['Name'] == 'bar-access-for-role'
|
- result.role.Policies.1.Name == 'bar-access-for-role'
|
||||||
- result['role']['Description'] == "Role for testing policies"
|
- result.role.Description == "Role for testing policies"
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
- name: Create a role with service identity
|
- name: Create a role with service identity
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
service_identities:
|
service_identities:
|
||||||
- name: web
|
- name: web
|
||||||
@@ -98,12 +95,11 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'][0]['ServiceName'] == "web"
|
- result.role.ServiceIdentities.0.ServiceName == "web"
|
||||||
- result['role']['ServiceIdentities'][0]['Datacenters'][0] == "dc1"
|
- result.role.ServiceIdentities.0.Datacenters.0 == "dc1"
|
||||||
|
|
||||||
- name: Update the role with service identity in check mode
|
- name: Update the role with service identity in check mode
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
service_identities:
|
service_identities:
|
||||||
- name: web
|
- name: web
|
||||||
@@ -115,12 +111,11 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'][0]['ServiceName'] == "web"
|
- result.role.ServiceIdentities.0.ServiceName == "web"
|
||||||
- result['role']['ServiceIdentities'][0]['Datacenters'][0] == "dc2"
|
- result.role.ServiceIdentities.0.Datacenters.0 == "dc2"
|
||||||
|
|
||||||
- name: Update the role with service identity to add a policy, leaving the service id unchanged
|
- name: Update the role with service identity to add a policy, leaving the service id unchanged
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
policies:
|
policies:
|
||||||
- name: "foo-access-for-role"
|
- name: "foo-access-for-role"
|
||||||
@@ -129,13 +124,12 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'][0]['ServiceName'] == "web"
|
- result.role.ServiceIdentities.0.ServiceName == "web"
|
||||||
- result['role']['ServiceIdentities'][0]['Datacenters'][0] == "dc1"
|
- result.role.ServiceIdentities.0.Datacenters.0 == "dc1"
|
||||||
- result['role']['Policies'][0]['Name'] == 'foo-access-for-role'
|
- result.role.Policies.0.Name == 'foo-access-for-role'
|
||||||
|
|
||||||
- name: Update the role with service identity to remove the policies
|
- name: Update the role with service identity to remove the policies
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
policies: []
|
policies: []
|
||||||
register: result
|
register: result
|
||||||
@@ -143,13 +137,12 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'][0]['ServiceName'] == "web"
|
- result.role.ServiceIdentities.0.ServiceName == "web"
|
||||||
- result['role']['ServiceIdentities'][0]['Datacenters'][0] == "dc1"
|
- result.role.ServiceIdentities.0.Datacenters.0 == "dc1"
|
||||||
- result['role']['Policies'] is not defined
|
- result.role.Policies is not defined
|
||||||
|
|
||||||
- name: Update the role with service identity to remove the node identities, in check mode
|
- name: Update the role with service identity to remove the node identities, in check mode
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
node_identities: []
|
node_identities: []
|
||||||
register: result
|
register: result
|
||||||
@@ -158,14 +151,13 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'][0]['ServiceName'] == "web"
|
- result.role.ServiceIdentities.0.ServiceName == "web"
|
||||||
- result['role']['ServiceIdentities'][0]['Datacenters'][0] == "dc1"
|
- result.role.ServiceIdentities.0.Datacenters.0 == "dc1"
|
||||||
- result['role']['Policies'] is not defined
|
- result.role.Policies is not defined
|
||||||
- result['role']['NodeIdentities'] == [] # in check mode the cleared field is returned as an empty array
|
- result.role.NodeIdentities == [] # in check mode the cleared field is returned as an empty array
|
||||||
|
|
||||||
- name: Update the role with service identity to remove the service identities
|
- name: Update the role with service identity to remove the service identities
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-service-identity
|
name: role-with-service-identity
|
||||||
service_identities: []
|
service_identities: []
|
||||||
register: result
|
register: result
|
||||||
@@ -173,12 +165,11 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['ServiceIdentities'] is not defined # in normal mode the dictionary is removed from the result
|
- result.role.ServiceIdentities is not defined # in normal mode the dictionary is removed from the result
|
||||||
- result['role']['Policies'] is not defined
|
- result.role.Policies is not defined
|
||||||
|
|
||||||
- name: Create a role with node identity
|
- name: Create a role with node identity
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-node-identity
|
name: role-with-node-identity
|
||||||
node_identities:
|
node_identities:
|
||||||
- name: node-1
|
- name: node-1
|
||||||
@@ -188,14 +179,16 @@
|
|||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
- result['role']['NodeIdentities'][0]['NodeName'] == "node-1"
|
- result.role.NodeIdentities.0.NodeName == "node-1"
|
||||||
- result['role']['NodeIdentities'][0]['Datacenter'] == "dc2"
|
- result.role.NodeIdentities.0.Datacenter == "dc2"
|
||||||
|
|
||||||
- name: Remove the last role
|
- name: Remove the last role
|
||||||
consul_role:
|
consul_role:
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
name: role-with-node-identity
|
name: role-with-node-identity
|
||||||
state: absent
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
- name: list sessions
|
- name: list sessions
|
||||||
consul_session:
|
consul_session:
|
||||||
state: list
|
state: list
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -18,7 +17,6 @@
|
|||||||
consul_session:
|
consul_session:
|
||||||
state: present
|
state: present
|
||||||
name: testsession
|
name: testsession
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -33,7 +31,6 @@
|
|||||||
- name: list sessions after creation
|
- name: list sessions after creation
|
||||||
consul_session:
|
consul_session:
|
||||||
state: list
|
state: list
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
@@ -61,7 +58,6 @@
|
|||||||
consul_session:
|
consul_session:
|
||||||
state: info
|
state: info
|
||||||
id: '{{ session_id }}'
|
id: '{{ session_id }}'
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -72,7 +68,6 @@
|
|||||||
consul_session:
|
consul_session:
|
||||||
state: info
|
state: info
|
||||||
name: test
|
name: test
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
@@ -80,70 +75,10 @@
|
|||||||
that:
|
that:
|
||||||
- result is failed
|
- result is failed
|
||||||
|
|
||||||
- name: ensure unknown scheme fails
|
|
||||||
consul_session:
|
|
||||||
state: info
|
|
||||||
id: '{{ session_id }}'
|
|
||||||
scheme: non_existent
|
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result is failed
|
|
||||||
|
|
||||||
- name: ensure SSL certificate is checked
|
|
||||||
consul_session:
|
|
||||||
state: info
|
|
||||||
id: '{{ session_id }}'
|
|
||||||
port: 8501
|
|
||||||
scheme: https
|
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: previous task should fail since certificate is not known
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result is failed
|
|
||||||
- "'certificate verify failed' in result.msg"
|
|
||||||
|
|
||||||
- name: ensure SSL certificate isn't checked when validate_certs is disabled
|
|
||||||
consul_session:
|
|
||||||
state: info
|
|
||||||
id: '{{ session_id }}'
|
|
||||||
port: 8501
|
|
||||||
scheme: https
|
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
validate_certs: false
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: previous task should succeed since certificate isn't checked
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result is changed
|
|
||||||
|
|
||||||
- name: ensure a secure connection is possible
|
|
||||||
consul_session:
|
|
||||||
state: info
|
|
||||||
id: '{{ session_id }}'
|
|
||||||
port: 8501
|
|
||||||
scheme: https
|
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
environment:
|
|
||||||
REQUESTS_CA_BUNDLE: '{{ remote_dir }}/cert.pem'
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result is changed
|
|
||||||
|
|
||||||
- name: delete a session
|
- name: delete a session
|
||||||
consul_session:
|
consul_session:
|
||||||
state: absent
|
state: absent
|
||||||
id: '{{ session_id }}'
|
id: '{{ session_id }}'
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -153,7 +88,6 @@
|
|||||||
- name: list sessions after deletion
|
- name: list sessions after deletion
|
||||||
consul_session:
|
consul_session:
|
||||||
state: list
|
state: list
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -180,7 +114,6 @@
|
|||||||
state: present
|
state: present
|
||||||
name: session-with-ttl
|
name: session-with-ttl
|
||||||
ttl: 180 # sec
|
ttl: 180 # sec
|
||||||
token: "{{ consul_management_token }}"
|
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|||||||
77
tests/integration/targets/consul/tasks/consul_token.yml
Normal file
77
tests/integration/targets/consul/tasks/consul_token.yml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||||
|
# 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: Create a policy with rules
|
||||||
|
community.general.consul_policy:
|
||||||
|
name: "{{ item }}"
|
||||||
|
rules: |
|
||||||
|
key "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
loop:
|
||||||
|
- foo-access
|
||||||
|
- foo-access2
|
||||||
|
|
||||||
|
- name: Create token
|
||||||
|
community.general.consul_token:
|
||||||
|
state: present
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
service_identities:
|
||||||
|
- service_name: test
|
||||||
|
datacenters: [test1, test2]
|
||||||
|
node_identities:
|
||||||
|
- node_name: test
|
||||||
|
datacenter: test
|
||||||
|
policies:
|
||||||
|
- name: foo-access
|
||||||
|
- name: foo-access2
|
||||||
|
expiration_ttl: 1h
|
||||||
|
register: create_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_result is changed
|
||||||
|
- create_result.token.AccessorID == "07a7de84-c9c7-448a-99cc-beaf682efd21"
|
||||||
|
- create_result.operation == 'create'
|
||||||
|
|
||||||
|
- name: Update token
|
||||||
|
community.general.consul_token:
|
||||||
|
state: present
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
description: Testing
|
||||||
|
policies:
|
||||||
|
- id: "{{ create_result.token.Policies[-1].ID }}"
|
||||||
|
service_identities: []
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.operation == 'update'
|
||||||
|
|
||||||
|
- name: Update token (noop)
|
||||||
|
community.general.consul_token:
|
||||||
|
state: present
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
policies:
|
||||||
|
- id: "{{ create_result.token.Policies[-1].ID }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.operation is not defined
|
||||||
|
|
||||||
|
- name: Remove token
|
||||||
|
community.general.consul_token:
|
||||||
|
state: absent
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- not result.token
|
||||||
|
- result.operation == 'remove'
|
||||||
@@ -77,21 +77,30 @@
|
|||||||
- name: Start Consul (dev mode enabled)
|
- name: Start Consul (dev mode enabled)
|
||||||
shell: nohup {{ consul_cmd }} agent -dev -config-file {{ remote_tmp_dir }}/consul_config.hcl </dev/null >/dev/null 2>&1 &
|
shell: nohup {{ consul_cmd }} agent -dev -config-file {{ remote_tmp_dir }}/consul_config.hcl </dev/null >/dev/null 2>&1 &
|
||||||
- name: Bootstrap ACL
|
- name: Bootstrap ACL
|
||||||
command: '{{ consul_cmd }} acl bootstrap --format=json'
|
consul_acl_bootstrap:
|
||||||
register: consul_bootstrap_result_string
|
register: consul_bootstrap_result
|
||||||
- set_fact:
|
- set_fact:
|
||||||
consul_management_token: '{{ consul_bootstrap_json_result["SecretID"] }}'
|
consul_management_token: '{{ consul_bootstrap_result.result.SecretID }}'
|
||||||
vars:
|
|
||||||
consul_bootstrap_json_result: '{{ consul_bootstrap_result_string.stdout | from_json }}'
|
|
||||||
- name: Create some data
|
- name: Create some data
|
||||||
command: '{{ consul_cmd }} kv put -token={{consul_management_token}} data/value{{ item }} foo{{ item }}'
|
command: '{{ consul_cmd }} kv put -token={{consul_management_token}} data/value{{ item }} foo{{ item }}'
|
||||||
loop:
|
loop:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
- import_tasks: consul_session.yml
|
- import_tasks: consul_general.yml
|
||||||
- import_tasks: consul_policy.yml
|
- import_tasks: consul_kv.yml
|
||||||
- import_tasks: consul_role.yml
|
|
||||||
|
- block:
|
||||||
|
- import_tasks: consul_session.yml
|
||||||
|
- import_tasks: consul_policy.yml
|
||||||
|
- import_tasks: consul_role.yml
|
||||||
|
- import_tasks: consul_token.yml
|
||||||
|
- import_tasks: consul_auth_method.yml
|
||||||
|
- import_tasks: consul_binding_rule.yml
|
||||||
|
module_defaults:
|
||||||
|
group/community.general.consul:
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Kill consul process
|
- name: Kill consul process
|
||||||
shell: kill $(cat {{ remote_tmp_dir }}/consul.pid)
|
shell: kill $(cat {{ remote_tmp_dir }}/consul.pid)
|
||||||
|
|||||||
19
tests/integration/targets/gitlab_label/README.md
Normal file
19
tests/integration/targets/gitlab_label/README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Gitlab integration tests
|
||||||
|
|
||||||
|
1. to run integration tests locally, I've setup a podman pod with both gitlab-ee image and the testing image
|
||||||
|
2. gitlab's related information were taken from [here](https://docs.gitlab.com/ee/install/docker.html), about the variable it needs (hostname, ports, volumes); volumes were pre-made before launching the image
|
||||||
|
3. image that run integration tests is started with `podman run --rm -it --pod <pod_name> --name <image_name> --network=host --volume <path_to_git_repo>/ansible_community/community.general:<container_path_to>/workspace/ansible_collections/community/general quay.io/ansible/azure-pipelines-test-container:4.0.1`
|
||||||
|
4. into the testing image, run
|
||||||
|
```sh
|
||||||
|
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||||
|
cd <container_path_to>/workspace/ansible_collections/community/general
|
||||||
|
ansible-test integration gitlab_label -vvv
|
||||||
|
```
|
||||||
|
|
||||||
|
While debugging with `q` package, open a second terminal and run `podman exec -it <image_name> /bin/bash` and inside it do `tail -f /tmp/q` .
|
||||||
6
tests/integration/targets/gitlab_label/aliases
Normal file
6
tests/integration/targets/gitlab_label/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
|
||||||
|
|
||||||
|
gitlab/ci
|
||||||
|
disabled
|
||||||
17
tests/integration/targets/gitlab_label/defaults/main.yml
Normal file
17
tests/integration/targets/gitlab_label/defaults/main.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
# 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
|
||||||
|
|
||||||
|
gitlab_project_name: ansible_test_project
|
||||||
|
gitlab_host: ansible_test_host
|
||||||
|
gitlab_api_token: ansible_test_api_token
|
||||||
|
gitlab_project_group: ansible_test_group
|
||||||
|
gitlab_branch: ansible_test_branch
|
||||||
|
gitlab_first_label: ansible_test_label
|
||||||
|
gitlab_first_label_color: "#112233"
|
||||||
|
gitlab_first_label_description: "label description"
|
||||||
|
gitlab_first_label_priority: 10
|
||||||
|
gitlab_second_label: ansible_test_second_label
|
||||||
|
gitlab_second_label_color: "#445566"
|
||||||
|
gitlab_second_label_new_name: ansible_test_second_label_new_name
|
||||||
463
tests/integration/targets/gitlab_label/tasks/main.yml
Normal file
463
tests/integration/targets/gitlab_label/tasks/main.yml
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# 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: Install required libs
|
||||||
|
pip:
|
||||||
|
name: python-gitlab
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- block:
|
||||||
|
###
|
||||||
|
### Group label
|
||||||
|
###
|
||||||
|
|
||||||
|
- name: Create {{ gitlab_project_group }}
|
||||||
|
gitlab_group:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_group }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Purge all group labels for check_mode test
|
||||||
|
gitlab_label:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Group label - Add a label in check_mode
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
check_mode: true
|
||||||
|
register: gitlab_group_label_state
|
||||||
|
|
||||||
|
- name: Group label - Check_mode state must be changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_state is changed
|
||||||
|
|
||||||
|
- name: Group label - Create label {{ gitlab_first_label }} and {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_first_label_color }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_create
|
||||||
|
|
||||||
|
- name: Group label - Test Label Created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_create is changed
|
||||||
|
- gitlab_group_label_create.labels.added|length == 2
|
||||||
|
- gitlab_group_label_create.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_create.labels.removed|length == 0
|
||||||
|
- gitlab_group_label_create.labels.updated|length == 0
|
||||||
|
- gitlab_group_label_create.labels.added[0] == "{{ gitlab_first_label }}"
|
||||||
|
- gitlab_group_label_create.labels.added[1] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Group label - Create Label ( Idempotency test )
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_first_label_color }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_create_idempotence
|
||||||
|
|
||||||
|
- name: Group label - Test Create Label is Idempotent
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_create_idempotence is not changed
|
||||||
|
|
||||||
|
- name: Group label - Update Label {{ gitlab_first_label }} changing color
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_update
|
||||||
|
|
||||||
|
- name: Group label - Test Label Updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_update.labels.added|length == 0
|
||||||
|
- gitlab_group_label_update.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_update.labels.removed|length == 0
|
||||||
|
- gitlab_group_label_update.labels.updated|length == 1
|
||||||
|
- gitlab_group_label_update.labels.updated[0] == "{{ gitlab_first_label }}"
|
||||||
|
|
||||||
|
- name: Group label - Change label {{ gitlab_second_label }} name to {{ gitlab_second_label_new_name }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
new_name: "{{ gitlab_second_label_new_name }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_new_name
|
||||||
|
|
||||||
|
- name: Group label - Test Label name changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_new_name.labels.added|length == 0
|
||||||
|
- gitlab_group_label_new_name.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_new_name.labels.removed|length == 0
|
||||||
|
- gitlab_group_label_new_name.labels.updated|length == 1
|
||||||
|
- gitlab_group_label_new_name.labels.updated[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Group label - Change label name back to {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label_new_name }}"
|
||||||
|
new_name: "{{ gitlab_second_label }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_orig_name
|
||||||
|
|
||||||
|
- name: Group label - Test Label name changed back
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_orig_name.labels.added|length == 0
|
||||||
|
- gitlab_group_label_orig_name.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_orig_name.labels.removed|length == 0
|
||||||
|
- gitlab_group_label_orig_name.labels.updated|length == 1
|
||||||
|
- gitlab_group_label_orig_name.labels.updated[0] == "{{ gitlab_second_label_new_name }}"
|
||||||
|
|
||||||
|
- name: Group label - Update Label Test ( Additions )
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_update_additions
|
||||||
|
|
||||||
|
- name: Group label - Test Label Updated ( Additions )
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_update_additions.labels.added|length == 0
|
||||||
|
- gitlab_group_label_update_additions.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_update_additions.labels.removed|length == 0
|
||||||
|
- gitlab_group_label_update_additions.labels.updated|length == 1
|
||||||
|
- gitlab_group_label_update_additions.labels.updated[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Group label - Delete Label {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_group_label_delete
|
||||||
|
|
||||||
|
- name: Group label - Test label is deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_delete is changed
|
||||||
|
- gitlab_group_label_delete.labels.added|length == 0
|
||||||
|
- gitlab_group_label_delete.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_delete.labels.removed|length == 1
|
||||||
|
- gitlab_group_label_delete.labels.updated|length == 0
|
||||||
|
- gitlab_group_label_delete.labels.removed[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Group label - Create label {{ gitlab_second_label }} again purging the other
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_label_create_purging
|
||||||
|
|
||||||
|
- name: Group label - Test Label Created again
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_label_create_purging is changed
|
||||||
|
- gitlab_group_label_create_purging.labels.added|length == 1
|
||||||
|
- gitlab_group_label_create_purging.labels.untouched|length == 0
|
||||||
|
- gitlab_group_label_create_purging.labels.removed|length == 1
|
||||||
|
- gitlab_group_label_create_purging.labels.updated|length == 0
|
||||||
|
- gitlab_group_label_create_purging.labels.added[0] == "{{ gitlab_second_label }}"
|
||||||
|
- gitlab_group_label_create_purging.labels.removed[0] == "{{ gitlab_first_label }}"
|
||||||
|
|
||||||
|
###
|
||||||
|
### Project label
|
||||||
|
###
|
||||||
|
|
||||||
|
- name: Create {{ gitlab_project_name }}
|
||||||
|
gitlab_project:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_name }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
default_branch: "{{ gitlab_branch }}"
|
||||||
|
initialize_with_readme: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Purge all labels for check_mode test
|
||||||
|
gitlab_label:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Add a label in check_mode
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
check_mode: true
|
||||||
|
register: gitlab_first_label_state
|
||||||
|
|
||||||
|
- name: Check_mode state must be changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_state is changed
|
||||||
|
|
||||||
|
- name: Create label {{ gitlab_first_label }} and {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_first_label_color }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_create
|
||||||
|
|
||||||
|
- name: Test Label Created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_create is changed
|
||||||
|
- gitlab_first_label_create.labels.added|length == 2
|
||||||
|
- gitlab_first_label_create.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_create.labels.removed|length == 0
|
||||||
|
- gitlab_first_label_create.labels.updated|length == 0
|
||||||
|
- gitlab_first_label_create.labels.added[0] == "{{ gitlab_first_label }}"
|
||||||
|
- gitlab_first_label_create.labels.added[1] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Create Label ( Idempotency test )
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_first_label_color }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_create_idempotence
|
||||||
|
|
||||||
|
- name: Test Create Label is Idempotent
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_create_idempotence is not changed
|
||||||
|
|
||||||
|
- name: Update Label {{ gitlab_first_label }} changing color
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_update
|
||||||
|
|
||||||
|
- name: Test Label Updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_update.labels.added|length == 0
|
||||||
|
- gitlab_first_label_update.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_update.labels.removed|length == 0
|
||||||
|
- gitlab_first_label_update.labels.updated|length == 1
|
||||||
|
- gitlab_first_label_update.labels.updated[0] == "{{ gitlab_first_label }}"
|
||||||
|
|
||||||
|
- name: Change label {{ gitlab_second_label }} name to {{ gitlab_second_label_new_name }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
new_name: "{{ gitlab_second_label_new_name }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_new_name
|
||||||
|
|
||||||
|
- name: Test Label name changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_new_name.labels.added|length == 0
|
||||||
|
- gitlab_first_label_new_name.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_new_name.labels.removed|length == 0
|
||||||
|
- gitlab_first_label_new_name.labels.updated|length == 1
|
||||||
|
- gitlab_first_label_new_name.labels.updated[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Change label name back to {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label_new_name }}"
|
||||||
|
new_name: "{{ gitlab_second_label }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_orig_name
|
||||||
|
|
||||||
|
- name: Test Label name changed back
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_orig_name.labels.added|length == 0
|
||||||
|
- gitlab_first_label_orig_name.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_orig_name.labels.removed|length == 0
|
||||||
|
- gitlab_first_label_orig_name.labels.updated|length == 1
|
||||||
|
- gitlab_first_label_orig_name.labels.updated[0] == "{{ gitlab_second_label_new_name }}"
|
||||||
|
|
||||||
|
- name: Update Label Test ( Additions )
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
description: "{{ gitlab_first_label_description }}"
|
||||||
|
priority: "{{ gitlab_first_label_priority }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_update_additions
|
||||||
|
|
||||||
|
- name: Test Label Updated ( Additions )
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_update_additions.labels.added|length == 0
|
||||||
|
- gitlab_first_label_update_additions.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_update_additions.labels.removed|length == 0
|
||||||
|
- gitlab_first_label_update_additions.labels.updated|length == 1
|
||||||
|
- gitlab_first_label_update_additions.labels.updated[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Delete Label {{ gitlab_second_label }}
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_first_label_delete
|
||||||
|
|
||||||
|
- name: Test label is deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_delete is changed
|
||||||
|
- gitlab_first_label_delete.labels.added|length == 0
|
||||||
|
- gitlab_first_label_delete.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_delete.labels.removed|length == 1
|
||||||
|
- gitlab_first_label_delete.labels.updated|length == 0
|
||||||
|
- gitlab_first_label_delete.labels.removed[0] == "{{ gitlab_second_label }}"
|
||||||
|
|
||||||
|
- name: Create label {{ gitlab_second_label }} again purging the other
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
color: "{{ gitlab_second_label_color }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_label_create_purging
|
||||||
|
|
||||||
|
- name: Test Label Created again
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_create_purging is changed
|
||||||
|
- gitlab_first_label_create_purging.labels.added|length == 1
|
||||||
|
- gitlab_first_label_create_purging.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_create_purging.labels.removed|length == 1
|
||||||
|
- gitlab_first_label_create_purging.labels.updated|length == 0
|
||||||
|
- gitlab_first_label_create_purging.labels.added[0] == "{{ gitlab_second_label }}"
|
||||||
|
- gitlab_first_label_create_purging.labels.removed[0] == "{{ gitlab_first_label }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Delete Labels
|
||||||
|
gitlab_label:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
labels:
|
||||||
|
- name: "{{ gitlab_first_label }}"
|
||||||
|
- name: "{{ gitlab_second_label }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_first_label_always_delete
|
||||||
|
|
||||||
|
- name: Test label are deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_label_always_delete is changed
|
||||||
|
- gitlab_first_label_always_delete.labels.added|length == 0
|
||||||
|
- gitlab_first_label_always_delete.labels.untouched|length == 0
|
||||||
|
- gitlab_first_label_always_delete.labels.removed|length > 0
|
||||||
|
- gitlab_first_label_always_delete.labels.updated|length == 0
|
||||||
|
|
||||||
|
- name: Clean up {{ gitlab_project_name }}
|
||||||
|
gitlab_project:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: false
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_name }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Clean up {{ gitlab_project_group }}
|
||||||
|
gitlab_group:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_group }}"
|
||||||
|
state: absent
|
||||||
19
tests/integration/targets/gitlab_milestone/README.md
Normal file
19
tests/integration/targets/gitlab_milestone/README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Gitlab integration tests
|
||||||
|
|
||||||
|
1. to run integration tests locally, I've setup a podman pod with both gitlab-ee image and the testing image
|
||||||
|
2. gitlab's related information were taken from [here](https://docs.gitlab.com/ee/install/docker.html), about the variable it needs (hostname, ports, volumes); volumes were pre-made before launching the image
|
||||||
|
3. image that run integration tests is started with `podman run --rm -it --pod <pod_name> --name <image_name> --network=host --volume <path_to_git_repo>/ansible_community/community.general:<container_path_to>/workspace/ansible_collections/community/general quay.io/ansible/azure-pipelines-test-container:4.0.1`
|
||||||
|
4. into the testing image, run
|
||||||
|
```sh
|
||||||
|
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||||
|
cd <container_path_to>/workspace/ansible_collections/community/general
|
||||||
|
ansible-test integration gitlab_milestone -vvv
|
||||||
|
```
|
||||||
|
|
||||||
|
While debugging with `q` package, open a second terminal and run `podman exec -it <image_name> /bin/bash` and inside it do `tail -f /tmp/q` .
|
||||||
6
tests/integration/targets/gitlab_milestone/aliases
Normal file
6
tests/integration/targets/gitlab_milestone/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
|
||||||
|
|
||||||
|
gitlab/ci
|
||||||
|
disabled
|
||||||
18
tests/integration/targets/gitlab_milestone/defaults/main.yml
Normal file
18
tests/integration/targets/gitlab_milestone/defaults/main.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
# 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
|
||||||
|
|
||||||
|
gitlab_project_name: ansible_test_project
|
||||||
|
gitlab_host: ansible_test_host
|
||||||
|
gitlab_api_token: ansible_test_api_token
|
||||||
|
gitlab_project_group: ansible_test_group
|
||||||
|
gitlab_branch: ansible_test_branch
|
||||||
|
gitlab_first_milestone: ansible_test_milestone
|
||||||
|
gitlab_first_milestone_description: "milestone description"
|
||||||
|
gitlab_first_milestone_start_date: "2024-01-01"
|
||||||
|
gitlab_first_milestone_due_date: "2024-12-31"
|
||||||
|
gitlab_first_milestone_new_start_date: "2024-05-01"
|
||||||
|
gitlab_first_milestone_new_due_date: "2024-10-31"
|
||||||
|
gitlab_second_milestone: ansible_test_second_milestone
|
||||||
|
gitlab_second_milestone_description: "new milestone"
|
||||||
388
tests/integration/targets/gitlab_milestone/tasks/main.yml
Normal file
388
tests/integration/targets/gitlab_milestone/tasks/main.yml
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# 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: Install required libs
|
||||||
|
pip:
|
||||||
|
name: python-gitlab
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- block:
|
||||||
|
###
|
||||||
|
### Group milestone
|
||||||
|
###
|
||||||
|
- name: Create {{ gitlab_project_group }}
|
||||||
|
gitlab_group:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_group }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Purge all group milestones for check_mode test
|
||||||
|
gitlab_milestone:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Group milestone - Add a milestone in check_mode
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
check_mode: true
|
||||||
|
register: gitlab_group_milestone_state
|
||||||
|
|
||||||
|
- name: Group milestone - Check_mode state must be changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_state is changed
|
||||||
|
|
||||||
|
- name: Purge all group milestones for project milestone test - cannot exist with same name
|
||||||
|
gitlab_milestone:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
register: gitlab_group_milestone_purged
|
||||||
|
|
||||||
|
- name: Group milestone - Create milestone {{ gitlab_first_milestone }} and {{ gitlab_second_milestone }}
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_due_date }}"
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_milestone_create
|
||||||
|
|
||||||
|
- name: Group milestone - Test milestone Created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_create is changed
|
||||||
|
- gitlab_group_milestone_create.milestones.added|length == 2
|
||||||
|
- gitlab_group_milestone_create.milestones.untouched|length == 0
|
||||||
|
- gitlab_group_milestone_create.milestones.removed|length == 0
|
||||||
|
- gitlab_group_milestone_create.milestones.updated|length == 0
|
||||||
|
- gitlab_group_milestone_create.milestones.added[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
- gitlab_group_milestone_create.milestones.added[1] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Group milestone - Create milestone ( Idempotency test )
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_due_date }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_milestone_create_idempotence
|
||||||
|
|
||||||
|
- name: Group milestone - Test Create milestone is Idempotent
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_create_idempotence is not changed
|
||||||
|
|
||||||
|
- name: Group milestone - Update milestone {{ gitlab_first_milestone }} changing dates
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_new_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_new_due_date }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_milestone_update
|
||||||
|
|
||||||
|
- name: Group milestone - Test milestone Updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_update.milestones.added|length == 0
|
||||||
|
- gitlab_group_milestone_update.milestones.untouched|length == 0
|
||||||
|
- gitlab_group_milestone_update.milestones.removed|length == 0
|
||||||
|
- gitlab_group_milestone_update.milestones.updated|length == 1
|
||||||
|
- gitlab_group_milestone_update.milestones.updated[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
|
||||||
|
- name: Group milestone - Update milestone Test ( Additions )
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
description: "{{ gitlab_first_milestone_description }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_milestone_update_additions
|
||||||
|
|
||||||
|
- name: Group milestone - Test milestone Updated ( Additions )
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_update_additions.milestones.added|length == 0
|
||||||
|
- gitlab_group_milestone_update_additions.milestones.untouched|length == 0
|
||||||
|
- gitlab_group_milestone_update_additions.milestones.removed|length == 0
|
||||||
|
- gitlab_group_milestone_update_additions.milestones.updated|length == 1
|
||||||
|
- gitlab_group_milestone_update_additions.milestones.updated[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Group milestone - Delete milestone {{ gitlab_second_milestone }}
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_group_milestone_delete
|
||||||
|
|
||||||
|
- name: Group milestone - Test milestone is deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_delete is changed
|
||||||
|
- gitlab_group_milestone_delete.milestones.added|length == 0
|
||||||
|
- gitlab_group_milestone_delete.milestones.untouched|length == 0
|
||||||
|
- gitlab_group_milestone_delete.milestones.removed|length == 1
|
||||||
|
- gitlab_group_milestone_delete.milestones.updated|length == 0
|
||||||
|
- gitlab_group_milestone_delete.milestones.removed[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Group milestone - Create group milestone {{ gitlab_second_milestone }} again purging the other
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_group_milestone_create_purging
|
||||||
|
|
||||||
|
- name: Group milestone - Test milestone Created again
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_group_milestone_create_purging is changed
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.added|length == 1
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.untouched|length == 0
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.removed|length == 1
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.updated|length == 0
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.added[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
- gitlab_group_milestone_create_purging.milestones.removed[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
|
||||||
|
###
|
||||||
|
### Project milestone
|
||||||
|
###
|
||||||
|
- name: Purge all group milestones for project milestone test - cannot exist with same name
|
||||||
|
gitlab_milestone:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
purge: true
|
||||||
|
register: gitlab_group_milestone_purged
|
||||||
|
|
||||||
|
- name: Create {{ gitlab_project_name }}
|
||||||
|
gitlab_project:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_name }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
default_branch: "{{ gitlab_branch }}"
|
||||||
|
initialize_with_readme: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Purge all milestones for check_mode test
|
||||||
|
gitlab_milestone:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
|
||||||
|
- name: Add a milestone in check_mode
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
description: "{{ gitlab_second_milestone_description }}"
|
||||||
|
check_mode: true
|
||||||
|
register: gitlab_first_milestone_state
|
||||||
|
|
||||||
|
- name: Check_mode state must be changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_state is changed
|
||||||
|
|
||||||
|
- name: Create milestone {{ gitlab_first_milestone }} and {{ gitlab_second_milestone }}
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_due_date }}"
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_milestones_create
|
||||||
|
|
||||||
|
- name: Test milestone Created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_milestones_create is changed
|
||||||
|
- gitlab_milestones_create.milestones.added|length == 2
|
||||||
|
- gitlab_milestones_create.milestones.untouched|length == 0
|
||||||
|
- gitlab_milestones_create.milestones.removed|length == 0
|
||||||
|
- gitlab_milestones_create.milestones.updated|length == 0
|
||||||
|
- gitlab_milestones_create.milestones.added[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
- gitlab_milestones_create.milestones.added[1] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Create milestone ( Idempotency test )
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_due_date }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_milestone_create_idempotence
|
||||||
|
|
||||||
|
- name: Test Create milestone is Idempotent
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_create_idempotence is not changed
|
||||||
|
|
||||||
|
- name: Update milestone {{ gitlab_first_milestone }} changing dates
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
start_date: "{{ gitlab_first_milestone_new_start_date }}"
|
||||||
|
due_date: "{{ gitlab_first_milestone_new_due_date }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_milestone_update
|
||||||
|
|
||||||
|
- name: Test milestone Updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_update.milestones.added|length == 0
|
||||||
|
- gitlab_first_milestone_update.milestones.untouched|length == 0
|
||||||
|
- gitlab_first_milestone_update.milestones.removed|length == 0
|
||||||
|
- gitlab_first_milestone_update.milestones.updated|length == 1
|
||||||
|
- gitlab_first_milestone_update.milestones.updated[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
|
||||||
|
- name: Update milestone Test ( Additions )
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
description: "{{ gitlab_second_milestone_description }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_milestone_update_additions
|
||||||
|
|
||||||
|
- name: Test milestone Updated ( Additions )
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_update_additions.milestones.added|length == 0
|
||||||
|
- gitlab_first_milestone_update_additions.milestones.untouched|length == 0
|
||||||
|
- gitlab_first_milestone_update_additions.milestones.removed|length == 0
|
||||||
|
- gitlab_first_milestone_update_additions.milestones.updated|length == 1
|
||||||
|
- gitlab_first_milestone_update_additions.milestones.updated[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Delete milestone {{ gitlab_second_milestone }}
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_first_milestone_delete
|
||||||
|
|
||||||
|
- name: Test milestone is deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_delete is changed
|
||||||
|
- gitlab_first_milestone_delete.milestones.added|length == 0
|
||||||
|
- gitlab_first_milestone_delete.milestones.untouched|length == 0
|
||||||
|
- gitlab_first_milestone_delete.milestones.removed|length == 1
|
||||||
|
- gitlab_first_milestone_delete.milestones.updated|length == 0
|
||||||
|
- gitlab_first_milestone_delete.milestones.removed[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
|
||||||
|
- name: Create milestone {{ gitlab_second_milestone }} again purging the other
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: present
|
||||||
|
register: gitlab_first_milestone_create_purging
|
||||||
|
|
||||||
|
- name: Test milestone Created again
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_create_purging is changed
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.added|length == 1
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.untouched|length == 0
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.removed|length == 1
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.updated|length == 0
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.added[0] == "{{ gitlab_second_milestone }}"
|
||||||
|
- gitlab_first_milestone_create_purging.milestones.removed[0] == "{{ gitlab_first_milestone }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Delete milestones
|
||||||
|
gitlab_milestone:
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}"
|
||||||
|
purge: true
|
||||||
|
milestones:
|
||||||
|
- title: "{{ gitlab_first_milestone }}"
|
||||||
|
- title: "{{ gitlab_second_milestone }}"
|
||||||
|
state: absent
|
||||||
|
register: gitlab_first_milestone_always_delete
|
||||||
|
|
||||||
|
- name: Test milestone are deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- gitlab_first_milestone_always_delete is changed
|
||||||
|
- gitlab_first_milestone_always_delete.milestones.added|length == 0
|
||||||
|
- gitlab_first_milestone_always_delete.milestones.untouched|length == 0
|
||||||
|
- gitlab_first_milestone_always_delete.milestones.removed|length > 0
|
||||||
|
- gitlab_first_milestone_always_delete.milestones.updated|length == 0
|
||||||
|
|
||||||
|
- name: Clean up {{ gitlab_project_name }}
|
||||||
|
gitlab_project:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: false
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_name }}"
|
||||||
|
group: "{{ gitlab_project_group }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Clean up {{ gitlab_project_group }}
|
||||||
|
gitlab_group:
|
||||||
|
api_url: "{{ gitlab_host }}"
|
||||||
|
validate_certs: true
|
||||||
|
api_token: "{{ gitlab_api_token }}"
|
||||||
|
name: "{{ gitlab_project_group }}"
|
||||||
|
state: absent
|
||||||
99
tests/integration/targets/homebrew/tasks/casks.yml
Normal file
99
tests/integration/targets/homebrew/tasks/casks.yml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
# Test code for the homebrew module.
|
||||||
|
# Copyright (c) 2020, Abhijeet Kasurde <akasurde@redhat.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
|
||||||
|
|
||||||
|
- name: Find brew binary
|
||||||
|
command: which brew
|
||||||
|
register: brew_which
|
||||||
|
when: ansible_distribution in ['MacOSX']
|
||||||
|
|
||||||
|
- name: Get owner of brew binary
|
||||||
|
stat:
|
||||||
|
path: "{{ brew_which.stdout }}"
|
||||||
|
register: brew_stat
|
||||||
|
when: ansible_distribution in ['MacOSX']
|
||||||
|
|
||||||
|
#- name: Use ignored-pinned option while upgrading all
|
||||||
|
# homebrew:
|
||||||
|
# upgrade_all: true
|
||||||
|
# upgrade_options: ignore-pinned
|
||||||
|
# become: true
|
||||||
|
# become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
# register: upgrade_option_result
|
||||||
|
# environment:
|
||||||
|
# HOMEBREW_NO_AUTO_UPDATE: True
|
||||||
|
|
||||||
|
#- assert:
|
||||||
|
# that:
|
||||||
|
# - upgrade_option_result.changed
|
||||||
|
|
||||||
|
- vars:
|
||||||
|
package_name: kitty
|
||||||
|
|
||||||
|
block:
|
||||||
|
- name: Make sure {{ package_name }} package is not installed
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
|
||||||
|
- name: Install {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: present
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- package_result.changed
|
||||||
|
|
||||||
|
- name: Again install {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: present
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not package_result.changed
|
||||||
|
|
||||||
|
- name: Uninstall {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- package_result.changed
|
||||||
|
|
||||||
|
- name: Again uninstall {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not package_result.changed
|
||||||
99
tests/integration/targets/homebrew/tasks/formulae.yml
Normal file
99
tests/integration/targets/homebrew/tasks/formulae.yml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
# Test code for the homebrew module.
|
||||||
|
# Copyright (c) 2020, Abhijeet Kasurde <akasurde@redhat.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
|
||||||
|
|
||||||
|
- name: Find brew binary
|
||||||
|
command: which brew
|
||||||
|
register: brew_which
|
||||||
|
when: ansible_distribution in ['MacOSX']
|
||||||
|
|
||||||
|
- name: Get owner of brew binary
|
||||||
|
stat:
|
||||||
|
path: "{{ brew_which.stdout }}"
|
||||||
|
register: brew_stat
|
||||||
|
when: ansible_distribution in ['MacOSX']
|
||||||
|
|
||||||
|
#- name: Use ignored-pinned option while upgrading all
|
||||||
|
# homebrew:
|
||||||
|
# upgrade_all: true
|
||||||
|
# upgrade_options: ignore-pinned
|
||||||
|
# become: true
|
||||||
|
# become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
# register: upgrade_option_result
|
||||||
|
# environment:
|
||||||
|
# HOMEBREW_NO_AUTO_UPDATE: True
|
||||||
|
|
||||||
|
#- assert:
|
||||||
|
# that:
|
||||||
|
# - upgrade_option_result.changed
|
||||||
|
|
||||||
|
- vars:
|
||||||
|
package_name: gnu-tar
|
||||||
|
|
||||||
|
block:
|
||||||
|
- name: Make sure {{ package_name }} package is not installed
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
|
||||||
|
- name: Install {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: present
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- package_result.changed
|
||||||
|
|
||||||
|
- name: Again install {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: present
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not package_result.changed
|
||||||
|
|
||||||
|
- name: Uninstall {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- package_result.changed
|
||||||
|
|
||||||
|
- name: Again uninstall {{ package_name }} package using homebrew
|
||||||
|
homebrew:
|
||||||
|
name: "{{ package_name }}"
|
||||||
|
state: absent
|
||||||
|
update_homebrew: false
|
||||||
|
become: true
|
||||||
|
become_user: "{{ brew_stat.stat.pw_name }}"
|
||||||
|
register: package_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not package_result.changed
|
||||||
@@ -9,91 +9,9 @@
|
|||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
- name: Find brew binary
|
- block:
|
||||||
command: which brew
|
- include_tasks: 'formulae.yml'
|
||||||
register: brew_which
|
|
||||||
when: ansible_distribution in ['MacOSX']
|
- when: ansible_distribution in ['MacOSX']
|
||||||
|
|
||||||
- name: Get owner of brew binary
|
|
||||||
stat:
|
|
||||||
path: "{{ brew_which.stdout }}"
|
|
||||||
register: brew_stat
|
|
||||||
when: ansible_distribution in ['MacOSX']
|
|
||||||
|
|
||||||
#- name: Use ignored-pinned option while upgrading all
|
|
||||||
# homebrew:
|
|
||||||
# upgrade_all: true
|
|
||||||
# upgrade_options: ignore-pinned
|
|
||||||
# become: true
|
|
||||||
# become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
# register: upgrade_option_result
|
|
||||||
# environment:
|
|
||||||
# HOMEBREW_NO_AUTO_UPDATE: True
|
|
||||||
|
|
||||||
#- assert:
|
|
||||||
# that:
|
|
||||||
# - upgrade_option_result.changed
|
|
||||||
|
|
||||||
- vars:
|
|
||||||
package_name: gnu-tar
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
- name: Make sure {{ package_name }} package is not installed
|
- include_tasks: 'casks.yml'
|
||||||
homebrew:
|
|
||||||
name: "{{ package_name }}"
|
|
||||||
state: absent
|
|
||||||
update_homebrew: false
|
|
||||||
become: true
|
|
||||||
become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
|
|
||||||
- name: Install {{ package_name }} package using homebrew
|
|
||||||
homebrew:
|
|
||||||
name: "{{ package_name }}"
|
|
||||||
state: present
|
|
||||||
update_homebrew: false
|
|
||||||
become: true
|
|
||||||
become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
register: package_result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- package_result.changed
|
|
||||||
|
|
||||||
- name: Again install {{ package_name }} package using homebrew
|
|
||||||
homebrew:
|
|
||||||
name: "{{ package_name }}"
|
|
||||||
state: present
|
|
||||||
update_homebrew: false
|
|
||||||
become: true
|
|
||||||
become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
register: package_result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- not package_result.changed
|
|
||||||
|
|
||||||
- name: Uninstall {{ package_name }} package using homebrew
|
|
||||||
homebrew:
|
|
||||||
name: "{{ package_name }}"
|
|
||||||
state: absent
|
|
||||||
update_homebrew: false
|
|
||||||
become: true
|
|
||||||
become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
register: package_result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- package_result.changed
|
|
||||||
|
|
||||||
- name: Again uninstall {{ package_name }} package using homebrew
|
|
||||||
homebrew:
|
|
||||||
name: "{{ package_name }}"
|
|
||||||
state: absent
|
|
||||||
update_homebrew: false
|
|
||||||
become: true
|
|
||||||
become_user: "{{ brew_stat.stat.pw_name }}"
|
|
||||||
register: package_result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- not package_result.changed
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
- name: Copy templated helper script
|
- name: Copy templated helper script
|
||||||
template:
|
template:
|
||||||
src: obtainpid.sh
|
src: obtainpid.sh.j2
|
||||||
dest: "{{ remote_tmp_dir }}/obtainpid.sh"
|
dest: "{{ remote_tmp_dir }}/obtainpid.sh"
|
||||||
mode: 0755
|
mode: 0755
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDAjCCAeqgAwIBAgIJANguFROhaWocMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV
|
MIIDAjCCAeqgAwIBAgIJANguFROhaWocMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV
|
||||||
BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE5
|
BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE5
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
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)
|
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
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
SPDX-FileCopyrightText: Ansible Project
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqVt84czSxWnWW
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqVt84czSxWnWW
|
||||||
4Ng6hmKE3NarbLsycwtjrYBokV7Kk7Mp7PrBbYF05FOgSdJLvL6grlRSQK2VPsXd
|
4Ng6hmKE3NarbLsycwtjrYBokV7Kk7Mp7PrBbYF05FOgSdJLvL6grlRSQK2VPsXd
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
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)
|
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
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
SPDX-FileCopyrightText: Ansible Project
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDRjCCAi6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH
|
MIIDRjCCAi6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH
|
||||||
ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz
|
ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEowIBAAKCAQEAqDPjkNxwpwlAAM/Shhk8FgfYUG1HwGV5v7LZW9v7jgKd6zcM
|
MIIEowIBAAKCAQEAqDPjkNxwpwlAAM/Shhk8FgfYUG1HwGV5v7LZW9v7jgKd6zcM
|
||||||
QJQrP4IspgRxOiLupqytNOlZ/mfYm6iKw9i7gjsXLtucvIKKhutk4HT+bGvcEfuf
|
QJQrP4IspgRxOiLupqytNOlZ/mfYm6iKw9i7gjsXLtucvIKKhutk4HT+bGvcEfuf
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDRjCCAi6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH
|
MIIDRjCCAi6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH
|
||||||
ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz
|
ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEowIBAAKCAQEAyMBKx8AHrEQX3fR4mZJgd1WIdvHNUJBPSPJ2MhySl9mQVIQM
|
MIIEowIBAAKCAQEAyMBKx8AHrEQX3fR4mZJgd1WIdvHNUJBPSPJ2MhySl9mQVIQM
|
||||||
yvofNAZHEySfeNuualsgAh/8JeeF3v6HxVBaxmuL89Ks+FJC/yiNDhsNvGOKpyna
|
yvofNAZHEySfeNuualsgAh/8JeeF3v6HxVBaxmuL89Ks+FJC/yiNDhsNvGOKpyna
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
when:
|
when:
|
||||||
- ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['Fedora31', 'Fedora32']
|
- ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['Fedora31', 'Fedora32']
|
||||||
- not (ansible_os_family == 'Alpine') # TODO
|
- not (ansible_os_family == 'Alpine') # TODO
|
||||||
|
- not (ansible_distribution == 'Archlinux') # TODO
|
||||||
block:
|
block:
|
||||||
- name: set timezone to Etc/UTC
|
- name: set timezone to Etc/UTC
|
||||||
timezone:
|
timezone:
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
state: present
|
state: present
|
||||||
when: ansible_os_family == "FreeBSD"
|
when: ansible_os_family == "FreeBSD"
|
||||||
|
|
||||||
|
- name: Install requirements (RHEL 8)
|
||||||
|
package:
|
||||||
|
name:
|
||||||
|
- libxml2-devel
|
||||||
|
- libxslt-devel
|
||||||
|
- python3-lxml
|
||||||
|
state: present
|
||||||
|
when: ansible_distribution == "RedHat" and ansible_distribution_major_version == "8"
|
||||||
|
|
||||||
# Needed for MacOSX !
|
# Needed for MacOSX !
|
||||||
- name: Install lxml
|
- name: Install lxml
|
||||||
pip:
|
pip:
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
|
|
||||||
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
|
|
||||||
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
|
|
||||||
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
|
||||||
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
|
||||||
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
|
||||||
plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin
|
|
||||||
plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen
|
|
||||||
plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/parted.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/read_csv.py validate-modules:invalid-documentation
|
|
||||||
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/xfconf.py validate-modules:return-syntax-error
|
|
||||||
tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.6 # django generated code
|
|
||||||
tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.7 # django generated code
|
|
||||||
tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
|
||||||
plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin
|
|
||||||
plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen
|
|
||||||
plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/parted.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0
|
|
||||||
plugins/modules/read_csv.py validate-modules:invalid-documentation
|
|
||||||
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
|
|
||||||
plugins/modules/xfconf.py validate-modules:return-syntax-error
|
|
||||||
tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes
|
|
||||||
@@ -104,7 +104,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
{
|
{
|
||||||
'method': 'otptoken_add',
|
'method': 'otptoken_add',
|
||||||
'name': 'NewToken1',
|
'name': 'NewToken1',
|
||||||
'item': {'ipatokendisabled': 'FALSE',
|
'item': {'ipatokendisabled': False,
|
||||||
'all': True}
|
'all': True}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -130,7 +130,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
{
|
{
|
||||||
'method': 'otptoken_add',
|
'method': 'otptoken_add',
|
||||||
'name': 'NewToken1',
|
'name': 'NewToken1',
|
||||||
'item': {'ipatokendisabled': 'FALSE',
|
'item': {'ipatokendisabled': False,
|
||||||
'all': True}
|
'all': True}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -176,7 +176,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': 'KRSXG5CTMVRXEZLUGE======',
|
'ipatokenotpkey': 'KRSXG5CTMVRXEZLUGE======',
|
||||||
'description': 'Test description',
|
'description': 'Test description',
|
||||||
'ipatokenowner': 'pinky',
|
'ipatokenowner': 'pinky',
|
||||||
'ipatokendisabled': 'FALSE',
|
'ipatokendisabled': False,
|
||||||
'ipatokennotbefore': '20200101010101Z',
|
'ipatokennotbefore': '20200101010101Z',
|
||||||
'ipatokennotafter': '20900101010101Z',
|
'ipatokennotafter': '20900101010101Z',
|
||||||
'ipatokenvendor': 'Acme',
|
'ipatokenvendor': 'Acme',
|
||||||
@@ -220,7 +220,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
||||||
'description': ['Test description'],
|
'description': ['Test description'],
|
||||||
'ipatokenowner': ['pinky'],
|
'ipatokenowner': ['pinky'],
|
||||||
'ipatokendisabled': ['FALSE'],
|
'ipatokendisabled': [False],
|
||||||
'ipatokennotbefore': ['20200101010101Z'],
|
'ipatokennotbefore': ['20200101010101Z'],
|
||||||
'ipatokennotafter': ['20900101010101Z'],
|
'ipatokennotafter': ['20900101010101Z'],
|
||||||
'ipatokenvendor': ['Acme'],
|
'ipatokenvendor': ['Acme'],
|
||||||
@@ -271,7 +271,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
||||||
'description': ['Test description'],
|
'description': ['Test description'],
|
||||||
'ipatokenowner': ['pinky'],
|
'ipatokenowner': ['pinky'],
|
||||||
'ipatokendisabled': ['FALSE'],
|
'ipatokendisabled': [False],
|
||||||
'ipatokennotbefore': ['20200101010101Z'],
|
'ipatokennotbefore': ['20200101010101Z'],
|
||||||
'ipatokennotafter': ['20900101010101Z'],
|
'ipatokennotafter': ['20900101010101Z'],
|
||||||
'ipatokenvendor': ['Acme'],
|
'ipatokenvendor': ['Acme'],
|
||||||
@@ -296,7 +296,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'name': 'NewToken1',
|
'name': 'NewToken1',
|
||||||
'item': {'description': 'Test description',
|
'item': {'description': 'Test description',
|
||||||
'ipatokenowner': 'brain',
|
'ipatokenowner': 'brain',
|
||||||
'ipatokendisabled': 'FALSE',
|
'ipatokendisabled': False,
|
||||||
'ipatokennotbefore': '20200101010101Z',
|
'ipatokennotbefore': '20200101010101Z',
|
||||||
'ipatokennotafter': '20900101010101Z',
|
'ipatokennotafter': '20900101010101Z',
|
||||||
'ipatokenvendor': 'Acme',
|
'ipatokenvendor': 'Acme',
|
||||||
@@ -335,7 +335,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}],
|
||||||
'description': ['Test description'],
|
'description': ['Test description'],
|
||||||
'ipatokenowner': ['pinky'],
|
'ipatokenowner': ['pinky'],
|
||||||
'ipatokendisabled': ['FALSE'],
|
'ipatokendisabled': [False],
|
||||||
'ipatokennotbefore': ['20200101010101Z'],
|
'ipatokennotbefore': ['20200101010101Z'],
|
||||||
'ipatokennotafter': ['20900101010101Z'],
|
'ipatokennotafter': ['20900101010101Z'],
|
||||||
'ipatokenvendor': ['Acme'],
|
'ipatokenvendor': ['Acme'],
|
||||||
@@ -360,7 +360,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'name': 'NewToken1',
|
'name': 'NewToken1',
|
||||||
'item': {'description': 'New Test description',
|
'item': {'description': 'New Test description',
|
||||||
'ipatokenowner': 'pinky',
|
'ipatokenowner': 'pinky',
|
||||||
'ipatokendisabled': 'TRUE',
|
'ipatokendisabled': True,
|
||||||
'ipatokennotbefore': '20200101010102Z',
|
'ipatokennotbefore': '20200101010102Z',
|
||||||
'ipatokennotafter': '20900101010102Z',
|
'ipatokennotafter': '20900101010102Z',
|
||||||
'ipatokenvendor': 'NewAcme',
|
'ipatokenvendor': 'NewAcme',
|
||||||
@@ -384,7 +384,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}],
|
'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}],
|
||||||
'description': ['Test description'],
|
'description': ['Test description'],
|
||||||
'ipatokenowner': ['pinky'],
|
'ipatokenowner': ['pinky'],
|
||||||
'ipatokendisabled': ['FALSE'],
|
'ipatokendisabled': [False],
|
||||||
'ipatokennotbefore': ['20200101010101Z'],
|
'ipatokennotbefore': ['20200101010101Z'],
|
||||||
'ipatokennotafter': ['20900101010101Z'],
|
'ipatokennotafter': ['20900101010101Z'],
|
||||||
'ipatokenvendor': ['Acme'],
|
'ipatokenvendor': ['Acme'],
|
||||||
@@ -425,7 +425,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}],
|
'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}],
|
||||||
'description': ['Test description'],
|
'description': ['Test description'],
|
||||||
'ipatokenowner': ['pinky'],
|
'ipatokenowner': ['pinky'],
|
||||||
'ipatokendisabled': ['FALSE'],
|
'ipatokendisabled': [False],
|
||||||
'ipatokennotbefore': ['20200101010101Z'],
|
'ipatokennotbefore': ['20200101010101Z'],
|
||||||
'ipatokennotafter': ['20900101010101Z'],
|
'ipatokennotafter': ['20900101010101Z'],
|
||||||
'ipatokenvendor': ['Acme'],
|
'ipatokenvendor': ['Acme'],
|
||||||
@@ -448,7 +448,7 @@ class TestIPAOTPToken(ModuleTestCase):
|
|||||||
{
|
{
|
||||||
'method': 'otptoken_mod',
|
'method': 'otptoken_mod',
|
||||||
'name': 'NewToken1',
|
'name': 'NewToken1',
|
||||||
'item': {'ipatokendisabled': 'TRUE',
|
'item': {'ipatokendisabled': True,
|
||||||
'all': True}
|
'all': True}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ python-memcached ; python_version >= '3.6'
|
|||||||
|
|
||||||
# requirement for the redis cache plugin
|
# requirement for the redis cache plugin
|
||||||
redis
|
redis
|
||||||
|
async-timeout ; python_version == '3.11'
|
||||||
|
|
||||||
# requirement for the linode module
|
# requirement for the linode module
|
||||||
linode-python # APIv3
|
linode-python # APIv3
|
||||||
|
|||||||
@@ -65,16 +65,7 @@ else
|
|||||||
retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check
|
retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${SHIPPABLE_BUILD_ID:-}" ]; then
|
export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../"
|
||||||
export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible"
|
|
||||||
SHIPPABLE_RESULT_DIR="$(pwd)/shippable"
|
|
||||||
TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general"
|
|
||||||
mkdir -p "${TEST_DIR}"
|
|
||||||
cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}"
|
|
||||||
cd "${TEST_DIR}"
|
|
||||||
else
|
|
||||||
export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${test}" == "sanity/extra" ]; then
|
if [ "${test}" == "sanity/extra" ]; then
|
||||||
retry pip install junit-xml --disable-pip-version-check
|
retry pip install junit-xml --disable-pip-version-check
|
||||||
|
|||||||
Reference in New Issue
Block a user