mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #7878/29f98654 backport][stable-8] Add new consul modules and reuse code between them. (#7902)
Add new consul modules and reuse code between them. (#7878)
Refactored consul modules and added new roles.
(cherry picked from commit 29f9865497
)
Co-authored-by: Florian Apolloner <florian@apolloner.eu>
This commit is contained in:
parent
1ee2bba140
commit
0a904d60cd
17 changed files with 1508 additions and 568 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -1494,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
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
minor_changes:
|
minor_changes:
|
||||||
- 'consul_policy, consul_role, consul_session - removed dependency on ``requests`` and factored out common parts (https://github.com/ansible-collections/community.general/pull/7826).'
|
- '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_policy - added support for diff and check mode (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).
|
||||||
|
- 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 - ``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).
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,12 +34,16 @@ options:
|
||||||
description:
|
description:
|
||||||
- Whether to verify the TLS certificate of the consul agent.
|
- Whether to verify the TLS certificate of the consul agent.
|
||||||
default: true
|
default: true
|
||||||
token:
|
|
||||||
description:
|
|
||||||
- The token to use for authorization.
|
|
||||||
type: str
|
|
||||||
ca_path:
|
ca_path:
|
||||||
description:
|
description:
|
||||||
- The CA bundle to use for https connections
|
- The CA bundle to use for https connections
|
||||||
type: str
|
type: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TOKEN = r"""
|
||||||
|
options:
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- The token to use for authorization.
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
|
@ -5,8 +5,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
|
||||||
|
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
||||||
|
@ -15,35 +17,84 @@ 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))
|
||||||
|
|
||||||
|
|
||||||
def auth_argument_spec():
|
AUTH_ARGUMENTS_SPEC = dict(
|
||||||
return dict(
|
host=dict(default="localhost"),
|
||||||
host=dict(default="localhost"),
|
port=dict(type="int", default=8500),
|
||||||
port=dict(type="int", default=8500),
|
scheme=dict(default="http"),
|
||||||
scheme=dict(default="http"),
|
validate_certs=dict(type="bool", default=True),
|
||||||
validate_certs=dict(type="bool", default=True),
|
token=dict(no_log=True),
|
||||||
token=dict(no_log=True),
|
ca_path=dict(),
|
||||||
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:
|
class _ConsulModule:
|
||||||
|
@ -53,13 +104,160 @@ class _ConsulModule:
|
||||||
As such backwards incompatible changes can occur even in bugfix releases.
|
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):
|
def __init__(self, module):
|
||||||
self.module = 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):
|
def _request(self, method, url_parts, data=None, params=None):
|
||||||
module_params = self.module.params
|
module_params = self.params
|
||||||
|
|
||||||
if isinstance(url_parts, str):
|
if not isinstance(url_parts, (tuple, list)):
|
||||||
url_parts = [url_parts]
|
url_parts = [url_parts]
|
||||||
if params:
|
if params:
|
||||||
# Remove values that are None
|
# Remove values that are None
|
||||||
|
@ -74,7 +272,7 @@ class _ConsulModule:
|
||||||
url = "/".join([base_url] + list(url_parts))
|
url = "/".join([base_url] + list(url_parts))
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
token = self.module.params.get("token")
|
token = self.params.get("token")
|
||||||
if token:
|
if token:
|
||||||
headers["X-Consul-Token"] = token
|
headers["X-Consul-Token"] = token
|
||||||
|
|
||||||
|
@ -93,19 +291,25 @@ class _ConsulModule:
|
||||||
ca_path=ca_path,
|
ca_path=ca_path,
|
||||||
)
|
)
|
||||||
response_data = response.read()
|
response_data = response.read()
|
||||||
except urllib_error.URLError as e:
|
|
||||||
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))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
status = (
|
status = (
|
||||||
response.status if hasattr(response, "status") else response.getcode()
|
response.status if hasattr(response, "status") else response.getcode()
|
||||||
)
|
)
|
||||||
if 400 <= status < 600:
|
|
||||||
raise RequestError("%d %s" % (status, response_data))
|
|
||||||
|
|
||||||
return json.loads(response_data)
|
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):
|
def get(self, url_parts, **kwargs):
|
||||||
return self._request("GET", url_parts, **kwargs)
|
return self._request("GET", url_parts, **kwargs)
|
||||||
|
|
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()
|
206
plugins/modules/consul_auth_method.py
Normal file
206
plugins/modules/consul_auth_method.py
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
#!/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.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()
|
182
plugins/modules/consul_binding_rule.py
Normal file
182
plugins/modules/consul_binding_rule.py
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
#!/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.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
|
||||||
|
@ -20,12 +21,17 @@ author:
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.consul
|
- community.general.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:
|
||||||
|
@ -36,7 +42,6 @@ options:
|
||||||
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:
|
||||||
|
@ -49,12 +54,11 @@ options:
|
||||||
description:
|
description:
|
||||||
- Description of the policy.
|
- Description of the policy.
|
||||||
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.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Create a policy with rules
|
- name: Create a policy with rules
|
||||||
|
@ -95,8 +99,24 @@ 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
|
||||||
|
@ -104,146 +124,39 @@ operation:
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
_ConsulModule, auth_argument_spec)
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
OPERATION_READ,
|
||||||
NAME_PARAMETER_NAME = "name"
|
_ConsulModule,
|
||||||
DESCRIPTION_PARAMETER_NAME = "description"
|
)
|
||||||
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"),
|
||||||
RULES_PARAMETER_NAME: dict(type='str'),
|
"rules": dict(type="str"),
|
||||||
VALID_DATACENTERS_PARAMETER_NAME: dict(type='list', elements='str', default=[]),
|
"valid_datacenters": dict(type="list", elements="str"),
|
||||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE])
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
}
|
}
|
||||||
_ARGUMENT_SPEC.update(auth_argument_spec())
|
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||||
|
|
||||||
|
|
||||||
def update_policy(policy, configuration, consul_module):
|
class ConsulPolicyModule(_ConsulModule):
|
||||||
updated_policy = consul_module.put(('acl', 'policy', policy['ID']), data={
|
api_endpoint = "acl/policy"
|
||||||
'Name': configuration.name, # should be equal at this point.
|
result_key = "policy"
|
||||||
'Description': configuration.description,
|
unique_identifier = "id"
|
||||||
'Rules': configuration.rules,
|
|
||||||
'Datacenters': configuration.valid_datacenters
|
|
||||||
})
|
|
||||||
|
|
||||||
changed = (
|
def endpoint_url(self, operation, identifier=None):
|
||||||
policy.get('Rules', "") != updated_policy.get('Rules', "") or
|
if operation == OPERATION_READ:
|
||||||
policy.get('Description', "") != updated_policy.get('Description', "") or
|
return [self.api_endpoint, "name", self.params["name"]]
|
||||||
policy.get('Datacenters', []) != updated_policy.get('Datacenters', [])
|
return super(ConsulPolicyModule, self).endpoint_url(operation, identifier)
|
||||||
)
|
|
||||||
|
|
||||||
return Output(changed=changed, operation=UPDATE_OPERATION, policy=updated_policy)
|
|
||||||
|
|
||||||
|
|
||||||
def create_policy(configuration, consul_module):
|
|
||||||
created_policy = consul_module.put('acl/policy', data={
|
|
||||||
'Name': configuration.name,
|
|
||||||
'Description': configuration.description,
|
|
||||||
'Rules': configuration.rules,
|
|
||||||
'Datacenters': configuration.valid_datacenters
|
|
||||||
})
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION, policy=created_policy)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_policy(configuration, consul_module):
|
|
||||||
policies = get_policies(consul_module)
|
|
||||||
|
|
||||||
if configuration.name in policies:
|
|
||||||
|
|
||||||
policy_id = policies[configuration.name]['ID']
|
|
||||||
policy = get_policy(policy_id, consul_module)
|
|
||||||
consul_module.delete(('acl', 'policy', policy['ID']))
|
|
||||||
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
changed = False
|
|
||||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def get_policies(consul_module):
|
|
||||||
policies = consul_module.get('acl/policies')
|
|
||||||
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, consul_module):
|
|
||||||
return consul_module.get(('acl', 'policy', id))
|
|
||||||
|
|
||||||
|
|
||||||
def set_policy(configuration, consul_module):
|
|
||||||
policies = get_policies(consul_module)
|
|
||||||
|
|
||||||
if configuration.name in policies:
|
|
||||||
index_policy_object = policies[configuration.name]
|
|
||||||
policy_id = policies[configuration.name]['ID']
|
|
||||||
rest_policy_object = get_policy(policy_id, consul_module)
|
|
||||||
# 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, consul_module)
|
|
||||||
else:
|
|
||||||
return create_policy(configuration, consul_module)
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration:
|
|
||||||
"""
|
|
||||||
Configuration for this module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name=None, description=None, rules=None, valid_datacenters=None, state=None):
|
|
||||||
self.name = name # type: str
|
|
||||||
self.description = description # type: str
|
|
||||||
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 main():
|
def main():
|
||||||
"""
|
module = AnsibleModule(
|
||||||
Main method.
|
_ARGUMENT_SPEC,
|
||||||
"""
|
supports_check_mode=True,
|
||||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=False)
|
|
||||||
consul_module = _ConsulModule(module)
|
|
||||||
|
|
||||||
configuration = Configuration(
|
|
||||||
name=module.params.get(NAME_PARAMETER_NAME),
|
|
||||||
description=module.params.get(DESCRIPTION_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)
|
||||||
if configuration.state == PRESENT_STATE_VALUE:
|
consul_module.execute()
|
||||||
output = set_policy(configuration, consul_module)
|
|
||||||
else:
|
|
||||||
output = remove_policy(configuration, consul_module)
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -20,12 +21,16 @@ author:
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.consul
|
- community.general.consul
|
||||||
|
- community.general.consul.token
|
||||||
- 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:
|
||||||
|
@ -61,6 +66,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
|
||||||
|
@ -69,13 +91,17 @@ options:
|
||||||
- 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.
|
||||||
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.
|
||||||
|
@ -84,7 +110,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
|
||||||
|
@ -93,20 +118,24 @@ options:
|
||||||
- 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.
|
||||||
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
|
||||||
'''
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Create a role with 2 policies
|
- name: Create a role with 2 policies
|
||||||
|
@ -171,373 +200,80 @@ operation:
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
_ConsulModule, auth_argument_spec)
|
AUTH_ARGUMENTS_SPEC,
|
||||||
|
OPERATION_READ,
|
||||||
NAME_PARAMETER_NAME = "name"
|
_ConsulModule,
|
||||||
DESCRIPTION_PARAMETER_NAME = "description"
|
|
||||||
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 = {
|
||||||
NAME_PARAMETER_NAME: dict(required=True),
|
"name": dict(type="str", required=True),
|
||||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=None),
|
"description": dict(type="str"),
|
||||||
POLICIES_PARAMETER_NAME: dict(type='list', elements='dict', options=POLICY_RULE_SPEC,
|
"policies": dict(
|
||||||
mutually_exclusive=[('name', 'id')], required_one_of=[('name', 'id')], default=None),
|
type="list",
|
||||||
SERVICE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=SERVICE_ID_RULE_SPEC, default=None),
|
elements="dict",
|
||||||
NODE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=NODE_ID_RULE_SPEC, default=None),
|
options=NAME_ID_SPEC,
|
||||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE])
|
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,
|
||||||
|
),
|
||||||
|
"state": dict(default="present", choices=["present", "absent"]),
|
||||||
}
|
}
|
||||||
_ARGUMENT_SPEC.update(auth_argument_spec())
|
_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, consul_module):
|
|
||||||
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')
|
|
||||||
|
|
||||||
resulting_role = consul_module.put(('acl', 'role', role['ID']), data=update_role_data)
|
|
||||||
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, consul_module):
|
|
||||||
# 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:
|
|
||||||
resulting_role = consul_module.put('acl/role', data=create_role_data)
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION, role=resulting_role)
|
|
||||||
else:
|
|
||||||
return Output(changed=True, operation=CREATE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_role(configuration, consul_module):
|
|
||||||
roles = get_roles(consul_module)
|
|
||||||
|
|
||||||
if configuration.name in roles:
|
|
||||||
|
|
||||||
role_id = roles[configuration.name]['ID']
|
|
||||||
|
|
||||||
if not configuration.check_mode:
|
|
||||||
consul_module.delete(('acl', 'role', role_id))
|
|
||||||
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
changed = False
|
|
||||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
|
||||||
|
|
||||||
|
|
||||||
def get_roles(consul_module):
|
|
||||||
roles = consul_module.get('acl/roles')
|
|
||||||
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(consul_module):
|
|
||||||
config = consul_module.get('agent/self')["Config"]
|
|
||||||
return ConsulVersion(config["Version"])
|
|
||||||
|
|
||||||
|
|
||||||
def set_role(configuration, consul_module):
|
|
||||||
roles = get_roles(consul_module)
|
|
||||||
|
|
||||||
if configuration.name in roles:
|
|
||||||
role = roles[configuration.name]
|
|
||||||
return update_role(role, configuration, consul_module)
|
|
||||||
else:
|
|
||||||
return create_role(configuration, consul_module)
|
|
||||||
|
|
||||||
|
|
||||||
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, name=None, description=None, policies=None, service_identities=None,
|
|
||||||
node_identities=None, state=None, check_mode=None):
|
|
||||||
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 = _ConsulModule(module)
|
consul_module = ConsulRoleModule(module)
|
||||||
|
consul_module.execute()
|
||||||
try:
|
|
||||||
configuration = Configuration(
|
|
||||||
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
|
|
||||||
|
|
||||||
version = get_consul_version(consul_module)
|
|
||||||
configuration.version = version
|
|
||||||
|
|
||||||
if configuration.state == PRESENT_STATE_VALUE:
|
|
||||||
output = set_role(configuration, consul_module)
|
|
||||||
else:
|
|
||||||
output = remove_role(configuration, consul_module)
|
|
||||||
|
|
||||||
return_values = dict(changed=output.changed, operation=output.operation, role=output.role)
|
|
||||||
module.exit_json(**return_values)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -21,6 +21,7 @@ author:
|
||||||
- Håkon Lerring (@Hakon)
|
- Håkon Lerring (@Hakon)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.consul
|
- community.general.consul
|
||||||
|
- community.general.consul.token
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
|
@ -124,7 +125,7 @@ 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 (
|
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||||
auth_argument_spec, _ConsulModule
|
AUTH_ARGUMENTS_SPEC, _ConsulModule
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -281,7 +282,7 @@ def main():
|
||||||
'node',
|
'node',
|
||||||
'present']),
|
'present']),
|
||||||
datacenter=dict(type='str'),
|
datacenter=dict(type='str'),
|
||||||
**auth_argument_spec()
|
**AUTH_ARGUMENTS_SPEC
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
|
324
plugins/modules/consul_token.py
Normal file
324
plugins/modules/consul_token.py
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
#!/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.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()
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
# 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-----
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
||||||
|
|
||||||
|
- name: Delete auth method (noop)
|
||||||
|
community.general.consul_auth_method:
|
||||||
|
name: test
|
||||||
|
state: absent
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.operation is not defined
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
# 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-----
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
|
||||||
|
- name: Create a binding rule
|
||||||
|
community.general.consul_binding_rule:
|
||||||
|
name: test-binding
|
||||||
|
description: my description
|
||||||
|
auth_method: test
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
|
@ -19,7 +19,9 @@
|
||||||
- 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
|
||||||
|
@ -35,9 +37,12 @@
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
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
|
||||||
|
@ -53,9 +58,12 @@
|
||||||
}
|
}
|
||||||
token: "{{ consul_management_token }}"
|
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
|
||||||
|
@ -64,4 +72,5 @@
|
||||||
register: result
|
register: result
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- result is changed
|
- result is changed
|
||||||
|
- result.operation == 'remove'
|
|
@ -40,7 +40,8 @@
|
||||||
- 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:
|
||||||
|
@ -53,8 +54,9 @@
|
||||||
- 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:
|
||||||
|
@ -66,8 +68,9 @@
|
||||||
- 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:
|
||||||
|
@ -81,9 +84,10 @@
|
||||||
- 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:
|
||||||
|
@ -98,8 +102,8 @@
|
||||||
- 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:
|
||||||
|
@ -115,8 +119,8 @@
|
||||||
- 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:
|
||||||
|
@ -129,9 +133,9 @@
|
||||||
- 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:
|
||||||
|
@ -143,9 +147,9 @@
|
||||||
- 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:
|
||||||
|
@ -158,10 +162,10 @@
|
||||||
- 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:
|
||||||
|
@ -173,8 +177,8 @@
|
||||||
- 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:
|
||||||
|
@ -188,14 +192,17 @@
|
||||||
- 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 }}"
|
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'
|
82
tests/integration/targets/consul/tasks/consul_token.yml
Normal file
82
tests/integration/targets/consul/tasks/consul_token.yml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
loop:
|
||||||
|
- foo-access
|
||||||
|
- foo-access2
|
||||||
|
|
||||||
|
- name: Create token
|
||||||
|
community.general.consul_token:
|
||||||
|
state: present
|
||||||
|
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
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
|
||||||
|
token: "{{ consul_management_token }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- not result.token
|
||||||
|
- result.operation == 'remove'
|
|
@ -77,12 +77,10 @@
|
||||||
- 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:
|
||||||
|
@ -94,6 +92,9 @@
|
||||||
- import_tasks: consul_session.yml
|
- import_tasks: consul_session.yml
|
||||||
- import_tasks: consul_policy.yml
|
- import_tasks: consul_policy.yml
|
||||||
- import_tasks: consul_role.yml
|
- import_tasks: consul_role.yml
|
||||||
|
- import_tasks: consul_token.yml
|
||||||
|
- import_tasks: consul_auth_method.yml
|
||||||
|
- import_tasks: consul_binding_rule.yml
|
||||||
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)
|
||||||
|
|
Loading…
Reference in a new issue