1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 16:01:55 +00:00
community.general/plugins/modules/etcd3.py
2025-11-01 13:46:53 +01:00

253 lines
7.9 KiB
Python

#!/usr/bin/python
#
# Copyright (c) 2018, Jean-Philippe Evrard <jean-philippe@evrard.me>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
DOCUMENTATION = r"""
module: etcd3
short_description: Set or delete key value pairs from an etcd3 cluster
requirements:
- etcd3
description:
- Sets or deletes values in etcd3 cluster using its v3 API.
- Needs python etcd3 lib to work.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
key:
type: str
description:
- The key where the information is stored in the cluster.
required: true
value:
type: str
description:
- The information stored.
required: true
host:
type: str
description:
- The IP address of the cluster.
default: 'localhost'
port:
type: int
description:
- The port number used to connect to the cluster.
default: 2379
state:
type: str
description:
- The state of the value for the key.
- Can be present or absent.
required: true
choices: [present, absent]
user:
type: str
description:
- The etcd user to authenticate with.
password:
type: str
description:
- The password to use for authentication.
- Required if O(user) is defined.
ca_cert:
type: path
description:
- The Certificate Authority to use to verify the etcd host.
- Required if O(client_cert) and O(client_key) are defined.
client_cert:
type: path
description:
- PEM formatted certificate chain file to be used for SSL client authentication.
- Required if O(client_key) is defined.
client_key:
type: path
description:
- PEM formatted file that contains your private key to be used for SSL client authentication.
- Required if O(client_cert) is defined.
timeout:
type: int
description:
- The socket level timeout in seconds.
author:
- Jean-Philippe Evrard (@evrardjp)
- Victor Fauth (@vfauth)
"""
EXAMPLES = r"""
- name: Store a value "bar" under the key "foo" for a cluster located "http://localhost:2379"
community.general.etcd3:
key: "foo"
value: "baz3"
host: "localhost"
port: 2379
state: "present"
- name: Authenticate using user/password combination with a timeout of 10 seconds
community.general.etcd3:
key: "foo"
value: "baz3"
state: "present"
user: "someone"
password: "password123"
timeout: 10
- name: Authenticate using TLS certificates
community.general.etcd3:
key: "foo"
value: "baz3"
state: "present"
ca_cert: "/etc/ssl/certs/CA_CERT.pem"
client_cert: "/etc/ssl/certs/cert.crt"
client_key: "/etc/ssl/private/key.pem"
"""
RETURN = r"""
key:
description: The key that was queried.
returned: always
type: str
old_value:
description: The previous value in the cluster.
returned: always
type: str
"""
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native
try:
import etcd3
HAS_ETCD = True
ETCD_IMP_ERR = None
except ImportError:
ETCD_IMP_ERR = traceback.format_exc()
HAS_ETCD = False
def run_module():
# define the available arguments/parameters that a user can pass to
# the module
module_args = dict(
key=dict(type="str", required=True, no_log=False),
value=dict(type="str", required=True),
host=dict(type="str", default="localhost"),
port=dict(type="int", default=2379),
state=dict(type="str", required=True, choices=["present", "absent"]),
user=dict(type="str"),
password=dict(type="str", no_log=True),
ca_cert=dict(type="path"),
client_cert=dict(type="path"),
client_key=dict(type="path"),
timeout=dict(type="int"),
)
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
required_together=[["client_cert", "client_key"], ["user", "password"]],
)
# It is possible to set `ca_cert` to verify the server identity without
# setting `client_cert` or `client_key` to authenticate the client
# so required_together is enough
# Due to `required_together=[['client_cert', 'client_key']]`, checking the presence
# of either `client_cert` or `client_key` is enough
if module.params["ca_cert"] is None and module.params["client_cert"] is not None:
module.fail_json(msg="The 'ca_cert' parameter must be defined when 'client_cert' and 'client_key' are present.")
result["key"] = module.params.get("key")
module.params["cert_cert"] = module.params.pop("client_cert")
module.params["cert_key"] = module.params.pop("client_key")
if not HAS_ETCD:
module.fail_json(msg=missing_required_lib("etcd3"), exception=ETCD_IMP_ERR)
allowed_keys = ["host", "port", "ca_cert", "cert_cert", "cert_key", "timeout", "user", "password"]
client_params = {key: value for key, value in module.params.items() if key in allowed_keys}
try:
etcd = etcd3.client(**client_params)
except Exception as exp:
module.fail_json(msg=f"Cannot connect to etcd cluster: {exp}", exception=traceback.format_exc())
try:
cluster_value = etcd.get(module.params["key"])
except Exception as exp:
module.fail_json(msg=f"Cannot reach data: {exp}", exception=traceback.format_exc())
# Make the cluster_value[0] a string for string comparisons
result["old_value"] = to_native(cluster_value[0])
if module.params["state"] == "absent":
if cluster_value[0] is not None:
if module.check_mode:
result["changed"] = True
else:
try:
etcd.delete(module.params["key"])
except Exception as exp:
module.fail_json(
msg=f"Cannot delete {module.params['key']}: {exp}", exception=traceback.format_exc()
)
else:
result["changed"] = True
elif module.params["state"] == "present":
if result["old_value"] != module.params["value"]:
if module.check_mode:
result["changed"] = True
else:
try:
etcd.put(module.params["key"], module.params["value"])
except Exception as exp:
module.fail_json(
msg=f"Cannot add or edit key {module.params['key']}: {exp}", exception=traceback.format_exc()
)
else:
result["changed"] = True
else:
module.fail_json(msg="State not recognized")
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
# during the execution of the module, if there is an exception or a
# conditional state that effectively causes a failure, run
# AnsibleModule.fail_json() to pass in the message and the result
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
def main():
run_module()
if __name__ == "__main__":
main()