Page MenuHomeDevCentral

D2794.diff
No OneTemporary

D2794.diff

diff --git a/_modules/rabbitmq.py b/_modules/rabbitmq.py
--- a/_modules/rabbitmq.py
+++ b/_modules/rabbitmq.py
@@ -19,6 +19,11 @@
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def get_password(credential):
+ secret = __salt__["vault.read_secret"](credential)
+ return compute_password_hash(secret["password"])
+
+
def compute_password_hash(password):
salt = secrets.randbits(32)
return _compute_password_hash_with_salt(salt, password)
diff --git a/_modules/rabbitmq_api.py b/_modules/rabbitmq_api.py
new file mode 100644
--- /dev/null
+++ b/_modules/rabbitmq_api.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+
+# -------------------------------------------------------------
+# Salt - RabbitMQ management HTTP API client
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Connect to RabbitMQ management HTTP API
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import json
+import logging
+
+
+import requests
+from requests.auth import HTTPBasicAuth
+import yaml
+
+
+log = logging.getLogger(__name__)
+
+
+HTTP_SUCCESS_CODES = [200, 201, 204]
+HTTP_CONTENT_CODES = [200]
+
+
+def _request(cluster, method, path, data=None):
+ args = __opts__["rabbitmq"][cluster]
+
+ url = args["url"] + "/" + path
+
+ if args["auth"] == "basic":
+ auth = HTTPBasicAuth(args["user"], args["password"])
+ else:
+ raise RuntimeError(
+ f"RabbitMQ HTTP API authentication scheme not supported: {args['auth']}"
+ )
+
+ headers = {
+ "User-agent": "Salt-RabbitMQ/1.0",
+ }
+
+ if data is not None:
+ data = json.dumps(data)
+
+ log.debug(f"HTTP request {method} to {url}")
+ log.trace(f"Payload: {data}")
+ r = requests.request(method, url, headers=headers, auth=auth, data=data)
+
+ if r.status_code not in HTTP_SUCCESS_CODES:
+ log.error(f"HTTP status code {r.status_code}, 2xx expected.")
+ raise RuntimeError(f"Status code is {r.status_code}")
+
+ if r.status_code not in HTTP_CONTENT_CODES:
+ log.trace(
+ f"HTTP response is {r.status_code}. The API doesn't include any content for this code."
+ )
+ return True
+
+ return r.json()
+
+
+# -------------------------------------------------------------
+# Application entry point
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+ARGS_VHOST = ["description", "tags", "tracing"]
+
+
+def overview(cluster):
+ return _request(cluster, "GET", "overview")
+
+
+def list_vhosts(cluster):
+ return _request(cluster, "GET", "vhosts")
+
+
+def get_vhost(cluster, vhost):
+ vhost = requests.utils.quote(vhost, safe="")
+ return _request(cluster, "GET", f"vhosts/{vhost}")
+
+
+def update_vhost(cluster, vhost, **kwargs):
+ vhost = requests.utils.quote(vhost, safe="")
+ data = {}
+ for arg in ARGS_VHOST:
+ if arg in kwargs:
+ data[arg] = kwargs[arg]
+
+ return _request(cluster, "PUT", f"vhosts/{vhost}", data)
+
+
+def delete_vhost(cluster, vhost):
+ vhost = requests.utils.quote(vhost, safe="")
+ return _request(cluster, "DELETE", f"vhosts/{vhost}")
+
+
+def vhost_exists(cluster, vhost):
+ return vhost in [result["name"] for result in list_vhosts(cluster)]
diff --git a/_states/rabbitmq.py b/_states/rabbitmq.py
new file mode 100644
--- /dev/null
+++ b/_states/rabbitmq.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+# -------------------------------------------------------------
+# Salt - RabbitMQ management HTTP API state module
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Configure RabbitMQ through management HTTP API
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import logging
+
+
+log = logging.getLogger(__name__)
+
+
+def vhost_present(name, cluster, description="", tags=[], tracing=False):
+ ret = {"name": name, "result": False, "changes": {}, "comment": ""}
+
+ expected = {
+ "description": description,
+ "tags": tags,
+ "tracing": tracing,
+ }
+ actual = {}
+ is_existing = False
+
+ if __salt__["rabbitmq_api.vhost_exists"](cluster, name):
+ vhost = __salt__["rabbitmq_api.get_vhost"](cluster, name)
+ is_existing = True
+ actual = {
+ "description": vhost["metadata"]["description"],
+ "tags": vhost["metadata"]["tags"],
+ "tracing": vhost["tracing"],
+ }
+
+ if actual == expected:
+ ret["result"] = True
+ ret["comment"] = f"Vhost {name} is up to date"
+ return ret
+
+ ret["changes"] = _changes(actual, expected)
+ update_verb = "updated" if is_existing else "created"
+
+ if __opts__["test"]:
+ ret["result"] = None
+ ret["comment"] = f"Vhost {name} will be {update_verb}"
+ return ret
+
+ try:
+ __salt__["rabbitmq_api.update_vhost"](cluster, name, **expected)
+ except Exception as e:
+ log.error("Can't update RabbitMQ vhost: " + e)
+ ret["comment"] = e
+ return ret
+
+ ret["result"] = True
+ ret["comment"] = f"Vhost {update_verb}"
+
+ return ret
+
+
+def vhost_absent(name, cluster):
+ ret = {"name": name, "result": False, "changes": {}, "comment": ""}
+
+ if not __salt__["rabbitmq_api.vhost_exists"](cluster, name):
+ ret["result"] = True
+ ret["comment"] = f"Vhost {name} is absent"
+ return ret
+
+ if __opts__["test"]:
+ ret["result"] = None
+ ret["comment"] = f"Vhost {name} will be deleted"
+ return ret
+
+ try:
+ __salt__["rabbitmq_api.delete_vhost"](cluster, name)
+ except Exception as e:
+ log.error("Can't delete RabbitMQ vhost: " + e)
+ ret["comment"] = e
+ return ret
+
+ ret["result"] = True
+ ret["comment"] = f"Vhost deleted"
+
+ return ret
+
+
+def _changes(actual, expected):
+ """Compute a changes dictionary between actual and expected state dictionaries."""
+ changes = {}
+ for k, v in expected.items():
+ if k not in actual:
+ changes[k] = {"old": None, "new": expected[k]}
+ elif actual[k] != expected[k]:
+ changes[k] = {"old": actual[k], "new": expected[k]}
+
+ return changes
diff --git a/pillar/saas/rabbitmq.sls b/pillar/saas/rabbitmq.sls
new file mode 100644
--- /dev/null
+++ b/pillar/saas/rabbitmq.sls
@@ -0,0 +1,103 @@
+# -------------------------------------------------------------
+# Salt — RabbitMQ
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+# -------------------------------------------------------------
+# RabbitMQ clusters
+#
+# Each cluster is defined by a deployment method (e.g. docker),
+# and the node we can use to configure it.
+#
+# The cluster configuration is a collection of vhosts and users:
+#
+# vhosts:
+# <vhost name>: <configuration>
+#
+# users:
+# <user>: <password FULL secret path in Vault>
+#
+# In addition, a root account is managed by deployment states.
+#
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#
+# The vhost configuration allows to define the exchanges and queues,
+# and the permissions users have on them.
+#
+# exchanges:
+# type is 'direct', 'topic' or 'fanout'
+#
+# queues:
+# Application can create their own ephemeral queue.
+# For that, it needs configure permission on the vhost.
+#
+# If an application needs a stable one, it should be configured here,
+# so we can drop the configure permission.
+#
+# permissions:
+# See https://www.rabbitmq.com/access-control.html#authorisation
+# for the needed permissions for an AMQP operation
+#
+# To give access to server-generated queue names, use amq\.gen.*
+# To not give any access, use blank string
+#
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+rabbitmq_clusters:
+ white-rabbit:
+ deployment: docker
+ node: docker-002
+ container: white-rabbit
+ url: https://white-rabbit.nasqueron.org/
+
+ vhosts:
+
+ ###
+ ### Nasqueron dev services:
+ ### - Notifications center
+ ###
+
+ dev: &nasqueron-dev-services-vhost
+ description: Nasqueron dev services
+
+ exchanges:
+ # Producer: Notifications center
+ # Consumers: any notifications client
+ notifications:
+ type: topic
+
+ queues:
+ # Used by Wearg to stream notifications to IRC
+ wearg-notifications:
+ routing_key: "#"
+ binds:
+ - notifications
+
+ permissions:
+ # Notifications center (paas-docker role / notifications container)
+ notifications:
+ configure: ''
+ read: ''
+ write: '^notifications$'
+
+ # Wearg (viperserv role)
+ wearg:
+ configure: ''
+ read: '^wearg\-notifications$'
+ write: ''
+
+ # Notifications CLI clients
+ notifications-ysul: &notifications-client-permissions
+ configure: '^amq\.gen.*$'
+ read: '^(amq\.gen.*|notifications)$'
+ write: '^amq\.gen.*$'
+ notifications-windriver: *notifications-client-permissions
+
+ users:
+ # Notifications center server and clients
+ notifications: ops/secrets/nasqueron.notifications.broker
+ wearg: apps/viperserv/broker
+ notifications-ysul: ops/secrets/nasqueron/notifications/notifications-cli/ysul
+ notifications-windriver: ops/secrets/nasqueron/notifications/notifications-cli/windriver
diff --git a/roles/saas-rabbitmq/init.sls b/roles/saas-rabbitmq/init.sls
new file mode 100644
--- /dev/null
+++ b/roles/saas-rabbitmq/init.sls
@@ -0,0 +1,9 @@
+# -------------------------------------------------------------
+# Salt — RabbitMQ
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+include:
+ - .server
diff --git a/roles/saas-rabbitmq/server/content.sls b/roles/saas-rabbitmq/server/content.sls
new file mode 100644
--- /dev/null
+++ b/roles/saas-rabbitmq/server/content.sls
@@ -0,0 +1,81 @@
+# -------------------------------------------------------------
+# Salt — RabbitMQ
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+
+{% for cluster, cluster_args in pillar['rabbitmq_clusters'].items() %}
+
+
+# -------------------------------------------------------------
+# RabbitMQ vhosts
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% for vhost, vhost_args in cluster_args["vhosts"].items() %}
+rabbitmq_cluster_{{ cluster }}_vhost_{{ vhost }}:
+ rabbitmq.vhost_present:
+ - name: {{ vhost }}
+ - cluster: {{ cluster }}
+ - description: {{ vhost_args["description"] }}
+{% endfor %}
+
+# -------------------------------------------------------------
+# RabbitMQ exchanges and queues
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% for vhost, vhost_args in cluster_args["vhosts"].items() %}
+
+{% for exchange, exchange_args in vhost_args.get("exchanges", {}).items() %}
+rabbitmq_cluster_{{ cluster }}_vhost_{{ vhost }}_exchange_{{ exchange }}:
+ rabbitmq.exchange_present:
+ - name: {{ exchange }}
+ - cluster: {{ cluster }}
+ - vhost: {{ vhost }}
+ - type: {{ exchange_args["type"] }}
+{% endfor %}
+
+{% for queue, queue_args in vhost_args.get("queues", {}).items() %}
+rabbitmq_cluster_{{ cluster }}_vhost_{{ vhost }}_queue_{{ queue }}:
+ rabbitmq.queue_present:
+ - name: {{ queue }}
+ - cluster: {{ cluster }}
+ - vhost: {{ vhost }}
+ - routing_key: {{ queue_args["routing_key"] }}
+ - binds: {{ queue_args.get("binds", []) }}
+{% endfor %}
+
+{% endfor %}
+
+# -------------------------------------------------------------
+# RabbitMQ users and permissions
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% for user, credential in cluster_args['users'].items() %}
+
+rabbitmq_cluster_{{ cluster }}_user_{{ user }}:
+ rabbitmq.user_present:
+ - name: {{ user }}
+ - cluster: {{ cluster }}
+ - credential: {{ credential }}
+
+{% for vhost, vhost_args in cluster_args["vhosts"].items() %}
+{% if user in vhost_args.get("permissions", {}) %}
+{% set privilege = vhost_args["permissions"][user] %}
+rabbitmq_cluster_{{ cluster }}_vhost_{{ vhost }}_permissions_user_{{ user }}:
+ rabbitmq.user_permissions:
+ - cluster: {{ cluster }}
+ - vhost: {{ vhost }}
+ - user: {{ user }}
+ - permissions:
+ configure: {{ privilege['configure'] }}
+ write: {{ privilege['write'] }}
+ read: {{ privilege['read'] }}
+{% endif %}
+{% endfor %}
+
+{% endfor %}
+
+
+{% endfor %}
diff --git a/roles/saas-rabbitmq/server/init.sls b/roles/saas-rabbitmq/server/init.sls
new file mode 100644
--- /dev/null
+++ b/roles/saas-rabbitmq/server/init.sls
@@ -0,0 +1,12 @@
+# -------------------------------------------------------------
+# Salt — RabbitMQ
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+include:
+ - .software
+
+ # Content includes vhosts, exchanges, queues, users, privileges
+ - .content
diff --git a/roles/saas-rabbitmq/server/software.sls b/roles/saas-rabbitmq/server/software.sls
new file mode 100644
--- /dev/null
+++ b/roles/saas-rabbitmq/server/software.sls
@@ -0,0 +1,8 @@
+# -------------------------------------------------------------
+# Salt — RabbitMQ
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+# This state is left intentionally blank.
diff --git a/services.sls b/services.sls
new file mode 100644
--- /dev/null
+++ b/services.sls
@@ -0,0 +1,14 @@
+# -------------------------------------------------------------
+# Salt configuration for Nasqueron servers :: services
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: List of the roles configured through services API.
+# They are typically run on the Salt primary server,
+# especially as they can need Vault credentials,
+# but they don't touch any file *directly*.
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+base:
+ 'local':
+ - roles/saas-rabbitmq

File Metadata

Mime Type
text/plain
Expires
Tue, Nov 19, 03:36 (21 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2251856
Default Alt Text
D2794.diff (14 KB)

Event Timeline