Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3768753
D2794.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D2794.id.diff
View Options
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: ¬ifications-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
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 24, 10:11 (16 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2258669
Default Alt Text
D2794.id.diff (14 KB)
Attached To
Mode
D2794: WIP: Provision RabbitMQ configuration
Attached
Detach File
Event Timeline
Log In to Comment