Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import text_type
from ansible.plugins.action import ActionBase
from ansible.utils.vars import merge_hash
from ..module_utils import bonsai, errors
def validate(name, args, required, typ):
"""
Make sure that required values are not None and that if the value is
present, it is of the correct type.
"""
value = args.get(name)
if required and value is None:
raise errors.Error("{0} is required argument".format(name))
if value is not None and not isinstance(value, typ):
raise errors.Error("{0} should be {1}".format(name, typ))
class ActionModule(ActionBase):
_VALID_ARGS = frozenset((
"auth", "name", "version", "namespace", "rename", "labels",
"annotations", "on_remote",
))
def run(self, _tmp=None, task_vars=None):
self._supports_check_mode = True
self._supports_async = True
result = super(ActionModule, self).run(task_vars=task_vars)
wrap_async = (
self._task.async_val and not self._connection.has_native_async
)
try:
self.validate_arguments(self._task.args)
asset = self.download_asset_definition(
self._task.args.get("on_remote", False),
self._task.args["name"],
self._task.args["version"],
task_vars,
)
asset_args = self.build_asset_args(self._task.args, asset)
return merge_hash(
result,
self._execute_module(
module_name="sensu.sensu_go.asset", module_args=asset_args,
task_vars=task_vars, wrap_async=wrap_async,
),
)
except errors.Error as e:
return dict(result, failed=True, msg=str(e))
finally:
if not wrap_async:
self._remove_tmp_path(self._connection._shell.tmpdir)
@staticmethod
def validate_arguments(args):
# We only validate arguments that we use. We let the asset module
# validate the rest (like auth data).
# Next three string validations might seem strange at first, but there
# is a reason for this strangenes. On python 2, we should consider
# string to be instance of str or unicode. On python 3, strings are
# always instances of str. In order to avoid having a separate
# validate calls for python 2 and python 3, we always pass a pair of
# types that just happen to be the same on python 3.
validate("name", args, required=True, typ=(str, text_type))
validate("version", args, required=True, typ=(str, text_type))
validate("rename", args, required=False, typ=(str, text_type))
validate("labels", args, required=False, typ=dict)
validate("annotations", args, required=False, typ=dict)
validate("on_remote", args, required=False, typ=bool)
def download_asset_definition(self, on_remote, name, version, task_vars):
if not on_remote:
return bonsai.get_asset_parameters(name, version)
args = dict(name=name, version=version)
result = self._execute_module(
module_name="sensu.sensu_go.bonsai_asset", module_args=args,
task_vars=task_vars, wrap_async=False,
)
if result.get("failed", False):
raise errors.Error(result["msg"])
return result["asset"]
@staticmethod
def build_asset_args(args, bonsai_args):
asset_args = dict(
name=args.get("rename", args["name"]),
state="present",
builds=bonsai_args["builds"],
)
if "auth" in args:
asset_args["auth"] = args["auth"]
if "namespace" in args:
asset_args["namespace"] = args["namespace"]
# Only add optional parameter if it is present in at least one source.
for meta in ("labels", "annotations"):
if bonsai_args[meta] or args.get(meta):
asset_args[meta] = merge_hash(
bonsai_args[meta] or {}, args.get(meta, {}),
)
return asset_args

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
annotations:
description:
- Custom metadata fields with fewer restrictions, as key/value pairs.
- These are preserved by Sensu but not accessible as tokens or
identifiers, and are mainly intended for use with external tools.
type: dict
default: {}
"""

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
auth:
description:
- Authentication parameters. Can define each of them with ENV as well.
type: dict
suboptions:
user:
description:
- The username to use for connecting to the Sensu API.
If this is not set the value of the SENSU_USER environment
variable will be checked.
- This parameter is ignored if the I(auth.api_key) parameter is set.
type: str
default: admin
password:
description:
- The Sensu user's password.
If this is not set the value of the SENSU_PASSWORD environment
variable will be checked.
- This parameter is ignored if the I(auth.api_key) parameter is set.
type: str
default: P@ssw0rd!
url:
description:
- Location of the Sensu backend API.
If this is not set the value of the SENSU_URL environment variable
will be checked.
type: str
default: http://localhost:8080
api_key:
description:
- The API key that should be used when authenticating. If this is
not set, the value of the SENSU_API_KEY environment variable will
be checked.
- This replaces I(auth.user) and I(auth.password) parameters.
- For more information about the API key, refer to the official
Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/guides/use-apikey-feature/).
type: str
version_added: 1.3.0
verify:
description:
- Flag that controls the certificate validation.
- If you are using self-signed certificates, you can set this
parameter to C(false).
- ONLY USE THIS PARAMETER IN DEVELOPMENT SCENARIOS! In you use
self-signed certificates in production, see the I(auth.ca_path)
parameter.
- It is also possible to set this parameter via the I(SENSU_VERIFY)
environment variable.
type: bool
default: true
version_added: 1.5.0
ca_path:
description:
- Path to the CA bundle that should be used to validate the backend
certificate.
- If this parameter is not set, module will use the CA bundle that
python is using.
- It is also possible to set this parameter via the I(SENSU_CA_PATH)
environment variable.
type: path
version_added: 1.5.0
"""

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
name:
description:
- Retrieve information about this specific object instead of listing all objects.
type: str
"""

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
labels:
description:
- Custom metadata fields that can be accessed within Sensu, as key/value
pairs.
type: dict
default: {}
"""

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
name:
description:
- The Sensu resource's name. This name (in combination with the
namespace where applicable) uniquely identifies the resource that
Ansible operates on.
- If the resource with selected name already exists, Ansible module will
update it to match the specification in the task.
- Consult the I(name) metadata attribute specification in the upstream
docs on U(https://docs.sensu.io/sensu-go/latest/reference/) for
more details about valid names and other restrictions.
type: str
required: yes
"""

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
namespace:
description:
- RBAC namespace to operate in. If this is not set the value of the
SENSU_NAMESPACE environment variable will be used.
type: str
default: default
"""

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# We have an empty options key because ansible fails without it.
DOCUMENTATION = """
options: {}
requirements:
- python >= 2.7
"""

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
secrets:
description:
- List of secrets that are available to the command.
type: list
elements: dict
version_added: 1.6.0
suboptions:
name:
description:
- Variable name that will contain the sensitive data.
type: str
required: true
version_added: 1.6.0
secret:
description:
- Name of the secret that contains sensitive data.
type: str
required: true
version_added: 1.6.0
"""

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = """
options:
state:
description:
- Target state of the Sensu object.
type: str
choices: [ present, absent ]
default: present
"""

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
def _format_backend(vars):
if "api_key_file" in vars:
protocol = "wss"
else:
protocol = "ws"
return "{0}://{1}:{2}".format(protocol, vars["inventory_hostname"], 8081)
def backends(hostvars, groups):
return [
_format_backend(hostvars[name]) for name in groups.get("backends", [])
]
class FilterModule(object):
def filters(self):
return dict(
backends=backends,
)

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
def _apt_package_name(name, version, build):
if version == "latest":
return name
if build == "latest":
return "{0}={1}-*".format(name, version)
return "{0}={1}-{2}".format(name, version, build)
def _yum_package_name(name, version, build):
if version == "latest":
return name
if build == "latest":
return "{0}-{1}".format(name, version)
return "{0}-{1}-{2}".format(name, version, build)
KIND_HANDLERS = dict(
apt=_apt_package_name,
yum=_yum_package_name,
)
def package_name(kind, name, version, build):
return KIND_HANDLERS[kind](name, version, build)
class FilterModule(object):
def filters(self):
return dict(
package_name=package_name,
)

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import env_fallback
from . import client
SHARED_SPECS = dict(
auth=dict(
type="dict",
apply_defaults=True,
options=dict(
user=dict(
default="admin",
fallback=(env_fallback, ["SENSU_USER"]),
),
password=dict(
default="P@ssw0rd!",
no_log=True,
fallback=(env_fallback, ["SENSU_PASSWORD"]),
),
url=dict(
default="http://localhost:8080",
fallback=(env_fallback, ["SENSU_URL"]),
),
api_key=dict(
fallback=(env_fallback, ["SENSU_API_KEY"]),
no_log=True,
),
verify=dict(
default=True,
fallback=(env_fallback, ["SENSU_VERIFY"]),
type="bool",
),
ca_path=dict(
fallback=(env_fallback, ["SENSU_CA_PATH"]),
type="path",
),
),
),
state=dict(
default="present",
choices=["present", "absent"],
),
name=dict(
required=True,
),
namespace=dict(
default="default",
fallback=(env_fallback, ["SENSU_NAMESPACE"]),
),
labels=dict(
type="dict",
default={},
),
annotations=dict(
type="dict",
default={},
),
secrets=dict(
type="list",
elements="dict",
no_log=False,
options=dict(
name=dict(type="str", required=True),
secret=dict(type="str", required=True, no_log=False),
),
),
)
def get_spec(*param_names):
return dict((p, SHARED_SPECS[p]) for p in param_names)
def get_spec_payload(source, *wanted_params):
return dict(
(k, source[k]) for k in wanted_params if source.get(k) is not None
)
def get_renamed_spec_payload(source, param_mapping):
return dict(
(n, source[k]) for k, n in param_mapping.items()
if source.get(k) is not None
)
def get_mutation_payload(source, *wanted_params):
payload = get_spec_payload(source, *wanted_params)
payload["metadata"] = dict(
name=source["name"],
)
# Cluster-wide objects are not limited to a single namespace. This is why we set
# metadata.namespace field only if namespace is present in parameters.
if "namespace" in source:
if not source["namespace"]:
# We are raising an exception here for the sake of sanity test.
raise AssertionError("BUG: namespace should not be None")
payload["metadata"]["namespace"] = source["namespace"]
for kind in "labels", "annotations":
if source.get(kind):
payload["metadata"][kind] = dict(
(k, str(v)) for k, v in source[kind].items()
)
return payload
def get_sensu_client(auth):
return client.Client(
auth["url"], auth["user"], auth["password"], auth["api_key"],
auth["verify"], auth["ca_path"],
)

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from . import errors, http
def get(path):
url = "https://bonsai.sensu.io/api/v1/assets/{0}".format(path)
resp = http.request("GET", url)
if resp.status != 200:
raise errors.BonsaiError(
"Server returned status {0}".format(resp.status),
)
if resp.json is None:
raise errors.BonsaiError("Server returned invalid JSON document")
return resp.json
def get_available_asset_versions(namespace, name):
asset_data = get("{0}/{1}".format(namespace, name))
try:
return set(v["version"] for v in asset_data["versions"])
except (TypeError, KeyError):
raise errors.BonsaiError(
"Cannot extract versions from {0}".format(asset_data),
)
def get_asset_version_builds(namespace, name, version):
asset = get("{0}/{1}/{2}/release_asset_builds".format(
namespace, name, version,
))
if "spec" not in asset or "builds" not in asset["spec"]:
raise errors.BonsaiError("Invalid build spec: {0}".format(asset))
return asset
def get_asset_parameters(name, version):
try:
namespace, asset_name = name.split("/")
except ValueError:
raise errors.BonsaiError(
"Bonsai asset names should be formatted as <namespace>/<name>.",
)
available_versions = get_available_asset_versions(namespace, asset_name)
if version not in available_versions:
raise errors.BonsaiError(
"Version {0} is not available. Choose from: {1}.".format(
version, ", ".join(available_versions),
),
)
asset_builds = get_asset_version_builds(namespace, asset_name, version)
return dict(
labels=asset_builds.get("metadata", {}).get("labels"),
annotations=asset_builds.get("metadata", {}).get("annotations"),
builds=asset_builds["spec"]["builds"],
)

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
try:
from ansible.module_utils.compat import version
except ImportError:
from distutils import version
from . import errors, http
class Client:
BAD_VERSION = version.StrictVersion("9999.99.99")
def __init__(self, address, username, password, api_key, verify, ca_path):
self.address = address.rstrip("/")
self.username = username
self.password = password
self.api_key = api_key
self.verify = verify
self.ca_path = ca_path
self._auth_header = None # Login when/if required
self._version = None # Set version only if the consumer needs it
@property
def auth_header(self):
if not self._auth_header:
self._auth_header = self._login()
return self._auth_header
@property
def version(self):
if self._version is None:
resp = self.get("/version")
if resp.status != 200:
raise errors.SensuError(
"Version API returned status {0}".format(resp.status),
)
if resp.json is None:
raise errors.SensuError(
"Version API did not return a valid JSON",
)
if "sensu_backend" not in resp.json:
raise errors.SensuError(
"Version API did not return backend version",
)
try:
self._version = version.StrictVersion(
resp.json["sensu_backend"].split("#")[0]
)
except ValueError:
# Backend has no version compiled in - we are probably running
# againts self-compiled version from git.
self._version = self.BAD_VERSION
return self._version
def _login(self):
if self.api_key:
return self._api_key_login()
return self._username_password_login()
def _api_key_login(self):
# We cannot validate the API key because there is no API endpoint that
# we could hit for verification purposes. This means that the error
# reporting will be a mess but there is not much we can do here.
return dict(Authorization="Key {0}".format(self.api_key))
def _username_password_login(self):
resp = http.request(
"GET", "{0}/auth".format(self.address), force_basic_auth=True,
url_username=self.username, url_password=self.password,
validate_certs=self.verify, ca_path=self.ca_path,
)
if resp.status != 200:
raise errors.SensuError(
"Authentication call returned status {0}".format(resp.status),
)
if resp.json is None:
raise errors.SensuError(
"Authentication call did not return a valid JSON",
)
if "access_token" not in resp.json:
raise errors.SensuError(
"Authentication call did not return access token",
)
return dict(
Authorization="Bearer {0}".format(resp.json["access_token"]),
)
def request(self, method, path, payload=None):
url = self.address + path
headers = self.auth_header
response = http.request(
method, url, payload=payload, headers=headers,
validate_certs=self.verify, ca_path=self.ca_path,
)
if response.status in (401, 403):
raise errors.SensuError(
"Authentication problem. Verify your credentials."
)
return response
def get(self, path):
return self.request("GET", path)
def put(self, path, payload):
return self.request("PUT", path, payload)
def delete(self, path):
return self.request("DELETE", path)
def validate_auth_data(self, username, password):
resp = http.request(
"GET", "{0}/auth/test".format(self.address),
force_basic_auth=True, url_username=username,
url_password=password, validate_certs=self.verify,
ca_path=self.ca_path,
)
if resp.status not in (200, 401):
raise errors.SensuError(
"Authentication test returned status {0}".format(resp.status),
)
return resp.status == 200

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
import tempfile
from datetime import datetime
DEBUG = os.environ.get("SENSU_ANSIBLE_DEBUG", "").lower() in ["yes", "true"]
def log(message, *args, **kwargs):
"""
Log message to a file (/tmp/sensu-ansible.log) at remote target
Sensu API returns fairly modest error messages (e.g. when PUT payload contains
unsupported parameter, the error message won't tell you which one) and that
makes it difficult to debug. For that reason we decided to support at least
the most primitive type of logging: write to /tmp/sensu-ansible.log file.
Beware the log file resides on Ansible target and not host because this is
where the module gets executed.
This function won't do anything unless target has environment variable
SENSU_ANSIBLE_DEBUG set to "yes". When troubleshooting, just set the env
variable in the playbook.
"""
if DEBUG:
with open(os.path.join(tempfile.gettempdir(), "sensu-ansible.log"), "a") as f:
f.write("[{0}]: {1}\n".format(datetime.utcnow(), message.format(*args, **kwargs)))
def log_request(method, url, payload, resp=None, comment=None):
"""Log API request and response"""
if DEBUG:
if resp:
code, data = resp.status, resp.data
else:
code = data = "?"
fmt = "{0} {1} {2}\nPAYLOAD:{3}\nRESPONSE:{4}\nCOMMENT:{5}"
log(fmt, code, method, url, payload, data, comment)

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Error(Exception):
""" Base error that serves as a parent for all other errors. """
class HttpError(Error):
""" Error that signals failure in HTTP connection. """
class SyncError(Error):
""" Error that signals failure when syncing state with remote. """
class SensuError(Error):
""" Error that signals problems with Sensu Go web API. """
class BonsaiError(Error):
""" Error that signals problems with Bonsai assets. """

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
try:
from ssl import CertificateError
except ImportError:
# This will never match the ssl exception, which will cause exception to
# bubble up the call stack.
class CertificateError(Exception):
pass
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils.urls import open_url
from . import errors, debug
class Response:
def __init__(self, status, data):
self.status = status
self.data = data
self._json = None
@property
def json(self):
if self._json is None:
try:
self._json = json.loads(self.data)
except TypeError: # Handle python 3.5 returning bytes
try:
self._json = json.loads(self.data.decode('utf-8'))
except ValueError:
self._json = None
except ValueError: # Cannot use JSONDecodeError here (python 2)
self._json = None
return self._json
def request(method, url, payload=None, data=None, headers=None, **kwargs):
if payload is not None:
data = json.dumps(payload, separators=(",", ":"))
headers = dict(headers or {}, **{"content-type": "application/json"})
try:
raw_resp = open_url(
method=method, url=url, data=data, headers=headers, **kwargs
)
resp = Response(raw_resp.getcode(), raw_resp.read())
debug.log_request(method, url, payload, resp)
return resp
except HTTPError as e:
# This is not an error, since client consumers might be able to
# work around/expect non 20x codes.
resp = Response(e.code, e.reason)
debug.log_request(method, url, payload, resp)
return resp
except URLError as e:
debug.log_request(method, url, payload, comment=e.reason)
raise errors.HttpError(
"{0} request failed: {1}".format(method, e.reason),
)
except CertificateError as e:
raise errors.HttpError("Certificate error: {0}".format(e))

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from . import utils
def validate_module_params(params):
if params['state'] == 'present':
if not params['rules']:
return 'state is present but all of the following are missing: rules'
return None
def validate_binding_module_params(params):
if params["state"] == "present":
if not (params["users"] or params["groups"]):
return 'missing required parameters: users or groups'
def type_name_dict(obj_type, name):
return {
'type': obj_type,
'name': name,
}
def build_subjects(groups, users):
groups_dicts = [type_name_dict('Group', g) for g in (groups or [])]
users_dicts = [type_name_dict('User', u) for u in (users or [])]
return groups_dicts + users_dicts
def do_role_bindings_differ(current, desired):
if _do_subjects_differ(current['subjects'], desired['subjects']):
return True
return utils.do_differ(current, desired, 'subjects')
# sorts a list of subjects (dicts returned by type_name_dict)
# by 'type' and 'name' keys and returns the result of comparison.
def _do_subjects_differ(a, b):
sorted_a = sorted(a, key=lambda x: (x['type'], x['name']))
sorted_b = sorted(b, key=lambda x: (x['type'], x['name']))
return sorted_a != sorted_b
def _rule_set(rules):
return set(
(
frozenset(r.get('verbs', []) or []),
frozenset(r.get('resources', []) or []),
frozenset(r.get('resource_names', []) or [])
) for r in rules
)
def _do_rules_differ(current_rules, desired_rules):
if len(current_rules) != len(desired_rules):
return True
if _rule_set(current_rules) != _rule_set(desired_rules):
return True
return False
def do_roles_differ(current, desired):
if _do_rules_differ(current['rules'], desired['rules']):
return True
return utils.do_differ(current, desired, 'rules')

View File

@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six.moves.urllib.parse import quote
from . import errors
def do_differ(current, desired, *ignored_keys):
if "metadata" in desired:
# We treat metadata as any regular dict but ignore the created_by
# value since this piece of data is autogenerated on the backend and
# must be skipped in the comparison.
if do_differ(current["metadata"], desired["metadata"], "created_by"):
return True
ignored_keys = ignored_keys + ("metadata",)
for key, value in desired.items():
if key in ignored_keys:
continue
if value != current.get(key):
return True
return False
def do_differ_v1(current, desired, *ignored_keys):
# Default comparator for v1 API (mostly enterprise features)
if "metadata" in desired:
# We treat metadata as any regular dict but ignore the created_by
# value since this piece of data is autogenerated on the backend and
# must be skipped in the comparison.
if do_differ(current["metadata"], desired["metadata"], "created_by"):
return True
for key, value in desired.get("spec", {}).items():
if key in ignored_keys:
continue
if value != current["spec"].get(key):
return True
return False
def sync(state, client, path, payload, check_mode, compare=do_differ):
remote_object = get(client, path)
if state == "absent" and remote_object is None:
return False, None
if state == "absent":
if not check_mode:
delete(client, path)
return True, None
# Making sure remote_object is present from here on
if remote_object is None or compare(remote_object, payload):
if check_mode:
return True, payload
put(client, path, payload)
return True, get(client, path)
return False, remote_object
def sync_v1(state, client, path, payload, check_mode, compare=do_differ_v1):
changed, result = sync(state, client, path, payload, check_mode, compare)
return changed, convert_v1_to_v2_response(result)
def _abort(msg, *args, **kwargs):
raise errors.SyncError(msg.format(*args, **kwargs))
def get(client, path):
resp = client.get(path)
if resp.status not in (200, 404):
_abort(
"GET {0} failed with status {1}: {2}", path, resp.status, resp.data,
)
if resp.status == 200 and resp.json is None:
_abort("Server returned invalid JSON {0}", resp.data)
return resp.json
def delete(client, path):
resp = client.delete(path)
if resp.status != 204:
_abort(
"DELETE {0} failed with status {1}: {2}",
path, resp.status, resp.data,
)
return None
def put(client, path, payload):
resp = client.put(path, payload)
if resp.status not in (200, 201):
_abort(
"PUT {0} failed with status {1}: {2}",
path, resp.status, resp.data,
)
return None
def dict_to_single_item_dicts(data):
return [{k: v} for k, v in data.items()]
def single_item_dicts_to_dict(data):
result = {}
for item in data:
(k, v), = item.items()
result[k] = v
return result
def dict_to_key_value_strings(data):
return ["{0}={1}".format(k, v) for k, v in data.items()]
def build_url_path(api_group, api_version, namespace, *parts):
prefix = "/api/{0}/{1}/".format(api_group, api_version)
if namespace:
prefix += "namespaces/{0}/".format(quote(namespace, safe=""))
return prefix + "/".join(quote(p, safe="") for p in parts if p)
def build_core_v2_path(namespace, *parts):
return build_url_path("core", "v2", namespace, *parts)
def prepare_result_list(result):
if isinstance(result, list):
return result
return [] if result is None else [result]
def convert_v1_to_v2_response(response):
# dict(metadata=<meta>, spec=dict(a=1)) -> dict(metadata=<meta>, a=1)
if not response:
return response
if "metadata" not in response:
return response["spec"]
# Move metadata key into the spec.
return dict(response["spec"], metadata=response["metadata"])
def do_secrets_differ(current, desired):
return set(
(c["name"], c["secret"]) for c in (current.get("secrets") or [])
) != set(
(d["name"], d["secret"]) for d in (desired.get("secrets") or [])
)
def deprecate(module, msg, version):
try:
module.deprecate(msg, version=version, collection_name="sensu.sensu_go")
except TypeError:
# Ansible < 2.9.10 does not support collection_name kwarg. Output at
# least msg and version of collection when things will stop working.
module.deprecate(msg, version=version)

View File

@@ -0,0 +1,396 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: ad_auth_provider
author:
- Aljaz Kosir (@aljazkosir)
- Manca Bizjak (@mancabizjak)
- Miha Dolinar (@mdolin)
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu AD authentication provider
description:
- Create, update or delete a Sensu Go AD authentication provider.
- For more information, refer to the Sensu Go documentation at
U(https://docs.sensu.io/sensu-go/latest/operations/control-access/ad-auth/).
version_added: 1.10.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.state
options:
servers:
description:
- An array of AD servers for your directory.
type: list
elements: dict
suboptions:
host:
description:
- AD server IP address.
required: true
type: str
port:
description:
- AD server port.
type: int
insecure:
description:
- Skips SSL certificate verification when set to true.
type: bool
default: false
security:
description:
- Encryption type to be used for the connection to the AD server.
type: str
choices: [ insecure, tls, starttls ]
default: tls
trusted_ca_file:
description:
- Path to an alternative CA bundle file.
type: str
client_cert_file:
description:
- Path to the certificate that should be sent to the server if requested.
type: str
client_key_file:
description:
- Path to the key file associated with the client_cert_file.
- Required if I(client_cert_file) is present.
type: str
default_upn_domain:
description:
- Enables UPN authentication when set. The default UPN suffix that will be appended
to the username when a domain is not specified during login
(for example, user becomes user@defaultdomain.xyz).
type: str
include_nested_groups:
description:
- If true, the group search includes any nested groups a user is a member of.
If false, the group search includes only the top-level groups a user is a member of.
type: bool
binding:
description:
- The AD account that performs user and group lookups.
- If your sever supports anonymous binding, you can omit the user_dn or password
attributes to query the directory without credentials.
type: dict
suboptions:
user_dn:
description:
- The AD account that performs user and group lookups.
- If your sever supports anonymous binding, you can omit this attribute.
type: str
required: true
password:
description:
- Password for the user_dn account.
- If your sever supports anonymous binding, you can omit this attribute.
type: str
required: true
group_search:
description:
- Search configuration for groups.
type: dict
suboptions:
base_dn:
description:
- Which part of the directory tree to search.
required: true
type: str
attribute:
description:
- Used for comparing result entries.
type: str
default: member
name_attribute:
description:
- Represents the attribute to use as the entry name.
type: str
default: cn
object_class:
description:
- Identifies the class of objects returned in the search result.
type: str
default: group
user_search:
description:
- Search configuration for users.
type: dict
suboptions:
base_dn:
description:
- Which part of the directory tree to search.
required: true
type: str
attribute:
description:
- Used for comparing result entries.
type: str
default: sAMAccountName
name_attribute:
description:
- Represents the attribute to use as the entry name.
type: str
default: displayName
object_class:
description:
- Identifies the class of objects returned in the search result.
type: str
default: person
groups_prefix:
description:
- The prefix added to all AD groups.
type: str
username_prefix:
description:
- The prefix added to all AD usernames.
type: str
seealso:
- module: sensu.sensu_go.auth_provider_info
- module: sensu.sensu_go.ldap_auth_provider
- module: sensu.sensu_go.oidc_auth_provider
"""
EXAMPLES = """
- name: Create a AD auth provider
sensu.sensu_go.ad_auth_provider:
name: activedirectory
servers:
- host: 127.0.0.1
group_search:
base_dn: dc=acme,dc=org
user_search:
base_dn: dc=acme,dc=org
- name: Delete a AD auth provider
sensu.sensu_go.ad_auth_provider:
name: activedirectory
state: absent
"""
RETURN = """
object:
description: Object representing Sensu AD authentication provider.
returned: success
type: dict
sample:
metadata:
name: 'activedirectory'
servers:
host: '127.0.0.1'
port: '636'
insecure: 'False'
security: 'tls'
trusted_ca_file: '/path/to/trusted-certificate-authorities.pem'
client_cert_file: '/path/to/ssl/cert.pem'
client_key_file: '/path/to/ssl/key.pem'
default_upn_domain: 'example.org'
binding:
user_dn: 'cn=binder,dc=acme,dc=org'
group_search:
base_dn: 'dc=acme,dc=org'
attribute: 'member'
name_attribute': 'cn'
object_class: 'group'
user_search:
base_dn: 'dc=acme,dc=org'
attribute: 'sAMAccountName'
name_attribute: 'displayName'
object_class: 'person'
groups_prefix: 'AD'
username_prefix: 'AD'
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
API_GROUP = "enterprise"
API_VERSION = "authentication/v2"
def remove_item(result):
if result:
for server in result["servers"]:
if server["binding"] and "password" in server["binding"]:
del server["binding"]["password"]
return result
def _filter(payload):
# Remove keys with None values from dict
return dict((k, v) for k, v in payload.items() if v is not None)
def do_differ(current, desired):
if utils.do_differ_v1(current, desired, "servers"):
return True
if len(current["spec"]["servers"]) != len(desired["spec"]["servers"]):
return True
for c, d in zip(current["spec"]["servers"], desired["spec"]["servers"]):
if utils.do_differ(c, _filter(d)):
return True
return False
def main():
required_if = [("state", "present", ["servers"])]
module = AnsibleModule(
required_if=required_if,
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec(
"auth",
"name",
"state",
),
servers=dict(
type="list",
elements="dict",
options=dict(
host=dict(
type="str",
required=True,
),
port=dict(
type="int",
),
insecure=dict(
type="bool",
default=False,
),
security=dict(
type="str",
choices=["insecure", "tls", "starttls"],
default="tls",
),
trusted_ca_file=dict(
type="str",
),
client_cert_file=dict(
type="str",
),
client_key_file=dict(
type="str",
),
default_upn_domain=dict(
type="str",
),
include_nested_groups=dict(
type="bool",
),
binding=dict(
type="dict",
options=dict(
user_dn=dict(
type="str",
required=True,
),
password=dict(
type="str",
no_log=True,
required=True,
),
),
),
group_search=dict(
type="dict",
options=dict(
base_dn=dict(
type="str",
required=True,
),
attribute=dict(
type="str",
default="member",
),
name_attribute=dict(
type="str",
default="cn",
),
object_class=dict(type="str", default="group"),
),
),
user_search=dict(
type="dict",
options=dict(
base_dn=dict(
type="str",
required=True,
),
attribute=dict(
type="str",
default="sAMAccountName",
),
name_attribute=dict(
type="str",
default="displayName",
),
object_class=dict(
type="str",
default="person",
),
),
),
),
),
groups_prefix=dict(
type="str",
),
username_prefix=dict(
type="str",
),
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_url_path(
API_GROUP, API_VERSION, None, "authproviders", module.params["name"]
)
payload = dict(
type="ad",
api_version=API_VERSION,
metadata=dict(name=module.params["name"]),
spec=arguments.get_spec_payload(
module.params, "servers", "groups_prefix", "username_prefix"
),
)
try:
changed, ad_provider = utils.sync_v1(
module.params["state"], client, path, payload, module.check_mode, do_differ
)
module.exit_json(changed=changed, object=remove_item(ad_provider))
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,213 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Cameron Hurst <cahurst@cisco.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: asset
author:
- Cameron Hurst (@wakemaster39)
- Aljaz Kosir (@aljazkosir)
- Manca Bizjak (@mancabizjak)
- Miha Plesko (@miha-plesko)
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu assets
description:
- Create, update or delete Sensu Go asset.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/assets/).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.namespace
- sensu.sensu_go.state
- sensu.sensu_go.labels
- sensu.sensu_go.annotations
seealso:
- module: sensu.sensu_go.asset_info
- module: sensu.sensu_go.bonsai_asset
options:
builds:
description:
- A list of asset builds used to define multiple artefacts which
provide the named asset.
- Required if I(state) is C(present).
type: list
elements: dict
suboptions:
url:
description:
- The URL location of the asset.
type: str
required: yes
sha512:
description:
- The checksum of the asset.
type: str
required: yes
filters:
description:
- A set of Sensu query expressions used to determine if the asset
should be installed.
type: list
elements: str
headers:
description:
- Additional headers to send when retrieving the asset, e.g. for
authorization.
type: dict
"""
EXAMPLES = """
- name: Create a multiple-build asset
sensu.sensu_go.asset:
name: sensu-plugins-cpu-checks
builds:
- url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz
sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b
filters:
- entity.system.os == 'linux'
- entity.system.arch == 'amd64'
- entity.system.platform == 'rhel'
- url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_alpine_linux_amd64.tar.gz
sha512: b2da25ecd7642e6de41fde37d674fe19dcb6ee3d680e145e32289f7cfc352e6b5f9413ee9b701d61faeaa47b399aa30b25885dbc1ca432c4061c8823774c28f3
filters:
- entity.system.os == 'linux'
- entity.system.arch == 'amd64'
- entity.system.platform == 'alpine'
- name: Delete an asset
sensu.sensu_go.asset:
name: sensu-plugins-cpu-check
state: absent
"""
RETURN = """
object:
description: Object representing Sensu asset.
returned: success
type: dict
sample:
metadata:
name: check_script
namespace: default
builds:
- sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b
url: http://example.com/asset.tar.gz
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
def validate_module_params(params):
if params["state"] == "present":
if not params['builds']:
return "builds must include at least one element"
return None
def _build_set(builds):
return set((
b.get('sha512'),
b.get('url'),
frozenset((b.get('headers', {}) or {}).items()),
frozenset(b.get('filters', []) or []),
) for b in builds)
def _do_builds_differ(current, desired):
# Since Sensu Go 5.16, the web API returns builds: None if the asset
# in question is a deprecated, single-build asset.
if current is None:
return True
if len(current) != len(desired):
return True
return _build_set(current) != _build_set(desired)
def do_differ(current, desired):
if _do_builds_differ(current['builds'], desired['builds']):
return True
return utils.do_differ(current, desired, 'builds')
def build_api_payload(params):
payload = arguments.get_mutation_payload(params)
if params['state'] == 'present':
builds = [arguments.get_spec_payload(b, *b.keys()) for b in params['builds']]
payload["builds"] = builds
return payload
def main():
required_if = [
("state", "present", ["builds"])
]
module = AnsibleModule(
required_if=required_if,
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec(
"auth", "name", "namespace", "state", "labels", "annotations",
),
builds=dict(
type="list",
elements="dict",
options=dict(
url=dict(
required=True,
),
sha512=dict(
required=True,
),
filters=dict(
type="list",
elements="str",
),
headers=dict(
type="dict",
),
)
),
),
)
msg = validate_module_params(module.params)
if msg:
module.fail_json(msg=msg)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_core_v2_path(
module.params["namespace"], "assets", module.params["name"],
)
payload = build_api_payload(module.params)
try:
changed, asset = utils.sync(
module.params["state"], client, path, payload, module.check_mode, do_differ
)
module.exit_json(changed=changed, object=asset)
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,99 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: asset_info
author:
- Paul Arthur (@flowerysong)
- Aljaz Kosir (@aljazkosir)
- Miha Plesko (@miha-plesko)
- Tadej Borovsak (@tadeboro)
short_description: List Sensu assets
description:
- Retrieve information about Sensu Go assets.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/assets/).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.info
- sensu.sensu_go.namespace
seealso:
- module: sensu.sensu_go.asset
- module: sensu.sensu_go.bonsai_asset
"""
EXAMPLES = """
- name: List all Sensu assets
sensu.sensu_go.asset_info:
register: result
- name: List the selected Sensu asset
sensu.sensu_go.asset_info:
name: my_asset
register: result
- name: Do something with result
ansible.builtin.debug:
msg: "{{ result.objects.0.metadata.name }}"
"""
RETURN = """
objects:
description: List of Sensu assets.
returned: success
type: list
elements: dict
sample:
- metadata:
name: check_script
namespace: default
builds:
- sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b
url: http://example.com/asset.tar.gz
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth", "namespace"),
name=dict(), # Name is not required in info modules.
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_core_v2_path(
module.params["namespace"], "assets", module.params["name"],
)
try:
assets = utils.prepare_result_list(utils.get(client, path))
except errors.Error as e:
module.fail_json(msg=str(e))
module.exit_json(changed=False, objects=assets)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,145 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: auth_provider_info
author:
- Aljaz Kosir (@aljazkosir)
- Manca Bizjak (@mancabizjak)
- Miha Dolinar (@mdolin)
- Tadej Borovsak (@tadeboro)
short_description: List Sensu authentication providers
description:
- Retrieve information about Sensu Go authentication providers.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/operations/control-access/).
version_added: 1.10.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.info
seealso:
- module: sensu.sensu_go.ad_auth_provider
- module: sensu.sensu_go.ldap_auth_provider
- module: sensu.sensu_go.oidc_auth_provider
"""
EXAMPLES = """
- name: List all Sensu authentication providers
sensu.sensu_go.auth_provider_info:
register: result
- name: List the selected Sensu authentication provider
sensu.sensu_go.auth_provider_info:
name: my_auth_provider
register: result
- name: Do something with result
ansible.builtin.debug:
msg: "{{ result.objects.0.metadata.name }}"
"""
RETURN = """
objects:
description: List of Sensu authentication providers.
returned: success
type: list
elements: dict
sample:
- metadata:
name: 'openldap'
groups_prefix: ''
servers:
binding:
user_dn: 'cn=binder,dc=acme,dc=org'
client_cert_file: ''
client_key_file: ''
default_upn_domain: ''
group_search:
attribute: 'member'
base_dn: 'dc=acme,dc=org'
name_attribute: 'cn'
object_class: 'groupOfNames'
host: '127.0.0.1'
insecure: false
port: 636
security: 'tls'
trusted_ca_file: ''
user_search:
attribute: 'uid'
base_dn: 'dc=acme,dc=org'
name_attribute: 'cn'
object_class: 'person'
username_prefix: ''
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
API_GROUP = "enterprise"
API_VERSION = "authentication/v2"
def remove_item(result):
for server in result.get("servers", []):
if server["binding"] and "password" in server["binding"]:
del server["binding"]["password"]
if "client_secret" in result:
del result["client_secret"]
return result
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth"),
name=dict(), # Name is not required in info modules.
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_url_path(
API_GROUP,
API_VERSION,
None,
"authproviders",
module.params["name"],
)
try:
providers = utils.prepare_result_list(utils.get(client, path))
except errors.Error as e:
module.fail_json(msg=str(e))
# We simulate the behavior of v2 API here and only return the spec.
module.exit_json(
changed=False,
objects=[remove_item(utils.convert_v1_to_v2_response(p)) for p in providers],
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,131 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: bonsai_asset
author:
- Aljaz Kosir (@aljazkosir)
- Manca Bizjak (@mancabizjak)
- Tadej Borovsak (@tadeboro)
short_description: Add Sensu assets from Bonsai
description:
- Create or update a Sensu Go asset whose definition is available in the
Bonsai, the Sensu asset index.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/assets/)
and U(https://bonsai.sensu.io/).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.namespace
- sensu.sensu_go.labels
- sensu.sensu_go.annotations
options:
version:
description:
- Version number of the asset to install.
type: str
required: true
rename:
description:
- The name that will be used when adding the asset to Sensu.
- If not present, value of the I(name) parameter will be used.
type: str
on_remote:
description:
- If set to C(true), module will download asset defnition on remote host.
- If not set or set to C(false), ansible downloads asset definition
on control node.
type: bool
version_added: 1.13.0
notes:
- I(labels) and I(annotations) values are merged with the values obtained
from Bonsai. Values passed-in as parameters take precedence over the
values obtained from Bonsai.
- To delete an asset, use regular M(sensu.sensu_go.asset) module.
seealso:
- module: sensu.sensu_go.asset
- module: sensu.sensu_go.asset_info
"""
EXAMPLES = """
- name: Make sure specific version of asset is installed
sensu.sensu_go.bonsai_asset:
name: sensu/monitoring-plugins
version: 2.2.0-1
- name: Remove previously added asset
sensu.sensu_go.asset:
name: sensu/monitoring-plugins
state: absent
- name: Store Bonsai asset under a different name
sensu.sensu_go.bonsai_asset:
name: sensu/monitoring-plugins
version: 2.2.0-1
rename: sensu-monitoring-2.2.0-1
- name: Display asset info
sensu.sensu_go.asset_info:
name: sensu-monitoring-2.2.0-1 # value from rename field
"""
RETURN = """
object:
description: Object representing Sensu asset.
returned: success
type: dict
sample:
metadata:
name: check_script
namespace: default
builds:
- sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b
url: http://example.com/asset.tar.gz
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import bonsai, errors
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
name=dict(
type="str",
required=True,
),
version=dict(
type="str",
required=True,
),
),
)
try:
asset = bonsai.get_asset_parameters(
module.params["name"], module.params["version"],
)
module.exit_json(changed=False, asset=asset)
except errors.Error as e:
module.fail_json(changed=False, msg=str(e))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,449 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = '''
module: check
author:
- Paul Arthur (@flowerysong)
- Aljaz Kosir (@aljazkosir)
- Miha Plesko (@miha-plesko)
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu checks
description:
- Create, update or delete Sensu Go check.
- For more information, refer to the Sensu Go documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/checks/).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.namespace
- sensu.sensu_go.state
- sensu.sensu_go.labels
- sensu.sensu_go.annotations
- sensu.sensu_go.secrets
seealso:
- module: sensu.sensu_go.check_info
options:
command:
description:
- Check command to run.
- Required if I(state) is C(present).
type: str
subscriptions:
description:
- List of subscriptions which receive check requests.
- Required if I(state) is C(present).
type: list
elements: str
handlers:
description:
- List of handlers which receive check results.
type: list
elements: str
interval:
description:
- Check request interval.
- Cannot be used when I(cron) option is used.
type: int
cron:
description:
- Schedule check requests using crontab syntax.
- Cannot be used when I(interval) option is used.
type: str
publish:
description:
- Enables or disables scheduled publication of check requests.
type: bool
timeout:
description:
- Check execution timeout.
type: int
ttl:
description:
- Amount of time after which a check result is considered stale.
type: int
stdin:
description:
- Enables writing of serialized JSON data to the check command's stdin.
- Only usable with checks written specifically for Sensu Go.
type: bool
low_flap_threshold:
description:
- Low flap threshold.
type: int
high_flap_threshold:
description:
- High flap threshold.
type: int
runtime_assets:
description:
- List of runtime assets required to run the check.
type: list
elements: str
check_hooks:
description:
- A mapping of response codes to hooks which will be run by the agent
when that code is returned.
- Note that the structure of this parameter is a bit different from the
one described at
U(https://docs.sensu.io/sensu-go/latest/reference/checks/#check-hooks-attribute).
- See check hooks example below for more information on exact mapping
structure.
type: dict
proxy_entity_name:
description:
- Entity name to associate this check with instead of the agent it ran on.
type: str
proxy_requests:
description:
- Allows you to assign the check to run for multiple entities according
to their entity_attributes.
type: dict
suboptions:
entity_attributes:
description:
- List of attribute checks for determining which proxy entities this check should be scheduled against.
type: list
elements: str
splay:
description:
- Enables or disables splaying of check request scheduling.
type: bool
splay_coverage:
description:
- Percentage of the C(interval) over which to splay checks.
type: int
output_metric_format:
description:
- Enable parsing of metrics in the specified format from this check's
output.
type: str
choices:
- graphite_plaintext
- influxdb_line
- nagios_perfdata
- opentsdb_line
output_metric_handlers:
description:
- List of handlers which receive check results. I'm not sure why this exists.
type: list
elements: str
round_robin:
description:
- An array of environment variables to use with command execution.
type: bool
env_vars:
description:
- A mapping of environment variable names and values to use with command execution.
type: dict
'''
EXAMPLES = '''
- name: Check executing command every 30 seconds
sensu.sensu_go.check:
name: check
command: check-cpu.sh -w 75 -c 90
subscriptions:
- checks
interval: 30
publish: yes
- name: Check executing command with cron scheduler
sensu.sensu_go.check:
name: check
command: check-cpu.sh -w 75 -c 90
subscriptions:
- systems
handlers:
- slack
cron: "* * * * *"
publish: yes
- name: Ad-hoc scheduling
sensu.sensu_go.check:
name: check
command: check-cpu.sh -w 75 -c 90
subscriptions:
- systems
handlers:
- slack
interval: 60
publish: no
- name: Report events under proxy entity name instead of agent entity
sensu.sensu_go.check:
name: check
command: http_check.sh https://sensu.io
subscriptions:
- proxy
handlers:
- slack
interval: 60
proxy_entity_name: sensu-site
round_robin: yes
publish: yes
- name: Event that triggers hooks
sensu.sensu_go.check:
name: check
command: http_check.sh https://sensu.io
subscriptions: [ proxy ]
# The upstream JSON payload for the hooks below would look like this:
#
# "check_hooks": [
# {"0": ["passing-hook", "always-run-this-hook"]},
# {"critical": ["failing-hook", "always-run-this-hook"]}
# ]
#
# Ansible task simplifies this structure into a simple mapping:
check_hooks:
"0":
- passing-hook
- always-run-this-hook
critical:
- failing-hook
- always-run-this-hook
- name: Remove check
sensu.sensu_go.check:
name: my-check
state: absent
'''
RETURN = '''
object:
description: Object representing Sensu check.
returned: success
type: dict
sample:
metadata:
name: check_minimum
namespace: default
command: collect.sh
handlers:
- slack
interval: 10
publish: true
subscriptions:
- system
'''
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
def validate_module_params(module):
params = module.params
proxy_requests = params['proxy_requests']
if (proxy_requests and proxy_requests.get('splay', False) and
proxy_requests.get('splay_coverage') is None):
module.fail_json(msg='splay is true but all of the following are missing: splay_coverage')
if params['state'] == 'present' and not (params['interval'] or params['cron']):
module.fail_json(msg='one of the following is required: interval, cron')
def do_sets_differ(current, desired, key):
return set(current.get(key) or []) != set(desired.get(key) or [])
def do_proxy_requests_differ(current, desired):
if 'proxy_requests' not in desired:
return False
current = current.get('proxy_requests') or {}
desired = desired['proxy_requests']
return (
(
'entity_attributes' in desired and
do_sets_differ(current, desired, 'entity_attributes')
) or
utils.do_differ(current, desired, 'entity_attributes')
)
def do_check_hooks_differ(current, desired):
if 'check_hooks' not in desired:
return False
current = utils.single_item_dicts_to_dict(current.get('check_hooks') or [])
current = dict((k, set(v)) for k, v in current.items())
desired = utils.single_item_dicts_to_dict(desired['check_hooks'])
desired = dict((k, set(v)) for k, v in desired.items())
return current != desired
def do_differ(current, desired):
return (
utils.do_differ(
current, desired, 'proxy_requests', 'subscriptions', 'handlers',
'runtime_assets', 'check_hooks', 'output_metric_handlers',
'env_vars', 'secrets',
) or
utils.do_secrets_differ(current, desired) or
do_proxy_requests_differ(current, desired) or
do_sets_differ(current, desired, 'subscriptions') or
do_sets_differ(current, desired, 'handlers') or
do_sets_differ(current, desired, 'runtime_assets') or
do_check_hooks_differ(current, desired) or
do_sets_differ(current, desired, 'output_metric_handlers') or
do_sets_differ(current, desired, 'env_vars')
)
def build_api_payload(params):
payload = arguments.get_mutation_payload(
params,
'command',
'cron',
'handlers',
'high_flap_threshold',
'interval',
'low_flap_threshold',
'output_metric_format',
'output_metric_handlers',
'proxy_entity_name',
'publish',
'round_robin',
'runtime_assets',
'secrets',
'stdin',
'subscriptions',
'timeout',
'ttl'
)
if params['proxy_requests']:
payload['proxy_requests'] = arguments.get_spec_payload(
params['proxy_requests'],
'entity_attributes', 'splay', 'splay_coverage',
)
if params['check_hooks']:
payload['check_hooks'] = utils.dict_to_single_item_dicts(params['check_hooks'])
if params['env_vars']:
payload['env_vars'] = utils.dict_to_key_value_strings(params['env_vars'])
return payload
def main():
required_if = [
('state', 'present', ['subscriptions', 'command'])
]
mutually_exclusive = [('interval', 'cron')]
module = AnsibleModule(
supports_check_mode=True,
required_if=required_if,
mutually_exclusive=mutually_exclusive,
argument_spec=dict(
arguments.get_spec(
"auth", "name", "state", "labels", "annotations", "namespace",
"secrets",
),
command=dict(),
subscriptions=dict(
type='list', elements='str',
),
handlers=dict(
type='list', elements='str',
),
interval=dict(
type='int'
),
cron=dict(),
publish=dict(
type='bool'
),
timeout=dict(
type='int'
),
ttl=dict(
type='int'
),
stdin=dict(
type='bool'
),
env_vars=dict(
type='dict'
),
low_flap_threshold=dict(
type='int'
),
high_flap_threshold=dict(
type='int'
),
runtime_assets=dict(
type='list', elements='str',
),
check_hooks=dict(
type='dict'
),
proxy_entity_name=dict(),
proxy_requests=dict(
type='dict',
options=dict(
entity_attributes=dict(
type='list', elements='str',
),
splay=dict(
type='bool'
),
splay_coverage=dict(
type='int'
)
)
),
output_metric_format=dict(
choices=['nagios_perfdata', 'graphite_plaintext', 'influxdb_line', 'opentsdb_line']
),
output_metric_handlers=dict(
type='list', elements='str',
),
round_robin=dict(
type='bool'
)
)
)
validate_module_params(module)
client = arguments.get_sensu_client(module.params['auth'])
path = utils.build_core_v2_path(
module.params['namespace'], 'checks', module.params['name'],
)
payload = build_api_payload(module.params)
try:
changed, check = utils.sync(
module.params['state'], client, path, payload, module.check_mode,
do_differ,
)
module.exit_json(changed=changed, object=check)
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,97 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = '''
module: check_info
author:
- Paul Arthur (@flowerysong)
- Aljaz Kosir (@aljazkosir)
- Miha Plesko (@miha-plesko)
- Tadej Borovsak (@tadeboro)
short_description: List Sensu checks
description:
- Retrieve information about Sensu Go checks.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/checks/).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.info
- sensu.sensu_go.namespace
seealso:
- module: sensu.sensu_go.check
'''
EXAMPLES = '''
- name: List all Sensu checks
sensu.sensu_go.check_info:
register: result
- name: Obtain a specific check
sensu.sensu_go.check_info:
name: my-check
register: result
'''
RETURN = '''
objects:
description: List of Sensu checks.
returned: success
type: list
elements: dict
sample:
- metadata:
name: check_minimum
namespace: default
command: collect.sh
handlers:
- slack
interval: 10
publish: true
subscriptions:
- system
'''
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth", "namespace"),
name=dict(), # Name is not required in info modules.
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_core_v2_path(
module.params["namespace"], "checks", module.params["name"],
)
try:
checks = utils.prepare_result_list(utils.get(client, path))
except errors.Error as e:
module.fail_json(msg=str(e))
module.exit_json(changed=False, objects=checks)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: cluster
author:
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu Go clusters
description:
- Create, update or delete Sensu cluster.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/).
version_added: 1.9.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.state
seealso:
- module: sensu.sensu_go.cluster_info
options:
api_urls:
description:
- List of API urls that compose a single cluster.
- Required if I(state) is C(present).
type: list
elements: str
"""
EXAMPLES = """
- name: Create a small cluster
sensu.sensu_go.cluster:
name: small-cluster
api_urls: https://sensu.alpha.example.com:8080
- name: Create a larger cluster
sensu.sensu_go.cluster:
name: large-cluster
api_urls:
- https://sensu.alpha.example.com:8080
- https://sensu.beta.example.com:8080
- https://sensu.gamma.example.com:8080
- name: Delete a cluster
sensu.sensu_go.cluster:
name: small-cluster
state: absent
"""
RETURN = """
object:
description: Object representing Sensu cluster.
returned: success
type: dict
sample:
metadata:
name: alpha-cluster
api_urls:
- "http://10.10.0.1:8080"
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
API_GROUP = "enterprise"
API_VERSION = "federation/v1"
def main():
required_if = [
("state", "present", ["api_urls"]),
]
module = AnsibleModule(
required_if=required_if,
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth", "name", "state"),
api_urls=dict(type="list", elements="str"),
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_url_path(
API_GROUP, API_VERSION, None, "clusters", module.params["name"],
)
payload = dict(
type="Cluster",
api_version=API_VERSION,
metadata=dict(name=module.params["name"]),
spec=arguments.get_spec_payload(module.params, "api_urls"),
)
try:
changed, cluster = utils.sync_v1(
module.params["state"], client, path, payload, module.check_mode,
)
module.exit_json(changed=changed, object=cluster)
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,101 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = """
module: cluster_info
author:
- Tadej Borovsak (@tadeboro)
short_description: List available Sensu Go clusters
description:
- Retrieve information about Sensu Go clusters.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/).
version_added: 1.9.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.info
seealso:
- module: sensu.sensu_go.cluster
"""
EXAMPLES = """
- name: List all Sensu Go clusters
sensu.sensu_go.etcd_replicator_info:
register: result
- name: Retrieve the selected Sensu Go cluster
sensu.sensu_go.etcd_replicator_info:
name: my-cluster
register: result
- name: Do something with result
ansible.builtin.debug:
msg: "{{ result.objects.0.api_urls }}"
"""
RETURN = """
objects:
description: List of Sensu Go etcd clusters.
returned: success
type: list
elements: dict
sample:
- metadata:
name: alpha-cluster
api_urls:
- "http://10.10.0.1:8080"
- metadata:
name: beta-cluster
api_urls:
- "https://10.20.0.1:8080"
- "https://10.20.0.2:8080"
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils
API_GROUP = "enterprise"
API_VERSION = "federation/v1"
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth"),
name=dict(), # Name is not required in info modules.
),
)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_url_path(
API_GROUP, API_VERSION, None, "clusters", module.params["name"],
)
try:
clusters = utils.prepare_result_list(utils.get(client, path))
except errors.Error as e:
module.fail_json(msg=str(e))
# We simulate the behavior of v2 API here and only return the spec.
module.exit_json(changed=False, objects=[
utils.convert_v1_to_v2_response(s) for s in clusters
])
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,164 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = '''
module: cluster_role
author:
- Paul Arthur (@flowerysong)
- Manca Bizjak (@mancabizjak)
- Aljaz Kosir (@aljazkosir)
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu cluster roles
description:
- Create, update or delete Sensu role.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.state
seealso:
- module: sensu.sensu_go.cluster_role_info
- module: sensu.sensu_go.cluster_role_binding
- module: sensu.sensu_go.role
- module: sensu.sensu_go.role_binding
options:
rules:
description:
- Rules that the cluster role applies.
- Must be non-empty if I(state) is C(present).
type: list
elements: dict
suboptions:
verbs:
description:
- Permissions to be applied by the rule.
type: list
elements: str
required: yes
choices: [get, list, create, update, delete]
resources:
description:
- Types of resources the rule has permission to access.
type: list
elements: str
required: yes
resource_names:
description:
- Names of specific resources the rule has permission to access.
- Note that for the C(create) verb, this argument will not be
taken into account when enforcing RBAC, even if it is provided.
type: list
elements: str
'''
EXAMPLES = '''
- name: Create a cluster role
sensu.sensu_go.cluster_role:
name: readonly
rules:
- verbs:
- get
- list
resources:
- checks
- entities
- name: Delete a cluster role
sensu.sensu_go.cluster_role:
name: readonly
state: absent
'''
RETURN = '''
object:
description: Object representing Sensu cluster role.
returned: success
type: dict
sample:
metadata:
name: cluster-role
rules:
- resource_names:
- sample-name
resources:
- assets
- checks
verbs:
- get
- list
'''
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils, role_utils
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth", "name", "state"),
rules=dict(
type="list",
elements="dict",
options=dict(
verbs=dict(
required=True,
type="list",
elements="str",
choices=["get", "list", "create", "update", "delete"],
),
resources=dict(
required=True,
type="list",
elements="str",
),
resource_names=dict(
type="list",
elements="str",
),
)
)
)
)
msg = role_utils.validate_module_params(module.params)
if msg:
module.fail_json(msg=msg)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_core_v2_path(
None, "clusterroles", module.params["name"],
)
payload = arguments.get_mutation_payload(
module.params, "rules"
)
try:
changed, cluster_role = utils.sync(
module.params['state'], client, path,
payload, module.check_mode, role_utils.do_roles_differ
)
module.exit_json(changed=changed, object=cluster_role)
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,145 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com>
# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["stableinterface"],
"supported_by": "certified",
}
DOCUMENTATION = '''
module: cluster_role_binding
author:
- Paul Arthur (@flowerysong)
- Manca Bizjak (@mancabizjak)
- Aljaz Kosir (@aljazkosir)
- Tadej Borovsak (@tadeboro)
short_description: Manage Sensu cluster role bindings
description:
- Create, update or delete Sensu cluster role binding.
- For more information, refer to the Sensu documentation at
U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings).
version_added: 1.0.0
extends_documentation_fragment:
- sensu.sensu_go.requirements
- sensu.sensu_go.auth
- sensu.sensu_go.name
- sensu.sensu_go.state
options:
cluster_role:
description:
- Name of the cluster role.
- Required if I(state) is C(present).
type: str
users:
description:
- List of users to bind to the cluster role.
- Note that at least one of I(users) and I(groups) must be
specified when creating a cluster role binding.
type: list
elements: str
groups:
description:
- List of groups to bind to the cluster role.
- Note that at least one of I(users) and I(groups) must be
specified when creating a cluster role binding.
type: list
elements: str
seealso:
- module: sensu.sensu_go.cluster_role_binding_info
- module: sensu.sensu_go.cluster_role
- module: sensu.sensu_go.role_binding
'''
EXAMPLES = '''
- name: Create a cluster role binding
sensu.sensu_go.cluster_role_binding:
name: all-cluster-admins
cluster_role: cluster-admin
groups:
- cluster-admins
users:
- alice
- name: Delete a cluster role binding
sensu.sensu_go.cluster_role_binding:
name: all-cluster-admins
state: absent
'''
RETURN = '''
object:
description: Object representing Sensu cluster role binding.
returned: success
type: dict
sample:
metadata:
name: cluster-admin
role_ref:
name: cluster-admin
type: ClusterRole
subjects:
- name: cluster-admins
type: Group
'''
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import arguments, errors, utils, role_utils
def build_api_payload(params):
payload = arguments.get_mutation_payload(params)
payload["subjects"] = role_utils.build_subjects(params["groups"], params["users"])
payload["role_ref"] = role_utils.type_name_dict("ClusterRole", params["cluster_role"])
return payload
def main():
required_if = [
("state", "present", ["cluster_role"])
]
module = AnsibleModule(
required_if=required_if,
supports_check_mode=True,
argument_spec=dict(
arguments.get_spec("auth", "name", "state"),
cluster_role=dict(),
users=dict(
type="list", elements="str",
),
groups=dict(
type="list", elements="str",
),
)
)
msg = role_utils.validate_binding_module_params(module.params)
if msg:
module.fail_json(msg=msg)
client = arguments.get_sensu_client(module.params["auth"])
path = utils.build_core_v2_path(
None, "clusterrolebindings", module.params["name"],
)
payload = build_api_payload(module.params)
try:
changed, cluster_role_binding = utils.sync(
module.params["state"], client, path, payload, module.check_mode, role_utils.do_role_bindings_differ
)
module.exit_json(changed=changed, object=cluster_role_binding)
except errors.Error as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

Some files were not shown because too many files have changed in this diff Show More