mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 06:30:22 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e35ac13390 | |||
| 94255e2393 | |||
| 02b13d8e0c | |||
| 1f91de480e | |||
| eb29b4e298 | |||
| 5841d3f7e4 | |||
| f44b910546 | |||
| 52896241c5 | |||
| 1b36c6e1b6 | |||
| d5d5e8bf7e | |||
| b2967fb2e5 | |||
| 7fb5dd7b55 | |||
| 2ef8437d8b | |||
| 6dbcfcae58 | |||
| ab8d335767 | |||
| 99cea4abe8 | |||
| 04379dc7a9 | |||
| 0800b260d5 | |||
| 67feea693e | |||
| db1676cb0f | |||
| 4b57387110 | |||
| e3373e1b49 | |||
| 453c880efc | |||
| dc2b063b6e | |||
| 65ee66dc32 | |||
| df9fe93a98 | |||
| 84fba105c9 | |||
| f75f08db4a | |||
| be1dc130d6 | |||
| 178a62c08f | |||
| 362a4fc76d | |||
| 8e75edc9f5 | |||
| a031426715 | |||
| 962b473a5f | |||
| de6f3921a4 | |||
| 0a45a96fac | |||
| c2afb88e79 | |||
| 9a3a07cd2e | |||
| 28e809b9d8 | |||
| eb14615cf5 | |||
| d652652b79 | |||
| a2087a1f1f | |||
| f195ced6a0 | |||
| a33d1cfe4f | |||
| 40cbb66638 | |||
| 08b5ac52cf | |||
| 0c1ccf1f04 | |||
| f28a3e8390 | |||
| 3685d231cf | |||
| f3c5d603a0 | |||
| 5050caa3d3 | |||
| 4ed31c2c66 |
@@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SuperMQ API Documentation</title>
|
||||
<title>Magistrala API Documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.30.3/swagger-ui.css">
|
||||
<style>
|
||||
body {
|
||||
@@ -101,7 +101,7 @@ SPDX-License-Identifier: Apache-2.0
|
||||
</head>
|
||||
<body>
|
||||
<div class="service-selector">
|
||||
<h1>SuperMQ API Documentation</h1>
|
||||
<h1>Magistrala API Documentation</h1>
|
||||
<div class="service-dropdown-container">
|
||||
<label for="serviceDropdown">Select Service:</label>
|
||||
<select id="serviceDropdown" class="service-dropdown"></select>
|
||||
|
||||
@@ -22,10 +22,14 @@ jobs:
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
|
||||
- name: Get Go version from go.mod
|
||||
id: go-version
|
||||
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: ${{ steps.go-version.outputs.version }}
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Set GOBIN
|
||||
@@ -46,10 +50,10 @@ jobs:
|
||||
verbose: true
|
||||
|
||||
- name: Set up Docker Build
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
|
||||
@@ -42,4 +42,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./swagger-ui
|
||||
cname: docs.api.supermq.absmach.eu
|
||||
cname: docs.api.magistrala.absmach.eu
|
||||
|
||||
@@ -17,10 +17,14 @@ jobs:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get Go version from go.mod
|
||||
id: go-version
|
||||
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: ${{ steps.go-version.outputs.version }}
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Set GOBIN
|
||||
@@ -47,7 +51,7 @@ jobs:
|
||||
- name: Run linters
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.4.0
|
||||
version: latest
|
||||
args: --config ./tools/config/.golangci.yaml
|
||||
|
||||
run-tests:
|
||||
@@ -61,14 +65,18 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get Go version from go.mod
|
||||
id: go-version
|
||||
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: ${{ steps.go-version.outputs.version }}
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Check for changes in specific paths
|
||||
uses: dorny/paths-filter@v3
|
||||
uses: dorny/paths-filter@v4
|
||||
id: changes
|
||||
with:
|
||||
base: main
|
||||
@@ -133,7 +141,6 @@ jobs:
|
||||
- "alarms/**"
|
||||
- "cmd/alarms/**"
|
||||
|
||||
|
||||
- name: Create coverage directory
|
||||
run: |
|
||||
mkdir coverage
|
||||
|
||||
@@ -7,7 +7,31 @@ SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-wri
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
GOARCH ?= amd64
|
||||
# Auto-detect architecture: use arm64 for Apple Silicon, default to amd64 otherwise
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_M),arm64)
|
||||
GOARCH ?= arm64
|
||||
else ifeq ($(UNAME_M),aarch64)
|
||||
GOARCH ?= arm64
|
||||
else
|
||||
GOARCH ?= amd64
|
||||
endif
|
||||
|
||||
# Detect OS for sed compatibility: macOS (BSD sed) vs Linux (GNU sed)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
else
|
||||
SED_INPLACE := sed -i
|
||||
endif
|
||||
|
||||
# For Apple Silicon: use amd64 platform for pre-built images (emulation via Rosetta)
|
||||
# This is needed because upstream stable images may not have ARM64 builds
|
||||
ifeq ($(UNAME_M),arm64)
|
||||
DOCKER_PLATFORM := --platform linux/amd64
|
||||
else
|
||||
DOCKER_PLATFORM :=
|
||||
endif
|
||||
VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'unknown')
|
||||
COMMIT ?= $(shell git rev-parse HEAD)
|
||||
TIME ?= $(shell date +%F_%T)
|
||||
@@ -20,7 +44,7 @@ DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config
|
||||
DEFAULT_DOCKER_COMPOSE_COMMAND := up
|
||||
GRPC_MTLS_CERT_FILES_EXISTS = 0
|
||||
MOCKERY = $(GOBIN)/mockery
|
||||
MOCKERY_VERSION=3.5.3
|
||||
MOCKERY_VERSION=3.7.0
|
||||
PKG_PROTO_GEN_OUT_DIR=api/grpc
|
||||
INTERNAL_PROTO_DIR=internal/proto
|
||||
INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed 's|$(INTERNAL_PROTO_DIR)/||')
|
||||
@@ -71,6 +95,31 @@ define make_docker_dev
|
||||
-f docker/Dockerfile.dev ./build
|
||||
endef
|
||||
|
||||
define run_with_arch_detection
|
||||
@echo "Detecting architecture..."
|
||||
@if [ "$(DETECTED_ARCH)" = "arm64" ] || [ "$(DETECTED_ARCH)" = "aarch64" ]; then \
|
||||
echo "ARM64 architecture detected."; \
|
||||
git checkout $(1); \
|
||||
GOARCH=arm64 $(MAKE) dockers; \
|
||||
for svc in $(SERVICES); do \
|
||||
docker tag ghcr.io/absmach/magistrala/$$svc ghcr.io/absmach/magistrala/$$svc:latest; \
|
||||
done; \
|
||||
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=latest/' docker/.env && rm -f docker/.env.bak; \
|
||||
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
-f docker/addons/timescale-reader/docker-compose.yaml \
|
||||
-f docker/addons/timescale-writer/docker-compose.yaml \
|
||||
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
|
||||
else \
|
||||
echo "x86_64 architecture detected."; \
|
||||
git checkout $(1); \
|
||||
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$(2)/' docker/.env && rm -f docker/.env.bak; \
|
||||
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
-f docker/addons/timescale-reader/docker-compose.yaml \
|
||||
-f docker/addons/timescale-writer/docker-compose.yaml \
|
||||
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
|
||||
fi
|
||||
endef
|
||||
|
||||
ADDON_SERVICES = bootstrap provision certs timescale-reader timescale-writer postgres-reader postgres-writer
|
||||
|
||||
EXTERNAL_SERVICES = prometheus
|
||||
@@ -229,21 +278,21 @@ grpc_mtls_certs:
|
||||
|
||||
check_tls:
|
||||
ifeq ($(GRPC_TLS),true)
|
||||
@unset GRPC_MTLS
|
||||
@bash -c 'unset GRPC_MTLS'
|
||||
@echo "gRPC TLS is enabled"
|
||||
GRPC_MTLS=
|
||||
else
|
||||
@unset GRPC_TLS
|
||||
@bash -c 'unset GRPC_TLS'
|
||||
GRPC_TLS=
|
||||
endif
|
||||
|
||||
check_mtls:
|
||||
ifeq ($(GRPC_MTLS),true)
|
||||
@unset GRPC_TLS
|
||||
@bash -c 'unset GRPC_TLS'
|
||||
@echo "gRPC MTLS is enabled"
|
||||
GRPC_TLS=
|
||||
else
|
||||
@unset GRPC_MTLS
|
||||
@bash -c 'unset GRPC_MTLS'
|
||||
GRPC_MTLS=
|
||||
endif
|
||||
|
||||
@@ -260,25 +309,26 @@ fetch_supermq:
|
||||
@./scripts/supermq.sh
|
||||
|
||||
run_latest: check_certs
|
||||
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
-f docker/addons/timescale-reader/docker-compose.yaml \
|
||||
-f docker/addons/timescale-writer/docker-compose.yaml \
|
||||
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
|
||||
|
||||
run_stable: check_certs
|
||||
$(eval version = $(shell git describe --abbrev=0 --tags))
|
||||
git checkout $(version)
|
||||
sed -i 's/^SMQ_RELEASE_TAG=.*/SMQ_RELEASE_TAG=$(version)/' docker/supermq-docker/.env
|
||||
sed -i 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$(version)/' docker/.env
|
||||
docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
|
||||
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
-f docker/addons/timescale-reader/docker-compose.yaml \
|
||||
-f docker/addons/timescale-writer/docker-compose.yaml \
|
||||
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
|
||||
@version=$$(git describe --abbrev=0 --tags 2>/dev/null) || { echo "Error: No git tags found. Please create a release tag first (e.g., git tag v0.1.0) or use 'make run_latest' instead."; exit 1; }; \
|
||||
echo "Using stable version: $$version"; \
|
||||
git checkout $$version; \
|
||||
$(SED_INPLACE) "s/^SMQ_RELEASE_TAG=.*/SMQ_RELEASE_TAG=$$version/" docker/supermq-docker/.env; \
|
||||
$(SED_INPLACE) "s/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$$version/" docker/.env; \
|
||||
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
|
||||
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
|
||||
-f docker/addons/timescale-reader/docker-compose.yaml \
|
||||
-f docker/addons/timescale-writer/docker-compose.yaml \
|
||||
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
|
||||
|
||||
|
||||
run_addons: check_certs
|
||||
$(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC))))
|
||||
@for SVC in $(RUN_ADDON_ARGS); do \
|
||||
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
|
||||
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
|
||||
done
|
||||
|
||||
@@ -1,64 +1,60 @@
|
||||
> [!WARNING]
|
||||
> This repository is obsolete. All of its content has been merged to [www.github.com/absmach/magistrala](https://www.github.com/absmach/magistrala).
|
||||
> Please use that repository for all active development.
|
||||
|
||||
<div align="center">
|
||||
|
||||
# Magistrala
|
||||
# SuperMQ
|
||||
|
||||
### A Modern IoT Platform Built on SuperMQ
|
||||
### Planetary event-driven infrastructure
|
||||
|
||||
### Scalable • Secure • Open-Source
|
||||
**Made with ❤️ by [Abstract Machines](https://absmach.eu/)**
|
||||
|
||||
[](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
|
||||
[](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
|
||||
[](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
|
||||
[](https://goreportcard.com/report/github.com/absmach/magistrala)
|
||||
[](https://deepwiki.com/absmach/magistrala)
|
||||
[](https://codecov.io/gh/absmach/magistrala)
|
||||
[](LICENSE)
|
||||
[](https://matrix.to/#/#magistrala:matrix.org)
|
||||
|
||||
### [Guide](https://docs.magistrala.absmach.eu) | [Contributing](CONTRIBUTING.md) | [Website](https://www.absmach.eu/magistrala) | [Chat](https://matrix.to/#/#magistrala:matrix.org)
|
||||
|
||||
Made with ❤️ by [Abstract Machines](https://www.absmach.eu)
|
||||
[](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
|
||||
[](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yaml)
|
||||
[](https://codecov.io/gh/absmach/magistrala)
|
||||
[](LICENSE)
|
||||
[](https://matrix.to/#/#supermq:matrix.org)
|
||||
|
||||
### [Guide](https://magistrala.absmach.eu/docs/) | [Contributing](CONTRIBUTING.md) | [Website](https://absmach.eu/) | [Chat](https://matrix.to/#/#supermq:matrix.org)
|
||||
|
||||
</div>
|
||||
|
||||
## Introduction 🌍
|
||||
## Introduction 📖
|
||||
|
||||
Magistrala is a cutting-edge, open-source IoT cloud platform built on top of [SuperMQ](https://github.com/absmach/supermq). It serves as a robust middleware solution for building complex IoT applications. With Magistrala, you can connect and manage IoT devices seamlessly using multi-protocol support, all while ensuring security and scalability.
|
||||
SuperMQ is a distributed, highly scalable, and secure open-source cloud platform for messaging and event-driven architecture (EDA). It is a planetarily distributed, highly scalable, and secure platform that serves as a robust foundation for building advanced real-time and reactive systems.
|
||||
|
||||
### Key Benefits
|
||||
## Why SuperMQ Stands Out 🚀
|
||||
|
||||
- **Unified IoT Management**: Connect sensors, actuators and applications over various network protocols.
|
||||
- **Scalability and Performance**: Designed to handle enterprise-grade IoT deployments.
|
||||
- **Secure by Design**: Features such as mutual TLS authentication and fine-grained access control.
|
||||
- **Open-Source Freedom**: Patent-free, community-driven, and designed for extensibility.
|
||||
SuperMQ bridges the gap between various network protocols (HTTP, MQTT, WebSocket, CoAP, and more) to provide a seamless messaging experience. Whether you're working on IoT solutions, real-time data pipelines, or event-driven systems, MagisSuperMQtrala has you covered. 🌐✨
|
||||
|
||||
## ✨ Features
|
||||
## Key Features 🌟
|
||||
|
||||
- 🏢 **Multi-Tenancy**: Support for managing multiple independent domains seamlessly.
|
||||
- 👥 **Multi-User Platform**: Unlimited organizational hierarchies and user roles for streamlined collaboration.
|
||||
- 🌐 **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more (see [contrib repository](https://www.github.com/absmach/mg-contrib) for LoRa and OPC UA).
|
||||
- 💻 **Device Management and Provisioning**: Including Zero-Touch provisioning for seamless device onboarding.
|
||||
- 🛡️ **Mutual TLS Authentication (mTLS)**: Secure communication using X.509 certificates.
|
||||
- 📜 **Fine-Grained Access Control**: Support for ABAC and RBAC policies.
|
||||
- 💾 **Message Persistence**: Timescale and PostgreSQL support (see [contrib repository](https://www.github.com/absmach/mg-contrib) for Cassandra, InfluxDB, and MongoDB).
|
||||
- 🔄 **Rules Engine (RE)**: Automate processes with flexible rules for decision-making.
|
||||
- 🚨 **Alarms and Triggers**: Immediate notifications for critical IoT events.
|
||||
- 📅 **Scheduled Actions**: Plan and execute tasks at predefined times.
|
||||
- 📝 **Audit Logs**: Maintain a detailed history of platform activities for compliance and debugging.
|
||||
- 📊 **Platform Logging and Instrumentation**: Integrated with Prometheus and OpenTelemetry.
|
||||
- ⚡ **Event Sourcing**: Streamlined architecture for real-time IoT event processing.
|
||||
- 🐳 **Container-Based Deployment**: Fully compatible with Docker and Kubernetes.
|
||||
- 🌍 **Edge and IoT Ready**: Agent and Export services for managing remote IoT gateways.
|
||||
- 🛠️ **Developer Tools**: Comprehensive SDK and CLI for efficient development.
|
||||
- 🏗️ **Domain-Driven Design**: High-quality codebase and extensive test coverage.
|
||||
- **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more! 🌉
|
||||
- **Secure by Design**: Mutual TLS (mTLS) with X.509 Certificates, JWT support, and multi-protocol authorization. 🔒
|
||||
- **Fine-Grained Access Control**: Support for ABAC and RBAC policies. 📜
|
||||
- **Multi-Tenant**: Manage multiple domains seamlessly. 🏢
|
||||
- **Multi-User**: Unlimited organizational hierarchies for user management. 👥
|
||||
- **Application Management**: Group and share messaging clients for streamlined operations. 📱
|
||||
- **Ease of Use**: Simple and powerful communication channel management, grouping, and sharing. ✨
|
||||
- **Personal Access Tokens (PATs)**: Scoped and revocable tokens for enhanced security. 🔑
|
||||
- **Observability**: Integrated logging and instrumentation with Prometheus and OpenTelemetry. 📈
|
||||
- **Event Sourcing**: Build robust and scalable architectures. ⚡
|
||||
- **Edge and IoT Ready**: Supports MQTT and CoAP protocols for seamless IoT gateway and sensor communication and management. 🌍
|
||||
- **Developer-Friendly**: SDKs, CLI tools, and comprehensive documentation to get you started. 👩💻👨💻
|
||||
- **Production-Ready**: Container-based deployment using Docker and Kubernetes. 🐳☸️
|
||||
|
||||
## Installation 🛠️
|
||||
|
||||
There are multiple ways to run Magistrala.
|
||||
There are multiple ways to run SuperMQ.
|
||||
First, clone the repository and position to it:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/absmach/magistrala.git
|
||||
cd magistrala
|
||||
git clone https://github.com/absmach/supermq.git
|
||||
cd supermq
|
||||
```
|
||||
|
||||
To run the latest stable (tagged) version, use:
|
||||
@@ -86,48 +82,76 @@ The `make run_stable` command will:
|
||||
git checkout main
|
||||
```
|
||||
|
||||
### Running on Apple Silicon (M1/M2/M3) Macs
|
||||
|
||||
## 📤 Usage
|
||||
When running SuperMQ on Apple Silicon Macs, the Makefile will automatically detect your ARM64 architecture and build Docker images locally.
|
||||
|
||||
**If using Docker Desktop:**
|
||||
|
||||
1. **Enable Apple Virtualization Framework**: In Docker Desktop, go to:
|
||||
- Settings → General → Enable "Use the new Virtualization framework"
|
||||
|
||||
2. **Enable Rosetta for x86_64 Emulation**: In Docker Desktop, go to:
|
||||
- Settings → General → Enable "Use Rosetta for x86_64/amd64 emulation on Apple Silicon"
|
||||
|
||||
After enabling these options, restart Docker Desktop, then run `make run_stable` or `make run_latest` as usual.
|
||||
|
||||
To manually run SuperMQ, clone the repository and start all core services:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/docker-compose.yaml --env-file docker/.env up
|
||||
```
|
||||
|
||||
### Usage 📤📥
|
||||
|
||||
**Using the CLI :**
|
||||
|
||||
Check the health of a specific service using the CLI:
|
||||
|
||||
```bash
|
||||
make cli
|
||||
./build/cli health <service>
|
||||
./build/supermq-cli status
|
||||
```
|
||||
|
||||
Replace `<service>` with the name of the service you want to check.
|
||||
This command retrieves the status of the SuperMQ server and outputs it to the console.
|
||||
|
||||
**Using Curl :**
|
||||
|
||||
Alternatively, use a simple HTTP `GET` request to check the platform's health:
|
||||
**Using HTTP with Curl :**
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8080/health
|
||||
curl -X GET http://localhost:8080/status
|
||||
```
|
||||
|
||||
For additional usage examples and advanced configurations, visit the [official documentation](https://docs.magistrala.absmach.eu).
|
||||
This request fetches the server status over HTTP and provides a JSON response.
|
||||
|
||||
## 📚 Documentation
|
||||
See our [CLI documentation](https://magistrala.absmach.eu/docs/dev-guide/cli/introduction-to-cli/) for more details.
|
||||
|
||||
Complete documentation is available at the [Magistrala official docs page](https://docs.magistrala.absmach.eu).
|
||||
## Documentation 📚
|
||||
|
||||
For CLI usage details, visit the [CLI Documentation](https://docs.magistrala.absmach.eu/dev-guide/cli/introduction-to-cli).
|
||||
The official documentation is hosted at [SuperMQ docs page](https://magistrala.absmach.eu/docs/).
|
||||
|
||||
## 🌐 Community and Contributing
|
||||
Documentation is auto-generated, check out the instructions in the [docs repository](https://github.com/absmach/magistrala-website).
|
||||
If you spot an error or a need for corrections, please let us know - or even better: send us a PR! 💌
|
||||
|
||||
Join the community and contribute to the future of IoT middleware:
|
||||
## Community and Contributing 🤝
|
||||
|
||||
- [Open Issues](https://github.com/absmach/magistrala/issues)
|
||||
- [Contribution Guide](CONTRIBUTING.md)
|
||||
- [Matrix Chat](https://matrix.to/#/#magistrala:matrix.org)
|
||||
Thank you for your interest in SuperMQ and the desire to contribute!
|
||||
|
||||
## 📜 License
|
||||
1. Take a look at our [open issues](https://github.com/absmach/magistrala/issues). The [good-first-issue](https://github.com/absmach/magistrala/labels/good-first-issue) label is specifically for issues that are great for getting started.
|
||||
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
|
||||
3. Make your changes compatible to our workflow.
|
||||
|
||||
Magistrala is open-source software licensed under the [Apache-2.0](LICENSE) license. Contributions are welcome and encouraged!
|
||||
Join our community:
|
||||
|
||||
## 💼 Professional Support
|
||||
- [Matrix Room](https://matrix.to/#/#supermq\:matrix.org)
|
||||
|
||||
Need help deploying Magistrala or integrating it into your systems? Contact **[Abstract Machines](https://www.absmach.eu)** for expert guidance and support.
|
||||
## Professional Support 💼
|
||||
|
||||
Need help deploying SuperMQ or integrating it into your system? Reach out to **[Abstract Machines](https://absmach.eu/)** for professional support and guidance.
|
||||
|
||||
## License 📜
|
||||
|
||||
SuperMQ is open-source software licensed under the [Apache License 2.0](LICENSE). Contributions are welcome!
|
||||
|
||||
## Acknowledgments 🙌
|
||||
|
||||
Special thanks to the amazing contributors who make SuperMQ possible. Check out the [MAINTAINERS](MAINTAINERS) file to see the team behind the magic.
|
||||
|
||||
Ready to build the future of messaging and event-driven systems? Let's get started! 🚀
|
||||
|
||||
+3
-1
@@ -72,6 +72,7 @@ type PageMetadata struct {
|
||||
AssignedBy string `json:"assigned_by" db:"assigned_by"`
|
||||
AcknowledgedBy string `json:"acknowledged_by" db:"acknowledged_by"`
|
||||
ResolvedBy string `json:"resolved_by" db:"resolved_by"`
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
}
|
||||
|
||||
func (a Alarm) Validate() error {
|
||||
@@ -116,6 +117,7 @@ type Repository interface {
|
||||
CreateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
|
||||
UpdateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
|
||||
ViewAlarm(ctx context.Context, alarmID, domainID string) (Alarm, error)
|
||||
ListAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
|
||||
ListAllAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
|
||||
ListUserAlarms(ctx context.Context, userID string, pm PageMetadata) (AlarmsPage, error)
|
||||
DeleteAlarm(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func updateAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(alarmReq)
|
||||
req := request.(updateAlarmReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,21 @@ func (req alarmReq) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type updateAlarmReq struct {
|
||||
alarms.Alarm `json:",inline"`
|
||||
}
|
||||
|
||||
func (req updateAlarmReq) validate() error {
|
||||
if req.Alarm.ID == "" {
|
||||
return errors.New("missing alarm id")
|
||||
}
|
||||
if req.Alarm.AssigneeID == "" && req.Alarm.AcknowledgedBy == "" && req.Alarm.ResolvedBy == "" && len(req.Alarm.Metadata) == 0 {
|
||||
return errors.New("at least one of assignee_id, acknowledged_by, resolved_by, or metadata must be set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listAlarmsReq struct {
|
||||
alarms.PageMetadata
|
||||
}
|
||||
|
||||
@@ -195,12 +195,12 @@ func decodeAlarmReq(_ context.Context, r *http.Request) (any, error) {
|
||||
|
||||
func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return alarmReq{}, apiutil.ErrUnsupportedContentType
|
||||
return updateAlarmReq{}, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
|
||||
req := alarmReq{}
|
||||
req := updateAlarmReq{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req.Alarm); err != nil {
|
||||
return alarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||
return updateAlarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||
}
|
||||
|
||||
req.Alarm.ID = chi.URLParam(r, "alarmID")
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/operations"
|
||||
"github.com/absmach/supermq/auth"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
smqauthz "github.com/absmach/supermq/pkg/authz"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
)
|
||||
@@ -22,50 +24,71 @@ var (
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc alarms.Service
|
||||
authz smqauthz.Authorization
|
||||
svc alarms.Service
|
||||
authz smqauthz.Authorization
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
}
|
||||
|
||||
var _ alarms.Service = (*authorizationMiddleware)(nil)
|
||||
|
||||
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization) alarms.Service {
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation]) (alarms.Service, error) {
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
entitiesOps: entitiesOps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (err error) {
|
||||
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) error {
|
||||
return am.svc.CreateAlarm(ctx, alarm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (dba alarms.Alarm, err error) {
|
||||
// If assignee is present, check if assignee is member of domain
|
||||
|
||||
if err := am.authorize(ctx, alarms.OpUpdateAlarm, session); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
if len(alarm.Metadata) > 0 {
|
||||
if err := am.authorize(ctx, operations.OpUpdateAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.AssigneeID != "" {
|
||||
domainUserId := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
|
||||
if err := am.authorize(ctx, operations.OpAssignAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
domainUserID := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
|
||||
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: domainUserId,
|
||||
Subject: domainUserID,
|
||||
Permission: policies.MembershipPermission,
|
||||
ObjectType: policies.DomainType,
|
||||
Object: session.DomainID,
|
||||
}); err != nil {
|
||||
}, nil); err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.AcknowledgedBy != "" {
|
||||
if err := am.authorize(ctx, operations.OpAcknowledgeAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.ResolvedBy != "" {
|
||||
if err := am.authorize(ctx, operations.OpResolveAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
return am.svc.UpdateAlarm(ctx, session, alarm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, alarms.OpDeleteAlarm, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpDeleteAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return errors.Wrap(errDomainDeleteAlarms, err)
|
||||
}
|
||||
|
||||
@@ -77,23 +100,27 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
|
||||
pm.DomainID = session.DomainID
|
||||
}
|
||||
|
||||
if err := am.authorize(ctx, alarms.OpListAlarms, session); err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
switch err := am.checkSuperAdmin(ctx, session); {
|
||||
case err == nil:
|
||||
session.SuperAdmin = true
|
||||
case errors.Contains(err, svcerr.ErrSuperAdminAction):
|
||||
default:
|
||||
return alarms.AlarmsPage{}, err
|
||||
}
|
||||
|
||||
return am.svc.ListAlarms(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
|
||||
if err := am.authorize(ctx, alarms.OpViewAlarm, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpViewAlarm, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewAlarm(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session) error {
|
||||
perm, err := alarms.GetPermission(op)
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,10 +130,43 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: perm,
|
||||
Object: obj,
|
||||
ObjectType: objType,
|
||||
Permission: perm.String(),
|
||||
}
|
||||
|
||||
return am.authz.Authorize(ctx, pr)
|
||||
var pat *smqauthz.PATReq
|
||||
if session.PatID != "" {
|
||||
opName := am.entitiesOps.OperationName(operations.EntityType, op)
|
||||
pat = &smqauthz.PATReq{
|
||||
UserID: session.UserID,
|
||||
PatID: session.PatID,
|
||||
EntityID: session.DomainID,
|
||||
EntityType: operations.EntityType,
|
||||
Operation: opName,
|
||||
Domain: session.DomainID,
|
||||
}
|
||||
}
|
||||
|
||||
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
|
||||
if session.Role != authn.SuperAdminRole {
|
||||
return svcerr.ErrSuperAdminAction
|
||||
}
|
||||
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
|
||||
SubjectType: policies.UserType,
|
||||
Subject: session.UserID,
|
||||
Permission: policies.AdminPermission,
|
||||
ObjectType: policies.PlatformType,
|
||||
Object: policies.SuperMQObject,
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+83
-11
@@ -165,12 +165,12 @@ func (_c *Repository_DeleteAlarm_Call) RunAndReturn(run func(ctx context.Context
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListAlarms provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
// ListAllAlarms provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
ret := _mock.Called(ctx, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListAlarms")
|
||||
panic("no return value specified for ListAllAlarms")
|
||||
}
|
||||
|
||||
var r0 alarms.AlarmsPage
|
||||
@@ -191,19 +191,19 @@ func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata)
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ListAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAlarms'
|
||||
type Repository_ListAlarms_Call struct {
|
||||
// Repository_ListAllAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAllAlarms'
|
||||
type Repository_ListAllAlarms_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListAlarms is a helper method to define mock.On call
|
||||
// ListAllAlarms is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - pm alarms.PageMetadata
|
||||
func (_e *Repository_Expecter) ListAlarms(ctx interface{}, pm interface{}) *Repository_ListAlarms_Call {
|
||||
return &Repository_ListAlarms_Call{Call: _e.mock.On("ListAlarms", ctx, pm)}
|
||||
func (_e *Repository_Expecter) ListAllAlarms(ctx interface{}, pm interface{}) *Repository_ListAllAlarms_Call {
|
||||
return &Repository_ListAllAlarms_Call{Call: _e.mock.On("ListAllAlarms", ctx, pm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAlarms_Call {
|
||||
func (_c *Repository_ListAllAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAllAlarms_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
@@ -221,12 +221,84 @@ func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarm
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAlarms_Call {
|
||||
func (_c *Repository_ListAllAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAllAlarms_Call {
|
||||
_c.Call.Return(alarmsPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAlarms_Call {
|
||||
func (_c *Repository_ListAllAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAllAlarms_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListUserAlarms provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
ret := _mock.Called(ctx, userID, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListUserAlarms")
|
||||
}
|
||||
|
||||
var r0 alarms.AlarmsPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) (alarms.AlarmsPage, error)); ok {
|
||||
return returnFunc(ctx, userID, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) alarms.AlarmsPage); ok {
|
||||
r0 = returnFunc(ctx, userID, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.AlarmsPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, alarms.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, userID, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ListUserAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListUserAlarms'
|
||||
type Repository_ListUserAlarms_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListUserAlarms is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - userID string
|
||||
// - pm alarms.PageMetadata
|
||||
func (_e *Repository_Expecter) ListUserAlarms(ctx interface{}, userID interface{}, pm interface{}) *Repository_ListUserAlarms_Call {
|
||||
return &Repository_ListUserAlarms_Call{Call: _e.mock.On("ListUserAlarms", ctx, userID, pm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ListUserAlarms_Call) Run(run func(ctx context.Context, userID string, pm alarms.PageMetadata)) *Repository_ListUserAlarms_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 alarms.PageMetadata
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(alarms.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListUserAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListUserAlarms_Call {
|
||||
_c.Call.Return(alarmsPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListUserAlarms_Call) RunAndReturn(run func(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListUserAlarms_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
)
|
||||
|
||||
const (
|
||||
OpAddAlarm = iota
|
||||
OpViewAlarm
|
||||
OpListAlarms
|
||||
OpUpdateAlarm
|
||||
OpDeleteAlarm
|
||||
)
|
||||
|
||||
func GetPermission(op permissions.Operation) (string, error) {
|
||||
if op < OpAddAlarm || op > OpDeleteAlarm {
|
||||
return "", errors.New("invalid operation")
|
||||
}
|
||||
|
||||
if op == OpUpdateAlarm || op == OpDeleteAlarm {
|
||||
return policies.AdminPermission, nil
|
||||
}
|
||||
|
||||
return policies.MembershipPermission, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package operations
|
||||
|
||||
import "github.com/absmach/supermq/pkg/permissions"
|
||||
|
||||
const EntityType = "alarm"
|
||||
|
||||
// Alarm Operations.
|
||||
const (
|
||||
OpViewAlarm permissions.Operation = iota
|
||||
OpDeleteAlarm
|
||||
OpListAlarms
|
||||
OpAssignAlarm
|
||||
OpAcknowledgeAlarm
|
||||
OpResolveAlarm
|
||||
OpUpdateAlarm
|
||||
)
|
||||
|
||||
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
|
||||
return map[permissions.Operation]permissions.OperationDetails{
|
||||
OpViewAlarm: {
|
||||
Name: "view",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpDeleteAlarm: {
|
||||
Name: "delete",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpListAlarms: {
|
||||
Name: "list",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpAssignAlarm: {
|
||||
Name: "assign",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpAcknowledgeAlarm: {
|
||||
Name: "acknowledge",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpResolveAlarm: {
|
||||
Name: "resolve",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateAlarm: {
|
||||
Name: "update",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
+46
-26
@@ -20,6 +20,10 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const alarmColumns = `alarms.id, alarms.rule_id, alarms.domain_id, alarms.channel_id, alarms.client_id, alarms.subtopic, alarms.measurement, alarms.value, alarms.unit,
|
||||
alarms.threshold, alarms.cause, alarms.status, alarms.severity, alarms.assignee_id, alarms.created_at, alarms.updated_at, alarms.updated_by, alarms.assigned_at,
|
||||
alarms.assigned_by, alarms.acknowledged_at, alarms.acknowledged_by, alarms.resolved_at, alarms.resolved_by, alarms.metadata`
|
||||
|
||||
type repository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
@@ -183,32 +187,49 @@ func (r *repository) ViewAlarm(ctx context.Context, alarmID, domainID string) (a
|
||||
return alarm, nil
|
||||
}
|
||||
|
||||
func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
func (r *repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
comQuery := fmt.Sprintf(`SELECT %s FROM alarms %s`, alarmColumns, query)
|
||||
|
||||
return r.alarmsPage(ctx, comQuery, pm)
|
||||
}
|
||||
|
||||
func (r *repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
pm.UserID = userID
|
||||
comQuery := fmt.Sprintf(`SELECT DISTINCT %s
|
||||
FROM alarms
|
||||
INNER JOIN rules_roles rr ON rr.entity_id = alarms.rule_id
|
||||
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
|
||||
%s`, alarmColumns, query)
|
||||
|
||||
return r.alarmsPage(ctx, comQuery, pm)
|
||||
}
|
||||
|
||||
func (r *repository) alarmsPage(ctx context.Context, comQuery string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
dir := api.DescDir
|
||||
if pm.Dir == api.AscDir {
|
||||
dir = api.AscDir
|
||||
}
|
||||
|
||||
orderClause := ""
|
||||
|
||||
var orderClause string
|
||||
switch pm.Order {
|
||||
case api.CreatedAtOrder:
|
||||
orderClause = fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
|
||||
case api.UpdatedAtOrder:
|
||||
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
|
||||
default:
|
||||
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit,
|
||||
threshold, cause, status, severity, assignee_id, created_at, updated_at, updated_by, assigned_at,
|
||||
assigned_by, acknowledged_at, acknowledged_by, resolved_at, resolved_by, metadata
|
||||
FROM alarms %s %s LIMIT :limit OFFSET :offset;`, query, orderClause)
|
||||
q := fmt.Sprintf(`SELECT * FROM (%s) AS sub_query %s LIMIT :limit OFFSET :offset;`, comQuery, orderClause)
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) AS total_count FROM (%s) AS sub_query;`, comQuery)
|
||||
|
||||
rows, err := r.db.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
@@ -231,8 +252,7 @@ func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (al
|
||||
items = append(items, a)
|
||||
}
|
||||
|
||||
q = fmt.Sprintf(`SELECT COUNT(*) FROM alarms %s;`, query)
|
||||
total, err := postgres.Total(ctx, r.db, q, pm)
|
||||
total, err := postgres.Total(ctx, r.db, cq, pm)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
@@ -444,49 +464,49 @@ func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
|
||||
func pageQuery(pm alarms.PageMetadata) (string, error) {
|
||||
var query []string
|
||||
if pm.DomainID != "" {
|
||||
query = append(query, "domain_id = :domain_id")
|
||||
query = append(query, "alarms.domain_id = :domain_id")
|
||||
}
|
||||
if pm.RuleID != "" {
|
||||
query = append(query, "rule_id = :rule_id")
|
||||
query = append(query, "alarms.rule_id = :rule_id")
|
||||
}
|
||||
if pm.ChannelID != "" {
|
||||
query = append(query, "channel_id = :channel_id")
|
||||
query = append(query, "alarms.channel_id = :channel_id")
|
||||
}
|
||||
if pm.Subtopic != "" {
|
||||
query = append(query, "subtopic = :subtopic")
|
||||
query = append(query, "alarms.subtopic = :subtopic")
|
||||
}
|
||||
if pm.ClientID != "" {
|
||||
query = append(query, "client_id = :client_id")
|
||||
query = append(query, "alarms.client_id = :client_id")
|
||||
}
|
||||
if pm.Measurement != "" {
|
||||
query = append(query, "measurement = :measurement")
|
||||
query = append(query, "alarms.measurement = :measurement")
|
||||
}
|
||||
if pm.Status != alarms.AllStatus {
|
||||
query = append(query, "status = :status")
|
||||
query = append(query, "alarms.status = :status")
|
||||
}
|
||||
if pm.Severity != math.MaxUint8 {
|
||||
query = append(query, "severity = :severity")
|
||||
query = append(query, "alarms.severity = :severity")
|
||||
}
|
||||
if pm.AssigneeID != "" {
|
||||
query = append(query, "assignee_id = :assignee_id")
|
||||
query = append(query, "alarms.assignee_id = :assignee_id")
|
||||
}
|
||||
if pm.UpdatedBy != "" {
|
||||
query = append(query, "updated_by = :updated_by")
|
||||
query = append(query, "alarms.updated_by = :updated_by")
|
||||
}
|
||||
if pm.ResolvedBy != "" {
|
||||
query = append(query, "resolved_by = :resolved_by")
|
||||
query = append(query, "alarms.resolved_by = :resolved_by")
|
||||
}
|
||||
if pm.AcknowledgedBy != "" {
|
||||
query = append(query, "acknowledged_by = :acknowledged_by")
|
||||
query = append(query, "alarms.acknowledged_by = :acknowledged_by")
|
||||
}
|
||||
if pm.AssignedBy != "" {
|
||||
query = append(query, "assigned_by = :assigned_by")
|
||||
query = append(query, "alarms.assigned_by = :assigned_by")
|
||||
}
|
||||
if !pm.CreatedFrom.IsZero() {
|
||||
query = append(query, "created_at >= :created_from")
|
||||
query = append(query, "alarms.created_at >= :created_from")
|
||||
}
|
||||
if !pm.CreatedTo.IsZero() {
|
||||
query = append(query, "created_at <= :created_to")
|
||||
query = append(query, "alarms.created_at <= :created_to")
|
||||
}
|
||||
|
||||
var emq string
|
||||
|
||||
@@ -403,7 +403,7 @@ func TestListAlarms(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
alarms, err := repo.ListAlarms(context.Background(), tc.pm)
|
||||
alarms, err := repo.ListAllAlarms(context.Background(), tc.pm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
|
||||
@@ -415,6 +415,184 @@ func TestListAlarms(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUserAlarms(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM rules")
|
||||
require.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
domainID := generateUUID(t)
|
||||
userID := generateUUID(t)
|
||||
otherUserID := generateUUID(t)
|
||||
adminUserID := generateUUID(t)
|
||||
|
||||
// Create 10 rules and 10 alarms referencing them.
|
||||
// Assign userID to the first 6 rules via role membership.
|
||||
var ruleIDs []string
|
||||
var createdAlarms []alarms.Alarm
|
||||
for i := range 10 {
|
||||
ruleID := generateUUID(t)
|
||||
_, err := db.Exec(`INSERT INTO rules (id, name, domain_id, status, logic_type, logic_value) VALUES ($1, $2, $3, 0, 0, '')`,
|
||||
ruleID, fmt.Sprintf("rule-%d", i), domainID)
|
||||
require.Nil(t, err, fmt.Sprintf("insert rule unexpected error: %s", err))
|
||||
ruleIDs = append(ruleIDs, ruleID)
|
||||
|
||||
alarm := alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: ruleID,
|
||||
DomainID: domainID,
|
||||
ChannelID: generateUUID(t),
|
||||
ClientID: generateUUID(t),
|
||||
Measurement: namegen.Generate(),
|
||||
Value: namegen.Generate(),
|
||||
Unit: namegen.Generate(),
|
||||
Threshold: namegen.Generate(),
|
||||
Cause: namegen.Generate(),
|
||||
Status: 0,
|
||||
AssigneeID: generateUUID(t),
|
||||
CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute),
|
||||
}
|
||||
alarm, err = repo.CreateAlarm(context.Background(), alarm)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
createdAlarms = append(createdAlarms, alarm)
|
||||
}
|
||||
|
||||
// Assign userID to the first 6 rules via rules_roles + rules_role_members.
|
||||
userRoleIDs := make([]string, 6)
|
||||
for i := range 6 {
|
||||
roleID := generateUUID(t)
|
||||
userRoleIDs[i] = roleID
|
||||
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
|
||||
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, ruleIDs[i])
|
||||
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
for i := range 10 {
|
||||
var roleID string
|
||||
if i < 6 {
|
||||
roleID = userRoleIDs[i]
|
||||
} else {
|
||||
roleID = generateUUID(t)
|
||||
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
|
||||
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
|
||||
}
|
||||
_, err := db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, adminUserID, ruleIDs[i])
|
||||
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
_ = createdAlarms
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
userID string
|
||||
pm alarms.PageMetadata
|
||||
count int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list user alarms returns only accessible alarms",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 6,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms with limit",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 3,
|
||||
},
|
||||
count: 3,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms with offset",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 4,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 2,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms with domain filter",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
DomainID: domainID,
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 6,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms with non-existing domain returns 0",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
DomainID: generateUUID(t),
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list alarms for user with no role assignments returns 0",
|
||||
userID: otherUserID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list alarms for admin user with role on all rules returns all alarms",
|
||||
userID: adminUserID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 10,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms ordered by created_at ascending",
|
||||
userID: userID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Order: "created_at",
|
||||
Dir: "asc",
|
||||
},
|
||||
count: 6,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
page, err := repo.ListUserAlarms(context.Background(), tc.userID, tc.pm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
return
|
||||
}
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
assert.Equal(t, tc.count, len(page.Alarms), fmt.Sprintf("%s: expected %d alarms, got %d", tc.desc, tc.count, len(page.Alarms)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAlarm(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
|
||||
+15
-3
@@ -4,13 +4,16 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
rpostgres "github.com/absmach/magistrala/re/postgres"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
// Migration of Users service.
|
||||
func Migration() *migrate.MemoryMigrationSource {
|
||||
return &migrate.MemoryMigrationSource{
|
||||
// Migration of Alarms service.
|
||||
func Migration() (*migrate.MemoryMigrationSource, error) {
|
||||
alarmsMigration := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "alarms_01",
|
||||
@@ -50,4 +53,13 @@ func Migration() *migrate.MemoryMigrationSource {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulesMigration, err := rpostgres.Migration()
|
||||
if err != nil {
|
||||
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
|
||||
}
|
||||
|
||||
alarmsMigration.Migrations = append(alarmsMigration.Migrations, rulesMigration.Migrations...)
|
||||
|
||||
return alarmsMigration, nil
|
||||
}
|
||||
|
||||
@@ -75,7 +75,11 @@ func TestMain(m *testing.M) {
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
if db, err = postgres.Setup(dbConfig, *apostgres.Migration()); err != nil {
|
||||
migration, err := apostgres.Migration()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get migration: %s", err)
|
||||
}
|
||||
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
|
||||
log.Fatalf("Could not setup test DB connection: %s", err)
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -43,6 +43,7 @@ func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) error {
|
||||
if _, err = s.repo.CreateAlarm(ctx, alarm); err != nil && err != repoerr.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,7 +52,10 @@ func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID
|
||||
}
|
||||
|
||||
func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) {
|
||||
return s.repo.ListAlarms(ctx, pm)
|
||||
if session.SuperAdmin {
|
||||
return s.repo.ListAllAlarms(ctx, pm)
|
||||
}
|
||||
return s.repo.ListUserAlarms(ctx, session.UserID, pm)
|
||||
}
|
||||
|
||||
func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error {
|
||||
|
||||
+11
-25
@@ -6,7 +6,6 @@ package alarms_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -22,9 +21,13 @@ import (
|
||||
|
||||
var idp = uuid.New()
|
||||
|
||||
func newService(t *testing.T, repo *mocks.Repository) alarms.Service {
|
||||
return alarms.NewService(idp, repo)
|
||||
}
|
||||
|
||||
func TestCreateAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := alarms.NewService(idp, repo)
|
||||
svc := newService(t, repo)
|
||||
ts := time.Now()
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -69,33 +72,16 @@ func TestCreateAlarm(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("CreateAlarm", context.Background(), mock.Anything).Return(tc.alarm, tc.err)
|
||||
repoCall1 := repo.On("ListAlarms", context.Background(), alarms.PageMetadata{
|
||||
Offset: 0, Limit: 1,
|
||||
DomainID: tc.alarm.DomainID,
|
||||
ChannelID: tc.alarm.ChannelID,
|
||||
ClientID: tc.alarm.ClientID,
|
||||
Subtopic: tc.alarm.Subtopic,
|
||||
Measurement: tc.alarm.Measurement,
|
||||
RuleID: tc.alarm.RuleID,
|
||||
Status: alarms.AllStatus,
|
||||
Severity: math.MaxUint8,
|
||||
CreatedTo: tc.alarm.CreatedAt,
|
||||
}).Return(alarms.AlarmsPage{}, tc.err)
|
||||
err := svc.CreateAlarm(context.Background(), tc.alarm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
|
||||
return
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := alarms.NewService(idp, repo)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -134,7 +120,7 @@ func TestViewAlarm(t *testing.T) {
|
||||
|
||||
func TestUpdateAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := alarms.NewService(idp, repo)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -192,7 +178,7 @@ func TestUpdateAlarm(t *testing.T) {
|
||||
|
||||
func TestListAlarms(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := alarms.NewService(idp, repo)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -219,7 +205,7 @@ func TestListAlarms(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
s := authn.Session{DomainID: tc.pm.DomainID}
|
||||
repoCall := repo.On("ListAlarms", context.Background(), tc.pm).Return(tc.page, tc.err)
|
||||
repoCall := repo.On("ListUserAlarms", context.Background(), s.UserID, tc.pm).Return(tc.page, tc.err)
|
||||
_, err := svc.ListAlarms(context.Background(), s, tc.pm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
@@ -233,7 +219,7 @@ func TestListAlarms(t *testing.T) {
|
||||
|
||||
func TestDeleteAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := alarms.NewService(idp, repo)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8050
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9013
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9014
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9003
|
||||
|
||||
@@ -6,7 +6,7 @@ info:
|
||||
title: Magistrala Reports Service API
|
||||
description: |
|
||||
HTTP API for managing reports service.
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
servers:
|
||||
- url: http://localhost:9017
|
||||
tags:
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.5
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9008
|
||||
|
||||
@@ -127,7 +127,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID
|
||||
Permission: policies.AdminPermission,
|
||||
ObjectType: policies.PlatformType,
|
||||
Object: policies.SuperMQObject,
|
||||
}); err != nil {
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -143,7 +143,7 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjTy
|
||||
ObjectType: objType,
|
||||
Object: obj,
|
||||
}
|
||||
if err := am.authz.Authorize(ctx, req); err != nil {
|
||||
if err := am.authz.Authorize(ctx, req, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
+76
-7
@@ -15,17 +15,23 @@ import (
|
||||
"github.com/absmach/magistrala/alarms/brokers"
|
||||
"github.com/absmach/magistrala/alarms/consumer"
|
||||
"github.com/absmach/magistrala/alarms/middleware"
|
||||
"github.com/absmach/magistrala/alarms/operations"
|
||||
alarmsRepo "github.com/absmach/magistrala/alarms/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
rconsumer "github.com/absmach/magistrala/pkg/re/events/consumer"
|
||||
rpostgres "github.com/absmach/magistrala/re/postgres"
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
|
||||
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
|
||||
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/jaeger"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
@@ -42,14 +48,18 @@ const (
|
||||
defDB = "alarms"
|
||||
defSvcHTTPPort = "8050"
|
||||
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
|
||||
alarmEntity = "alarm"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
ESConsumerName string `env:"MG_ALARMS_EVENT_CONSUMER" envDefault:"alarms"`
|
||||
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -87,7 +97,14 @@ func main() {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
db, err := postgres.Setup(dbConfig, *alarmsRepo.Migration())
|
||||
migrations, err := alarmsRepo.Migration()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load migrations: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
db, err := postgres.Setup(dbConfig, *migrations)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -138,11 +155,63 @@ func main() {
|
||||
|
||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure())
|
||||
|
||||
ddatabase := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
drepo := dpostgres.NewRepository(ddatabase)
|
||||
|
||||
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
rdatabase := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
rrepo := rpostgres.NewRepository(rdatabase)
|
||||
|
||||
if err := rconsumer.RulesEventsSubscribe(ctx, rrepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to subscribe to rules events: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
idp := uuid.New()
|
||||
|
||||
svc := alarms.NewService(idp, repo)
|
||||
|
||||
svc = middleware.NewAuthorizationMiddleware(svc, authz)
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to parse permissions file: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
alarmOps, _, err := permConfig.GetEntityPermissions(alarmEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get alarm permissions: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
permissions.EntitiesPermission{
|
||||
operations.EntityType: alarmOps,
|
||||
},
|
||||
permissions.EntitiesOperationDetails[permissions.Operation]{
|
||||
operations.EntityType: operations.OperationDetails(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create entity operations: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
svc, err = middleware.NewAuthorizationMiddleware(svc, authz, entitiesOps)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create authorization middleware: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
svc = middleware.NewLoggingMiddleware(logger, svc)
|
||||
counter, latency := prometheus.MakeMetrics("alarms", "api")
|
||||
svc = middleware.NewMetricsMiddleware(counter, latency, svc)
|
||||
|
||||
+30
-6
@@ -17,11 +17,15 @@ import (
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/absmach/magistrala/provision"
|
||||
httpapi "github.com/absmach/magistrala/provision/api"
|
||||
"github.com/absmach/magistrala/provision/middleware"
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/supermq/channels"
|
||||
"github.com/absmach/supermq/clients"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
@@ -30,8 +34,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "provision"
|
||||
contentType = "application/json"
|
||||
svcName = "provision"
|
||||
contentType = "application/json"
|
||||
envPrefixAuth = "SMQ_AUTH_GRPC_"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -65,6 +70,24 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
grpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
authn, authnClient, err := authnsvc.NewAuthentication(ctx, grpcCfg)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer authnClient.Close()
|
||||
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
am := smqauthn.NewAuthNMiddleware(authn)
|
||||
|
||||
if cfgFromFile, err := loadConfigFromFile(cfg.File); err != nil {
|
||||
logger.Warn(fmt.Sprintf("Continue with settings from env, failed to load from: %s: %s", cfg.File, err))
|
||||
} else {
|
||||
@@ -76,9 +99,10 @@ func main() {
|
||||
|
||||
SDKCfg := mgsdk.Config{
|
||||
UsersURL: cfg.Server.UsersURL,
|
||||
ChannelsURL: cfg.Server.ChannelsURL,
|
||||
ClientsURL: cfg.Server.ClientsURL,
|
||||
BootstrapURL: cfg.Server.MgBSURL,
|
||||
CertsURL: cfg.Server.MgCertsURL,
|
||||
CertsURL: cfg.Server.CertsURL,
|
||||
MsgContentType: contentType,
|
||||
TLSVerification: cfg.Server.TLS,
|
||||
}
|
||||
@@ -91,10 +115,10 @@ func main() {
|
||||
cSdk := csdk.NewSDK(csdkConf)
|
||||
|
||||
svc := provision.New(cfg, mgSdk, cSdk, logger)
|
||||
svc = httpapi.NewLoggingMiddleware(svc, logger)
|
||||
svc = middleware.NewLogging(svc, logger)
|
||||
|
||||
httpServerConfig := server.Config{Host: "", Port: cfg.Server.HTTPPort, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
|
||||
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, logger, cfg.InstanceID), logger)
|
||||
httpServerConfig := server.Config{Host: "", Port: cfg.Server.Port, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
|
||||
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, logger, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, supermq.Version, logger, cancel)
|
||||
|
||||
+124
-15
@@ -20,33 +20,47 @@ import (
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/magistrala/re"
|
||||
httpapi "github.com/absmach/magistrala/re/api"
|
||||
"github.com/absmach/magistrala/re/events"
|
||||
"github.com/absmach/magistrala/re/middleware"
|
||||
"github.com/absmach/magistrala/re/operations"
|
||||
repg "github.com/absmach/magistrala/re/postgres"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
"github.com/absmach/supermq"
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
mgauthz "github.com/absmach/supermq/pkg/authz"
|
||||
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
|
||||
"github.com/absmach/supermq/pkg/callout"
|
||||
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
|
||||
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
smqbrokers "github.com/absmach/supermq/pkg/messaging/brokers"
|
||||
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/policies/spicedb"
|
||||
pgclient "github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/authzed/authzed-go/v1"
|
||||
"github.com/authzed/grpcutil"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,15 +81,21 @@ const (
|
||||
const channBuffer = 256
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
|
||||
InstanceID string `env:"MG_RE_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
|
||||
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
|
||||
InstanceID string `env:"MG_RE_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
ESConsumerName string `env:"MG_RE_EVENT_CONSUMER" envDefault:"rules_engine"`
|
||||
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
|
||||
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
|
||||
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
|
||||
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
|
||||
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
|
||||
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -126,7 +146,14 @@ func main() {
|
||||
|
||||
return
|
||||
}
|
||||
db, err := pgclient.Setup(dbConfig, *repg.Migration())
|
||||
migration, err := repg.Migration()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
db, err := pgclient.Setup(dbConfig, *migration)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -238,6 +265,16 @@ func main() {
|
||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
|
||||
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
drepo := dpostgres.NewRepository(ddatabase)
|
||||
|
||||
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
regrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(®rpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err))
|
||||
@@ -255,7 +292,7 @@ func main() {
|
||||
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
|
||||
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
|
||||
|
||||
svc, err := newService(ctx, database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient, callout, cfg)
|
||||
svc, err := newService(ctx, cfg, database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient, callout, tracer)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
@@ -307,7 +344,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, callout callout.Callout, cfg config) (re.Service, error) {
|
||||
func newService(ctx context.Context, cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, callout callout.Callout, tracer trace.Tracer) (re.Service, error) {
|
||||
repo := repg.NewRepository(db)
|
||||
idp := uuid.New()
|
||||
|
||||
@@ -316,21 +353,93 @@ func newService(ctx context.Context, db pgclient.Database, runInfo chan pkglog.R
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
|
||||
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
|
||||
|
||||
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
|
||||
}
|
||||
|
||||
csvc, err := re.NewService(repo, runInfo, policyService, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient, availableActions, builtInRoles)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create RE service: %w", err)
|
||||
}
|
||||
|
||||
csvc, err = events.NewEventStoreMiddleware(ctx, csvc, cfg.ESURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init re event store middleware: %w", err)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
|
||||
}
|
||||
|
||||
ruleOps, ruleRoleOps, err := permConfig.GetEntityPermissions(operations.EntityType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rule permissions: %w", err)
|
||||
}
|
||||
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
permissions.EntitiesPermission{
|
||||
operations.EntityType: ruleOps,
|
||||
},
|
||||
permissions.EntitiesOperationDetails[permissions.Operation]{
|
||||
operations.EntityType: operations.OperationDetails(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create entities operations: %w", err)
|
||||
}
|
||||
|
||||
roleOps, err := permissions.NewOperations(roles.Operations(), ruleRoleOps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create role operations: %w", err)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc, err = middleware.NewCallout(csvc, callout)
|
||||
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc = middleware.LoggingMiddleware(csvc, logger)
|
||||
counter, latency := prometheus.MakeMetrics("re", "api")
|
||||
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
|
||||
csvc = middleware.NewTracingMiddleware(tracer, csvc)
|
||||
|
||||
return csvc, nil
|
||||
}
|
||||
|
||||
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
|
||||
client, err := authzed.NewClientWithExperimentalAPIs(
|
||||
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps := spicedb.NewPolicyService(client, logger)
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
|
||||
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, operations.EntityType)
|
||||
if err != nil {
|
||||
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
|
||||
}
|
||||
|
||||
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
|
||||
re.BuiltInRoleAdmin: availableActions,
|
||||
}
|
||||
|
||||
return availableActions, builtInRoles, err
|
||||
}
|
||||
|
||||
+136
-6
@@ -19,40 +19,57 @@ import (
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
httpapi "github.com/absmach/magistrala/reports/api"
|
||||
"github.com/absmach/magistrala/reports/middleware"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
repg "github.com/absmach/magistrala/reports/postgres"
|
||||
"github.com/absmach/supermq"
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
mgauthz "github.com/absmach/supermq/pkg/authz"
|
||||
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
|
||||
"github.com/absmach/supermq/pkg/callout"
|
||||
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
|
||||
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/policies/spicedb"
|
||||
pgclient "github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/authzed/authzed-go/v1"
|
||||
"github.com/authzed/grpcutil"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "reports"
|
||||
envPrefixDB = "MG_REPORTS_DB_"
|
||||
envPrefixHTTP = "MG_REPORTS_HTTP_"
|
||||
envPrefixCallout = "MG_REPORTS_CALLOUT_"
|
||||
envPrefixAuth = "SMQ_AUTH_GRPC_"
|
||||
defDB = "repo"
|
||||
defSvcHTTPPort = "9017"
|
||||
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
|
||||
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
|
||||
templatePath = "template/reports_default_template.html"
|
||||
reportEntity = "report"
|
||||
)
|
||||
|
||||
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
|
||||
@@ -67,10 +84,16 @@ type config struct {
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
ESConsumerName string `env:"MG_REPORTS_EVENT_CONSUMER" envDefault:"reports"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
DefaultTemplatePath string `env:"MG_REPORTS_DEFAULT_TEMPLATE" envDefault:""`
|
||||
ConverterURL string `env:"MG_PDF_CONVERTER_URL" envDefault:"http://localhost:4000/pdf"`
|
||||
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
|
||||
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
|
||||
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
|
||||
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
|
||||
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -131,6 +154,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
callCfg := callout.Config{}
|
||||
if err := env.ParseWithOptions(&callCfg, env.Options{Prefix: envPrefixCallout}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to parse callout config : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
dbConfig := pgclient.Config{Name: defDB}
|
||||
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
@@ -139,7 +169,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
db, err := pgclient.Setup(dbConfig, *repg.Migration())
|
||||
migration, err := repg.Migration()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
db, err := pgclient.Setup(dbConfig, *migration)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -170,6 +208,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
callout, err := callout.New(callCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
grpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
|
||||
@@ -211,6 +256,15 @@ func main() {
|
||||
defer authzClient.Close()
|
||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
|
||||
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
drepo := dpostgres.NewRepository(ddatabase)
|
||||
|
||||
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
regrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(®rpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
|
||||
@@ -231,7 +285,7 @@ func main() {
|
||||
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
svc, err := newService(database, runInfo, authz, ec, logger, readersClient, template, cfg.ConverterURL)
|
||||
svc, err := newService(cfg, database, runInfo, authz, ec, logger, readersClient, template, callout, tracer)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
@@ -271,21 +325,97 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, converterURL string) (reports.Service, error) {
|
||||
func newService(cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, callout callout.Callout, tracer trace.Tracer) (reports.Service, error) {
|
||||
repo := repg.NewRepository(db)
|
||||
idp := uuid.New()
|
||||
|
||||
emailerClient, err := emailer.New(&ec)
|
||||
emailClient, err := emailer.New(&ec)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
csvc := reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailerClient, readersClient, template, converterURL)
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
|
||||
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
|
||||
|
||||
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
|
||||
}
|
||||
|
||||
csvc, err := reports.NewService(repo, runInfo, policyService, idp, ticker.NewTicker(time.Second*30), emailClient, readersClient, template, cfg.ConverterURL, availableActions, builtInRoles)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create reports service: %w", err)
|
||||
}
|
||||
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
|
||||
}
|
||||
|
||||
reportOps, reportRoleOps, err := permConfig.GetEntityPermissions(reportEntity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get report permissions: %w", err)
|
||||
}
|
||||
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
permissions.EntitiesPermission{
|
||||
operations.EntityType: reportOps,
|
||||
},
|
||||
permissions.EntitiesOperationDetails[permissions.Operation]{
|
||||
operations.EntityType: operations.OperationDetails(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create entities operations: %w", err)
|
||||
}
|
||||
|
||||
roleOps, err := permissions.NewOperations(roles.Operations(), reportRoleOps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create role operations: %w", err)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc = middleware.LoggingMiddleware(csvc, logger)
|
||||
counter, latency := prometheus.MakeMetrics("reports", "api")
|
||||
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
|
||||
csvc = middleware.NewTracingMiddleware(tracer, csvc)
|
||||
|
||||
return csvc, nil
|
||||
}
|
||||
|
||||
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
|
||||
client, err := authzed.NewClientWithExperimentalAPIs(
|
||||
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps := spicedb.NewPolicyService(client, logger)
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
|
||||
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, reportEntity)
|
||||
if err != nil {
|
||||
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
|
||||
}
|
||||
|
||||
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
|
||||
reports.BuiltInRoleAdmin: availableActions,
|
||||
}
|
||||
|
||||
return availableActions, builtInRoles, err
|
||||
}
|
||||
|
||||
+30
-6
@@ -52,6 +52,8 @@ SMQ_AUTH_GRPC_TIMEOUT=300s
|
||||
SMQ_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
|
||||
SMQ_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key}
|
||||
SMQ_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
|
||||
SMQ_AUTH_ACCESS_TOKEN_DURATION=1h
|
||||
SMQ_AUTH_REFRESH_TOKEN_DURATION=24h
|
||||
|
||||
#### Clients Client Config
|
||||
SMQ_CLIENTS_URL=http://clients:9006
|
||||
@@ -85,11 +87,14 @@ SMQ_SPICEDB_DB_PORT=5432
|
||||
|
||||
### SpiceDB config
|
||||
SMQ_SPICEDB_PRE_SHARED_KEY="12345678"
|
||||
SMQ_SPICEDB_SCHEMA_FILE="/schema.zed"
|
||||
SMQ_SPICEDB_SCHEMA_FILE="/schemas/combined-schema.zed"
|
||||
SMQ_SPICEDB_HOST=supermq-spicedb
|
||||
SMQ_SPICEDB_PORT=50051
|
||||
SMQ_SPICEDB_DATASTORE_ENGINE=postgres
|
||||
|
||||
### Permissions
|
||||
SMQ_PERMISSIONS_FILE=/schemas/permission.yaml
|
||||
|
||||
### UI
|
||||
SMQ_UI_PATH_PREFIX=/ui
|
||||
|
||||
@@ -145,6 +150,7 @@ MG_ALARMS_DB_SSL_CERT=
|
||||
MG_ALARMS_DB_SSL_KEY=
|
||||
MG_ALARMS_DB_SSL_ROOT_CERT=
|
||||
MG_ALARMS_INSTANCE_ID=
|
||||
MG_ALARMS_EVENT_CONSUMER=alarms
|
||||
|
||||
### REPORTS
|
||||
MG_REPORTS_LOG_LEVEL=debug
|
||||
@@ -286,13 +292,14 @@ MG_PROVISION_HTTP_PORT=9016
|
||||
MG_PROVISION_ENV_CLIENTS_TLS=false
|
||||
MG_PROVISION_SERVER_CERT=
|
||||
MG_PROVISION_SERVER_KEY=
|
||||
MG_PROVISION_USERS_LOCATION=http://users:9002
|
||||
MG_PROVISION_CLIENTS_LOCATION=http://clients:9006
|
||||
MG_PROVISION_USERS_URL=http://users:9002
|
||||
MG_PROVISION_CHANNELS_URL=http://channels:9005
|
||||
MG_PROVISION_CLIENTS_URL=http://clients:9006
|
||||
MG_PROVISION_CERTS_URL=http://certs:9019
|
||||
MG_PROVISION_USER=
|
||||
MG_PROVISION_USERNAME=
|
||||
MG_PROVISION_PASS=
|
||||
MG_PROVISION_API_KEY=
|
||||
MG_PROVISION_CERTS_SVC_URL=http://certs:9019
|
||||
MG_PROVISION_X509_PROVISIONING=false
|
||||
MG_PROVISION_BS_SVC_URL=http://bootstrap:9013
|
||||
MG_PROVISION_BS_CONFIG_PROVISIONING=true
|
||||
@@ -407,10 +414,12 @@ MG_BACKEND_OBJECT_STORAGE_REGION=fra1
|
||||
MG_BACKEND_OBJECT_STORAGE_BUCKET=mg-ui-images
|
||||
MG_BACKEND_OBJECT_STORAGE_ENDPOINT=http://seaweedfs-s3:8333
|
||||
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE=true
|
||||
MG_BACKEND_OBJECT_STORAGE_REWRITE_URL=localhost
|
||||
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT=http://localhost:8333
|
||||
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY=localKey
|
||||
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY=localSecret
|
||||
MG_BACKEND_OBJECT_STORAGE_TTL=1h
|
||||
MG_BACKEND_OBJECT_STORAGE_WRITE_TTL=1m
|
||||
MG_BACKEND_OBJECT_STORAGE_READ_TTL=15m
|
||||
MG_BACKEND_OBJECT_STORAGE_TTL=15m
|
||||
|
||||
#### Auth GRPC Client Config
|
||||
MG_AUTH_GRPC_URL=auth:7001
|
||||
@@ -466,6 +475,21 @@ MG_UI_BASEURL=http://localhost:3000
|
||||
MG_SUPPORT_EMAIL=
|
||||
MG_SUPPORT_EMAIL_PASS=
|
||||
|
||||
## SMTP Variables
|
||||
MG_UI_SMTP_HOST=host.docker.internal
|
||||
MG_UI_SMTP_PORT=2525
|
||||
MG_UI_SMTP_SECURE=
|
||||
MG_UI_SUPPORT_FROM=from@example.com
|
||||
|
||||
# Message cli variables
|
||||
MG_UI_CLI_MQTT_HOST=localhost
|
||||
MG_UI_CLI_MQTT_PORT=8883
|
||||
MG_UI_CLI_WS_URL=ws://localhost:8186
|
||||
MG_UI_CLI_COAP_HOST=0.0.0.0
|
||||
MG_UI_CLI_COAP_PORT=5684
|
||||
MG_UI_CLI_HTTP_URL=http://localhost:8008
|
||||
MG_UI_CLI_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# Docker image tag
|
||||
MG_RELEASE_TAG=latest
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.26rc2-alpine3.22 AS builder
|
||||
FROM golang:1.26-alpine3.22 AS builder
|
||||
ARG SVC
|
||||
ARG GOARCH
|
||||
ARG GOARM
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
networks:
|
||||
magistrala-base-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
magistrala-bootstrap-db-volume:
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
type = "plain"
|
||||
workers = 10
|
||||
|
||||
[[things]]
|
||||
name = "thing"
|
||||
[[clients]]
|
||||
name = "client"
|
||||
|
||||
[things.metadata]
|
||||
[clients.metadata]
|
||||
external_id = "xxxxxx"
|
||||
|
||||
[[channels]]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
networks:
|
||||
magistrala-base-net:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
provision:
|
||||
@@ -25,13 +26,14 @@ services:
|
||||
MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS}
|
||||
MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT}
|
||||
MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY}
|
||||
MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION}
|
||||
MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION}
|
||||
MG_PROVISION_USERS_URL: ${MG_PROVISION_USERS_URL}
|
||||
MG_PROVISION_CHANNELS_URL: ${MG_PROVISION_CHANNELS_URL}
|
||||
MG_PROVISION_CLIENTS_URL: ${MG_PROVISION_CLIENTS_URL}
|
||||
MG_PROVISION_USER: ${MG_PROVISION_USER}
|
||||
MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME}
|
||||
MG_PROVISION_PASS: ${MG_PROVISION_PASS}
|
||||
MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY}
|
||||
MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL}
|
||||
MG_PROVISION_CERTS_URL: ${MG_PROVISION_CERTS_URL}
|
||||
MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING}
|
||||
MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL}
|
||||
MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING}
|
||||
@@ -40,6 +42,12 @@ services:
|
||||
MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID}
|
||||
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
|
||||
MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID}
|
||||
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
|
||||
SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT}
|
||||
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
|
||||
SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
|
||||
SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
|
||||
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
|
||||
volumes:
|
||||
- ./configs:/configs
|
||||
- ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"identities": [
|
||||
{
|
||||
"name": "magistrala",
|
||||
"credentials": [
|
||||
{
|
||||
"accessKey": "localKey",
|
||||
"secretKey": "localSecret"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
"Admin",
|
||||
"Read",
|
||||
"Write"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+82
-40
@@ -46,7 +46,6 @@ services:
|
||||
MG_READER_URL: ${MG_READER_URL}
|
||||
MG_BACKEND_URL: ${MG_UI_BACKEND_URL}
|
||||
MG_JOURNAL_URL: ${MG_JOURNAL_URL}
|
||||
MG_BILLING_URL: ${MG_BILLING_URL}
|
||||
MG_ALARMS_URL: ${MG_ALARMS_URL}
|
||||
MG_RE_URL: ${MG_RE_URL}
|
||||
MG_REPORTS_URL: ${MG_REPORTS_URL}
|
||||
@@ -66,6 +65,20 @@ services:
|
||||
MG_UI_DOCKER_ACCEPT_EULA: ${MG_UI_DOCKER_ACCEPT_EULA}
|
||||
MG_SUPPORT_EMAIL: ${MG_SUPPORT_EMAIL}
|
||||
MG_SUPPORT_EMAIL_PASS: ${MG_SUPPORT_EMAIL_PASS}
|
||||
MG_UI_CLI_MQTT_HOST: ${MG_UI_CLI_MQTT_HOST}
|
||||
MG_UI_CLI_WS_URL: ${MG_UI_CLI_WS_URL}
|
||||
MG_UI_CLI_COAP_HOST: ${MG_UI_CLI_COAP_HOST}
|
||||
MG_UI_CLI_COAP_PORT: ${MG_UI_CLI_COAP_PORT}
|
||||
MG_UI_CLI_HTTP_URL: ${MG_UI_CLI_HTTP_URL}
|
||||
MG_UI_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
|
||||
MG_ACCESS_TOKEN_EXPIRY: ${SMQ_AUTH_ACCESS_TOKEN_DURATION}
|
||||
MG_REFRESH_TOKEN_EXPIRY: ${SMQ_AUTH_REFRESH_TOKEN_DURATION}
|
||||
MG_UI_SMTP_HOST: ${MG_UI_SMTP_HOST}
|
||||
MG_UI_SMTP_PORT: ${MG_UI_SMTP_PORT}
|
||||
MG_UI_SMTP_SECURE: ${MG_UI_SMTP_SECURE}
|
||||
MG_UI_SUPPORT_FROM: ${MG_UI_SUPPORT_FROM}
|
||||
|
||||
|
||||
|
||||
ui-backend:
|
||||
image: ghcr.io/absmach/magistrala/ui-backend:latest
|
||||
@@ -114,10 +127,11 @@ services:
|
||||
MG_BACKEND_OBJECT_STORAGE_BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
|
||||
MG_BACKEND_OBJECT_STORAGE_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_ENDPOINT}
|
||||
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE: ${MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE}
|
||||
MG_BACKEND_OBJECT_STORAGE_REWRITE_URL: ${MG_BACKEND_OBJECT_STORAGE_REWRITE_URL}
|
||||
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT}
|
||||
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
|
||||
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
|
||||
MG_BACKEND_OBJECT_STORAGE_TTL: ${MG_BACKEND_OBJECT_STORAGE_TTL}
|
||||
MG_BACKEND_OBJECT_STORAGE_READ_TTL: ${MG_BACKEND_OBJECT_STORAGE_READ_TTL}
|
||||
depends_on:
|
||||
ui-backend-db:
|
||||
condition: service_healthy
|
||||
@@ -127,49 +141,49 @@ services:
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Reader gRPC client certificates
|
||||
- type: bind
|
||||
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /readers-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:+.key}
|
||||
target: /readers-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /readers-grpc-server-ca${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /readers-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -195,9 +209,9 @@ services:
|
||||
timeout: 3s
|
||||
retries: 60
|
||||
seaweedfs-s3:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
image: chrislusf/seaweedfs:4.16
|
||||
container_name: magistrala-seaweedfs-s3
|
||||
command: server -s3 -dir=/data
|
||||
command: server -s3 -s3.config=/etc/seaweedfs/s3.json -dir=/data
|
||||
ports:
|
||||
- "8333:8333" # S3 endpoint
|
||||
- "9333:9333" # master UI
|
||||
@@ -205,32 +219,39 @@ services:
|
||||
- "8888:8888" # filer UI
|
||||
volumes:
|
||||
- ./data/seaweedfs:/data
|
||||
- ./configs/seaweedfs-s3.json:/etc/seaweedfs/s3.json:ro
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
|
||||
seaweedfs-init:
|
||||
image: curlimages/curl
|
||||
image: amazon/aws-cli
|
||||
container_name: magistrala-seaweedfs-init
|
||||
entrypoint: /bin/sh
|
||||
depends_on:
|
||||
- seaweedfs-s3
|
||||
command: >
|
||||
/bin/sh -c "
|
||||
echo \"Creating bucket $${BUCKET}, wait for 25s...\";
|
||||
sleep 25;
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' -f -X PUT http://seaweedfs-s3:8333/$${BUCKET});
|
||||
if [ $${HTTP_CODE} = '200' ] || [ $${HTTP_CODE} = '201' ]; then
|
||||
echo \"[INIT] Bucket $${BUCKET} created successfully!\";
|
||||
curl -s http://seaweedfs-s3:8333/ | grep -o '<Name>[^<]*</Name>';
|
||||
exit 0;
|
||||
else
|
||||
echo \"[INIT] Failed to create bucket $${BUCKET}! HTTP code $${HTTP_CODE}.\" >&2;
|
||||
exit 1;
|
||||
fi
|
||||
"
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
echo "[INIT] Waiting 20s for SeaweedFS S3 to be ready...";
|
||||
sleep 20;
|
||||
OUT=$(aws --endpoint-url http://seaweedfs-s3:8333 s3api create-bucket --bucket $${BUCKET} 2>&1);
|
||||
EXIT=$$?;
|
||||
if [ $$EXIT -eq 0 ]; then
|
||||
echo "[INIT] Bucket $${BUCKET} created successfully.";
|
||||
elif echo "$$OUT" | grep -q 'BucketAlreadyOwnedByYou\|BucketAlreadyExists'; then
|
||||
echo "[INIT] Bucket $${BUCKET} already exists, skipping.";
|
||||
else
|
||||
echo "[INIT] Failed to create bucket $${BUCKET}: $$OUT" >&2;
|
||||
exit 1;
|
||||
fi
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
environment:
|
||||
BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
|
||||
AWS_ACCESS_KEY_ID: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
|
||||
AWS_SECRET_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
|
||||
AWS_DEFAULT_REGION: ${MG_BACKEND_OBJECT_STORAGE_REGION}
|
||||
AWS_EC2_METADATA_DISABLED: "true"
|
||||
|
||||
re-db:
|
||||
image: docker.io/postgres:18.0-alpine3.22
|
||||
@@ -253,6 +274,7 @@ services:
|
||||
container_name: magistrala-re
|
||||
depends_on:
|
||||
- re-db
|
||||
- spicedb-migrate
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_RE_LOG_LEVEL: ${MG_RE_LOG_LEVEL}
|
||||
@@ -290,6 +312,8 @@ services:
|
||||
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
|
||||
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
|
||||
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
|
||||
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
|
||||
MG_RE_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
|
||||
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
|
||||
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
|
||||
@@ -314,21 +338,23 @@ services:
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
|
||||
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
- ./templates/${MG_RE_EMAIL_TEMPLATE}:/email.tmpl
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -353,6 +379,7 @@ services:
|
||||
container_name: magistrala-alarms
|
||||
depends_on:
|
||||
- alarms-db
|
||||
- spicedb-migrate
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_ALARMS_LOG_LEVEL: ${MG_ALARMS_LOG_LEVEL}
|
||||
@@ -370,6 +397,7 @@ services:
|
||||
MG_ALARMS_DB_SSL_KEY: ${MG_ALARMS_DB_SSL_KEY}
|
||||
MG_ALARMS_DB_SSL_ROOT_CERT: ${MG_ALARMS_DB_SSL_ROOT_CERT}
|
||||
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
|
||||
SMQ_ES_URL: ${SMQ_ES_URL}
|
||||
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
|
||||
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
|
||||
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
|
||||
@@ -382,42 +410,50 @@ services:
|
||||
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
|
||||
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
|
||||
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
|
||||
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
|
||||
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
|
||||
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
|
||||
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
|
||||
MG_ALARMS_INSTANCE_ID: ${MG_ALARMS_INSTANCE_ID}
|
||||
MG_ALARMS_EVENT_CONSUMER: ${MG_ALARMS_EVENT_CONSUMER}
|
||||
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
|
||||
ports:
|
||||
- ${MG_ALARMS_HTTP_PORT}:${MG_ALARMS_HTTP_PORT}
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
|
||||
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -442,6 +478,7 @@ services:
|
||||
container_name: magistrala-reports
|
||||
depends_on:
|
||||
- reports-db
|
||||
- spicedb-migrate
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_REPORTS_LOG_LEVEL: ${MG_REPORTS_LOG_LEVEL}
|
||||
@@ -461,6 +498,7 @@ services:
|
||||
MG_REPORTS_DEFAULT_TEMPLATE: ${MG_REPORTS_DEFAULT_TEMPLATE}
|
||||
MG_PDF_CONVERTER_URL: ${MG_PDF_CONVERTER_URL}
|
||||
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
|
||||
SMQ_ES_URL: ${SMQ_ES_URL}
|
||||
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
|
||||
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
|
||||
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
|
||||
@@ -472,6 +510,8 @@ services:
|
||||
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
|
||||
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
|
||||
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
|
||||
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
|
||||
MG_REPORTS_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
|
||||
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
|
||||
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
|
||||
@@ -496,21 +536,23 @@ services:
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
|
||||
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
- ./templates/${MG_REPORTS_EMAIL_TEMPLATE}:/email.tmpl
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
alarm:
|
||||
operations:
|
||||
- list: alarm_read_permission
|
||||
- view: alarm_read_permission
|
||||
- update: alarm_update_permission
|
||||
- delete: alarm_delete_permission
|
||||
- assign: alarm_assign_permission
|
||||
- acknowledge: alarm_acknowledge_permission
|
||||
- resolve: alarm_resolve_permission
|
||||
|
||||
rule:
|
||||
operations:
|
||||
- add: rule_create_permission
|
||||
- list: rule_read_permission
|
||||
- view: read_permission
|
||||
- update: update_permission
|
||||
- update_tags: update_permission
|
||||
- update_schedule: update_permission
|
||||
- enable: update_permission
|
||||
- disable: update_permission
|
||||
- delete: delete_permission
|
||||
- alarm_assign: alarm_assign_permission
|
||||
- alarm_acknowledge: alarm_acknowledge_permission
|
||||
- alarm_resolve: alarm_resolve_permission
|
||||
roles_operations:
|
||||
- add: manage_role_permission
|
||||
- remove: manage_role_permission
|
||||
- update: manage_role_permission
|
||||
- retrieve: view_role_users_permission
|
||||
- retrieve_all: view_role_users_permission
|
||||
- add_actions: manage_role_permission
|
||||
- list_actions: view_role_users_permission
|
||||
- check_actions_exists: view_role_users_permission
|
||||
- remove_actions: manage_role_permission
|
||||
- remove_all_actions: manage_role_permission
|
||||
- add_members: add_role_users_permission
|
||||
- list_members: view_role_users_permission
|
||||
- check_members_exists: view_role_users_permission
|
||||
- remove_members: remove_role_users_permission
|
||||
- remove_all_members: remove_role_users_permission
|
||||
|
||||
report:
|
||||
operations:
|
||||
- add: report_create_permission
|
||||
- list: report_read_permission
|
||||
- generate: report_read_permission
|
||||
- view: read_permission
|
||||
- update: update_permission
|
||||
- update_schedule: update_permission
|
||||
- enable: update_permission
|
||||
- disable: update_permission
|
||||
- delete: delete_permission
|
||||
- update_template: update_permission
|
||||
- view_template: read_permission
|
||||
- delete_template: delete_permission
|
||||
roles_operations:
|
||||
- add: manage_role_permission
|
||||
- remove: manage_role_permission
|
||||
- update: manage_role_permission
|
||||
- retrieve: view_role_users_permission
|
||||
- retrieve_all: view_role_users_permission
|
||||
- add_actions: manage_role_permission
|
||||
- list_actions: view_role_users_permission
|
||||
- check_actions_exists: view_role_users_permission
|
||||
- remove_actions: manage_role_permission
|
||||
- remove_all_actions: manage_role_permission
|
||||
- add_members: add_role_users_permission
|
||||
- list_members: view_role_users_permission
|
||||
- check_members_exists: view_role_users_permission
|
||||
- remove_members: remove_role_users_permission
|
||||
- remove_all_members: remove_role_users_permission
|
||||
@@ -0,0 +1,688 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Code generated by scripts/combine-schema.sh. DO NOT EDIT.
|
||||
//
|
||||
// Combined from:
|
||||
// - docker/supermq-docker/spicedb/schema.zed
|
||||
// - docker/spicedb/override-schema.zed
|
||||
|
||||
definition user {}
|
||||
|
||||
|
||||
definition role {
|
||||
relation entity: domain | group | channel | client
|
||||
relation member: user
|
||||
relation built_in_role: domain | group | channel | client
|
||||
|
||||
permission delete = entity->manage_role_permission - built_in_role->manage_role_permission
|
||||
permission update = entity->manage_role_permission - built_in_role->manage_role_permission
|
||||
permission read = entity->manage_role_permission - built_in_role->manage_role_permission
|
||||
|
||||
permission add_user = entity->add_role_users_permission
|
||||
permission remove_user = entity->remove_role_users_permission
|
||||
permission view_user = entity->view_role_users_permission
|
||||
}
|
||||
|
||||
definition client {
|
||||
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain
|
||||
relation parent_group: group
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
relation set_parent_group: role#member
|
||||
relation connect_to_channel: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
permission update_permission = update + parent_group->client_update_permission + domain->client_update_permission
|
||||
permission read_permission = read + parent_group->client_read_permission + domain->client_read_permission
|
||||
permission delete_permission = delete + parent_group->client_delete_permission + domain->client_delete_permission
|
||||
permission set_parent_group_permission = set_parent_group + parent_group->client_set_parent_group_permission + domain->client_set_parent_group_permission
|
||||
permission connect_to_channel_permission = connect_to_channel + parent_group->client_connect_to_channel_permission + domain->client_connect_to_channel_permission
|
||||
|
||||
permission manage_role_permission = manage_role + parent_group->client_manage_role_permission + domain->client_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + parent_group->client_add_role_users_permission + domain->client_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + parent_group->client_remove_role_users_permission + domain->client_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + parent_group->client_view_role_users_permission + domain->client_view_role_users_permission
|
||||
}
|
||||
|
||||
definition channel {
|
||||
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain
|
||||
relation parent_group: group
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
relation set_parent_group: role#member
|
||||
relation connect_to_client: role#member
|
||||
relation publish: role#member | client
|
||||
relation subscribe: role#member | client
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
permission update_permission = update + parent_group->channel_update_permission + domain->channel_update_permission
|
||||
permission read_permission = read + parent_group->channel_read_permission + domain->channel_read_permission
|
||||
permission delete_permission = delete + parent_group->channel_delete_permission + domain->channel_delete_permission
|
||||
permission set_parent_group_permission = set_parent_group + parent_group->channel_set_parent_group_permission + domain->channel_set_parent_group_permission
|
||||
permission connect_to_client_permission = connect_to_client + parent_group->channel_connect_to_client_permission + domain->channel_connect_to_client_permission
|
||||
permission publish_permission = publish + parent_group->channel_publish_permission + domain->channel_publish_permission
|
||||
permission subscribe_permission = subscribe + parent_group->channel_subscribe_permission + domain->channel_subscribe_permission
|
||||
|
||||
permission manage_role_permission = manage_role + parent_group->channel_manage_role_permission + domain->channel_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + parent_group->channel_add_role_users_permission + domain->channel_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + parent_group->channel_remove_role_users_permission + domain->channel_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + parent_group->channel_view_role_users_permission + domain->channel_view_role_users_permission
|
||||
}
|
||||
|
||||
definition group {
|
||||
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it is safe to add domain
|
||||
relation parent_group: group
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation membership: role#member
|
||||
relation delete: role#member
|
||||
relation set_child: role#member
|
||||
relation set_parent: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
relation client_create: role#member
|
||||
relation channel_create: role#member
|
||||
// this allows to add parent for group during the new group creation
|
||||
relation subgroup_create: role#member
|
||||
relation subgroup_client_create: role#member
|
||||
relation subgroup_channel_create: role#member
|
||||
|
||||
relation client_update: role#member
|
||||
relation client_read: role#member
|
||||
relation client_delete: role#member
|
||||
relation client_set_parent_group: role#member
|
||||
relation client_connect_to_channel: role#member
|
||||
|
||||
relation client_manage_role: role#member
|
||||
relation client_add_role_users: role#member
|
||||
relation client_remove_role_users: role#member
|
||||
relation client_view_role_users: role#member
|
||||
|
||||
relation channel_update: role#member
|
||||
relation channel_read: role#member
|
||||
relation channel_delete: role#member
|
||||
relation channel_set_parent_group: role#member
|
||||
relation channel_connect_to_client: role#member
|
||||
relation channel_publish: role#member
|
||||
relation channel_subscribe: role#member
|
||||
|
||||
relation channel_manage_role: role#member
|
||||
relation channel_add_role_users: role#member
|
||||
relation channel_remove_role_users: role#member
|
||||
relation channel_view_role_users: role#member
|
||||
|
||||
relation subgroup_update: role#member
|
||||
relation subgroup_read: role#member
|
||||
relation subgroup_membership: role#member
|
||||
relation subgroup_delete: role#member
|
||||
relation subgroup_set_child: role#member
|
||||
relation subgroup_set_parent: role#member
|
||||
|
||||
relation subgroup_manage_role: role#member
|
||||
relation subgroup_add_role_users: role#member
|
||||
relation subgroup_remove_role_users: role#member
|
||||
relation subgroup_view_role_users: role#member
|
||||
|
||||
relation subgroup_client_update: role#member
|
||||
relation subgroup_client_read: role#member
|
||||
relation subgroup_client_delete: role#member
|
||||
relation subgroup_client_set_parent_group: role#member
|
||||
relation subgroup_client_connect_to_channel: role#member
|
||||
|
||||
relation subgroup_client_manage_role: role#member
|
||||
relation subgroup_client_add_role_users: role#member
|
||||
relation subgroup_client_remove_role_users: role#member
|
||||
relation subgroup_client_view_role_users: role#member
|
||||
|
||||
relation subgroup_channel_update: role#member
|
||||
relation subgroup_channel_read: role#member
|
||||
relation subgroup_channel_delete: role#member
|
||||
relation subgroup_channel_set_parent_group: role#member
|
||||
relation subgroup_channel_connect_to_client: role#member
|
||||
relation subgroup_channel_publish: role#member
|
||||
relation subgroup_channel_subscribe: role#member
|
||||
|
||||
relation subgroup_channel_manage_role: role#member
|
||||
relation subgroup_channel_add_role_users: role#member
|
||||
relation subgroup_channel_remove_role_users: role#member
|
||||
relation subgroup_channel_view_role_users: role#member
|
||||
|
||||
// Subgroup permission
|
||||
permission subgroup_create_permission = subgroup_create + parent_group->subgroup_create_permission
|
||||
permission subgroup_client_create_permission = subgroup_client_create + parent_group->subgroup_client_create_permission
|
||||
permission subgroup_channel_create_permission = subgroup_channel_create + parent_group->subgroup_channel_create_permission
|
||||
|
||||
permission subgroup_update_permission = subgroup_update + parent_group->subgroup_update_permission
|
||||
permission subgroup_membership_permission = subgroup_membership + parent_group->subgroup_membership_permission
|
||||
permission subgroup_read_permission = subgroup_read + parent_group->subgroup_read_permission
|
||||
permission subgroup_delete_permission = subgroup_delete + parent_group->subgroup_delete_permission
|
||||
permission subgroup_set_child_permission = subgroup_set_child + parent_group->subgroup_set_child_permission
|
||||
permission subgroup_set_parent_permission = subgroup_set_parent + parent_group->subgroup_set_parent_permission
|
||||
|
||||
permission subgroup_manage_role_permission = subgroup_manage_role + parent_group->subgroup_manage_role_permission
|
||||
permission subgroup_add_role_users_permission = subgroup_add_role_users + parent_group->subgroup_add_role_users_permission
|
||||
permission subgroup_remove_role_users_permission = subgroup_remove_role_users + parent_group->subgroup_remove_role_users_permission
|
||||
permission subgroup_view_role_users_permission = subgroup_view_role_users + parent_group->subgroup_view_role_users_permission
|
||||
|
||||
// Group permission
|
||||
permission update_permission = update + parent_group->subgroup_create_permission + domain->group_update_permission
|
||||
permission membership_permission = membership + parent_group->subgroup_membership_permission + domain->group_membership_permission
|
||||
permission read_permission = read + parent_group->subgroup_read_permission + domain->group_read_permission
|
||||
permission delete_permission = delete + parent_group->subgroup_delete_permission + domain->group_delete_permission
|
||||
permission set_child_permission = set_child + parent_group->subgroup_set_child_permission + domain->group_set_child_permission
|
||||
permission set_parent_permission = set_parent + parent_group->subgroup_set_parent_permission + domain->group_set_parent_permission
|
||||
|
||||
permission manage_role_permission = manage_role + parent_group->subgroup_manage_role_permission + domain->group_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + parent_group->subgroup_add_role_users_permission + domain->group_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + parent_group->subgroup_remove_role_users_permission + domain->group_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + parent_group->subgroup_view_role_users_permission + domain->group_view_role_users_permission
|
||||
|
||||
// Subgroup clients permission
|
||||
permission subgroup_client_update_permission = subgroup_client_update + parent_group->subgroup_client_update_permission
|
||||
permission subgroup_client_read_permission = subgroup_client_read + parent_group->subgroup_client_read_permission
|
||||
permission subgroup_client_delete_permission = subgroup_client_delete + parent_group->subgroup_client_delete_permission
|
||||
permission subgroup_client_set_parent_group_permission = subgroup_client_set_parent_group + parent_group->subgroup_client_set_parent_group_permission
|
||||
permission subgroup_client_connect_to_channel_permission = subgroup_client_connect_to_channel + parent_group->subgroup_client_connect_to_channel_permission
|
||||
|
||||
permission subgroup_client_manage_role_permission = subgroup_client_manage_role + parent_group->subgroup_client_manage_role_permission
|
||||
permission subgroup_client_add_role_users_permission = subgroup_client_add_role_users + parent_group->subgroup_client_add_role_users_permission
|
||||
permission subgroup_client_remove_role_users_permission = subgroup_client_remove_role_users + parent_group->subgroup_client_remove_role_users_permission
|
||||
permission subgroup_client_view_role_users_permission = subgroup_client_view_role_users + parent_group->subgroup_client_view_role_users_permission
|
||||
|
||||
// Group clients permission
|
||||
permission client_create_permission = client_create + parent_group->subgroup_client_create_permission + domain->client_create_permission
|
||||
permission client_update_permission = client_update + parent_group->subgroup_client_update_permission + domain->client_update_permission
|
||||
permission client_read_permission = client_read + parent_group->subgroup_client_read_permission + domain->client_read_permission
|
||||
permission client_delete_permission = client_delete + parent_group->subgroup_client_delete_permission + domain->client_delete_permission
|
||||
permission client_set_parent_group_permission = client_set_parent_group + parent_group->subgroup_client_set_parent_group_permission + domain->client_set_parent_group_permission
|
||||
permission client_connect_to_channel_permission = client_connect_to_channel + parent_group->subgroup_client_connect_to_channel_permission + domain->client_connect_to_channel_permission
|
||||
|
||||
permission client_manage_role_permission = client_manage_role + parent_group->subgroup_client_manage_role_permission + domain->client_manage_role_permission
|
||||
permission client_add_role_users_permission = client_add_role_users + parent_group->subgroup_client_add_role_users_permission + domain->client_add_role_users_permission
|
||||
permission client_remove_role_users_permission = client_remove_role_users + parent_group->subgroup_client_remove_role_users_permission + domain->client_remove_role_users_permission
|
||||
permission client_view_role_users_permission = client_view_role_users + parent_group->subgroup_client_view_role_users_permission + domain->client_view_role_users_permission
|
||||
|
||||
// Subgroup channels permission
|
||||
permission subgroup_channel_update_permission = subgroup_channel_update + parent_group->subgroup_channel_update_permission
|
||||
permission subgroup_channel_read_permission = subgroup_channel_read + parent_group->subgroup_channel_read_permission
|
||||
permission subgroup_channel_delete_permission = subgroup_channel_delete + parent_group->subgroup_channel_delete_permission
|
||||
permission subgroup_channel_set_parent_group_permission = subgroup_channel_set_parent_group + parent_group->subgroup_channel_set_parent_group_permission
|
||||
permission subgroup_channel_connect_to_client_permission = subgroup_channel_connect_to_client + parent_group->subgroup_channel_connect_to_client_permission
|
||||
permission subgroup_channel_publish_permission = subgroup_channel_publish + parent_group->subgroup_channel_publish_permission
|
||||
permission subgroup_channel_subscribe_permission = subgroup_channel_subscribe + parent_group->subgroup_channel_subscribe_permission
|
||||
|
||||
permission subgroup_channel_manage_role_permission = subgroup_channel_manage_role + parent_group->subgroup_channel_manage_role_permission
|
||||
permission subgroup_channel_add_role_users_permission = subgroup_channel_add_role_users + parent_group->subgroup_channel_add_role_users_permission
|
||||
permission subgroup_channel_remove_role_users_permission = subgroup_channel_remove_role_users + parent_group->subgroup_channel_remove_role_users_permission
|
||||
permission subgroup_channel_view_role_users_permission = subgroup_channel_view_role_users + parent_group->subgroup_channel_view_role_users_permission
|
||||
|
||||
// Group channels permission
|
||||
permission channel_create_permission = channel_create + parent_group->subgroup_channel_create_permission + domain->channel_create_permission
|
||||
permission channel_update_permission = channel_update + parent_group->subgroup_channel_update_permission + domain->channel_update_permission
|
||||
permission channel_read_permission = channel_read + parent_group->subgroup_channel_read_permission + domain->channel_read_permission
|
||||
permission channel_delete_permission = channel_delete + parent_group->subgroup_channel_delete_permission + domain->channel_delete_permission
|
||||
permission channel_set_parent_group_permission = channel_set_parent_group + parent_group->subgroup_channel_set_parent_group_permission + domain->channel_set_parent_group_permission
|
||||
permission channel_connect_to_client_permission = channel_connect_to_client + parent_group->subgroup_channel_connect_to_client_permission + domain->channel_connect_to_client_permission
|
||||
permission channel_publish_permission = channel_publish + parent_group->subgroup_channel_publish_permission + domain->channel_publish_permission
|
||||
permission channel_subscribe_permission = channel_subscribe + parent_group->subgroup_channel_subscribe_permission + domain->channel_subscribe_permission
|
||||
|
||||
permission channel_manage_role_permission = channel_manage_role + parent_group->channel_manage_role_permission + domain->channel_manage_role_permission
|
||||
permission channel_add_role_users_permission = channel_add_role_users + parent_group->channel_add_role_users_permission + domain->channel_add_role_users_permission
|
||||
permission channel_remove_role_users_permission = channel_remove_role_users + parent_group->channel_remove_role_users_permission + domain->channel_remove_role_users_permission
|
||||
permission channel_view_role_users_permission = channel_view_role_users + parent_group->channel_view_role_users_permission + domain->channel_view_role_users_permission
|
||||
|
||||
|
||||
}
|
||||
|
||||
definition domain {
|
||||
//Replace platform with organization in future
|
||||
relation organization: platform
|
||||
relation team: team
|
||||
|
||||
relation update: role#member | team#member
|
||||
relation enable: role#member | team#member
|
||||
relation disable: role#member | team#member
|
||||
relation read: role#member | team#member
|
||||
relation delete: role#member | team#member
|
||||
|
||||
relation manage_role: role#member | team#member
|
||||
relation add_role_users: role#member | team#member
|
||||
relation remove_role_users: role#member | team#member
|
||||
relation view_role_users: role#member | team#member
|
||||
|
||||
relation client_create: role#member | team#member
|
||||
relation channel_create: role#member | team#member
|
||||
relation group_create: role#member | team#member
|
||||
|
||||
relation client_update: role#member | team#member
|
||||
relation client_read: role#member | team#member
|
||||
relation client_delete: role#member | team#member
|
||||
relation client_set_parent_group: role#member | team#member
|
||||
relation client_connect_to_channel: role#member | team#member
|
||||
|
||||
relation client_manage_role: role#member | team#member
|
||||
relation client_add_role_users: role#member | team#member
|
||||
relation client_remove_role_users: role#member | team#member
|
||||
relation client_view_role_users: role#member | team#member
|
||||
|
||||
relation channel_update: role#member | team#member
|
||||
relation channel_read: role#member | team#member
|
||||
relation channel_delete: role#member | team#member
|
||||
relation channel_set_parent_group: role#member | team#member
|
||||
relation channel_connect_to_client: role#member | team#member
|
||||
relation channel_publish: role#member | team#member
|
||||
relation channel_subscribe: role#member | team#member
|
||||
|
||||
relation channel_manage_role: role#member | team#member
|
||||
relation channel_add_role_users: role#member | team#member
|
||||
relation channel_remove_role_users: role#member | team#member
|
||||
relation channel_view_role_users: role#member | team#member
|
||||
|
||||
relation group_update: role#member | team#member
|
||||
relation group_membership: role#member | team#member
|
||||
relation group_read: role#member | team#member
|
||||
relation group_delete: role#member | team#member
|
||||
relation group_set_child: role#member | team#member
|
||||
relation group_set_parent: role#member | team#member
|
||||
|
||||
relation group_manage_role: role#member | team#member
|
||||
relation group_add_role_users: role#member | team#member
|
||||
relation group_remove_role_users: role#member | team#member
|
||||
relation group_view_role_users: role#member | team#member
|
||||
|
||||
// Magistrala-specific relations
|
||||
relation alarm_update: role#member | team#member
|
||||
relation alarm_read: role#member | team#member
|
||||
relation alarm_delete: role#member | team#member
|
||||
relation rule_create: role#member | team#member
|
||||
relation rule_update: role#member | team#member
|
||||
relation rule_read: role#member | team#member
|
||||
relation rule_delete: role#member | team#member
|
||||
relation rule_manage_role: role#member | team#member
|
||||
relation rule_add_role_users: role#member | team#member
|
||||
relation rule_remove_role_users: role#member | team#member
|
||||
relation rule_view_role_users: role#member | team#member
|
||||
relation alarm_assign: role#member | team#member
|
||||
relation alarm_acknowledge: role#member | team#member
|
||||
relation alarm_resolve: role#member | team#member
|
||||
relation report_create: role#member | team#member
|
||||
relation report_update: role#member | team#member
|
||||
relation report_read: role#member | team#member
|
||||
relation report_delete: role#member | team#member
|
||||
relation report_manage_role: role#member | team#member
|
||||
relation report_add_role_users: role#member | team#member
|
||||
relation report_remove_role_users: role#member | team#member
|
||||
relation report_view_role_users: role#member | team#member
|
||||
|
||||
permission update_permission = update + team->domain_update + organization->admin
|
||||
permission read_permission = read + team->domain_read + organization->admin
|
||||
permission enable_permission = enable + team->domain_update + organization->admin
|
||||
permission disable_permission = disable + team->domain_update + organization->admin
|
||||
permission delete_permission = delete + team->domain_delete + organization->admin
|
||||
|
||||
permission manage_role_permission = manage_role + team->domain_manage_role + organization->admin
|
||||
permission add_role_users_permission = add_role_users + team->domain_add_role_users + organization->admin
|
||||
permission remove_role_users_permission = remove_role_users + team->domain_remove_role_users + organization->admin
|
||||
permission view_role_users_permission = view_role_users + team->domain_view_role_users + organization->admin
|
||||
|
||||
permission membership = read + update + enable + disable + delete +
|
||||
manage_role + add_role_users + remove_role_users + view_role_users +
|
||||
client_create + channel_create + group_create +
|
||||
client_update + client_read + client_delete + client_set_parent_group + client_connect_to_channel +
|
||||
client_manage_role + client_add_role_users + client_remove_role_users + client_view_role_users +
|
||||
channel_update + channel_read + channel_delete + channel_set_parent_group + channel_connect_to_client + channel_publish + channel_subscribe +
|
||||
channel_manage_role + channel_add_role_users + channel_remove_role_users + channel_view_role_users +
|
||||
group_update + group_membership + group_read + group_delete + group_set_child + group_set_parent +
|
||||
group_manage_role + group_add_role_users + group_remove_role_users + group_view_role_users +
|
||||
alarm_update + alarm_read + alarm_delete + rule_create + rule_update + rule_read + rule_delete + rule_manage_role + rule_add_role_users + rule_remove_role_users + rule_view_role_users + alarm_assign + alarm_acknowledge + alarm_resolve + report_create + report_update + report_read + report_delete + report_manage_role + report_add_role_users + report_remove_role_users + report_view_role_users +
|
||||
organization->admin
|
||||
|
||||
permission admin = (read & update & enable & disable & delete & manage_role & add_role_users & remove_role_users & view_role_users) + organization->admin
|
||||
|
||||
permission client_create_permission = client_create + team->client_create + organization->admin
|
||||
permission channel_create_permission = channel_create + team->channel_create + organization->admin
|
||||
permission group_create_permission = group_create + team->group_create + organization->admin
|
||||
|
||||
permission client_update_permission = client_update + team->client_update + organization->admin
|
||||
permission client_read_permission = client_read + team->client_read + organization->admin
|
||||
permission client_delete_permission = client_delete + team->client_delete + organization->admin
|
||||
permission client_set_parent_group_permission = client_set_parent_group + team->client_set_parent_group + organization->admin
|
||||
permission client_connect_to_channel_permission = client_connect_to_channel + team->client_connect_to_channel + organization->admin
|
||||
|
||||
permission client_manage_role_permission = client_manage_role + team->client_manage_role + organization->admin
|
||||
permission client_add_role_users_permission = client_add_role_users + team->client_add_role_users + organization->admin
|
||||
permission client_remove_role_users_permission = client_remove_role_users + team->client_remove_role_users + organization->admin
|
||||
permission client_view_role_users_permission = client_view_role_users + team->client_view_role_users + organization->admin
|
||||
|
||||
permission channel_update_permission = channel_update + team->channel_update + organization->admin
|
||||
permission channel_read_permission = channel_read + team->channel_read + organization->admin
|
||||
permission channel_delete_permission = channel_delete + team->channel_delete + organization->admin
|
||||
permission channel_set_parent_group_permission = channel_set_parent_group + team->channel_set_parent_group + organization->admin
|
||||
permission channel_connect_to_client_permission = channel_connect_to_client + team->channel_connect_to_client + organization->admin
|
||||
permission channel_publish_permission = channel_publish + team->channel_publish + organization->admin
|
||||
permission channel_subscribe_permission = channel_subscribe + team->channel_subscribe + organization->admin
|
||||
|
||||
permission channel_manage_role_permission = channel_manage_role + team->channel_manage_role + organization->admin
|
||||
permission channel_add_role_users_permission = channel_add_role_users + team->channel_add_role_users + organization->admin
|
||||
permission channel_remove_role_users_permission = channel_remove_role_users + team->channel_remove_role_users + organization->admin
|
||||
permission channel_view_role_users_permission = channel_view_role_users + team->channel_view_role_users + organization->admin
|
||||
|
||||
permission group_update_permission = group_update + team->group_update + organization->admin
|
||||
permission group_membership_permission = group_membership + team->group_membership + organization->admin
|
||||
permission group_read_permission = group_read + team->group_read + organization->admin
|
||||
permission group_delete_permission = group_delete + team->group_delete + organization->admin
|
||||
permission group_set_child_permission = group_set_child + team->group_set_child + organization->admin
|
||||
permission group_set_parent_permission = group_set_parent + team->group_set_parent + organization->admin
|
||||
|
||||
permission group_manage_role_permission = group_manage_role + team->group_manage_role + organization->admin
|
||||
permission group_add_role_users_permission = group_add_role_users + team->group_add_role_users + organization->admin
|
||||
permission group_remove_role_users_permission = group_remove_role_users + team->group_remove_role_users + organization->admin
|
||||
permission group_view_role_users_permission = group_view_role_users + team->group_view_role_users + organization->admin
|
||||
|
||||
// Magistrala-specific permissions
|
||||
permission alarm_update_permission = alarm_update + team->alarm_update + organization->admin
|
||||
permission alarm_read_permission = alarm_read + team->alarm_read + organization->admin
|
||||
permission alarm_delete_permission = alarm_delete + team->alarm_delete + organization->admin
|
||||
permission rule_create_permission = rule_create + team->rule_create + organization->admin
|
||||
permission rule_update_permission = rule_update + team->rule_update + organization->admin
|
||||
permission rule_read_permission = rule_read + team->rule_read + organization->admin
|
||||
permission rule_delete_permission = rule_delete + team->rule_delete + organization->admin
|
||||
permission rule_manage_role_permission = rule_manage_role + team->rule_manage_role + organization->admin
|
||||
permission rule_add_role_users_permission = rule_add_role_users + team->rule_add_role_users + organization->admin
|
||||
permission rule_remove_role_users_permission = rule_remove_role_users + team->rule_remove_role_users + organization->admin
|
||||
permission rule_view_role_users_permission = rule_view_role_users + team->rule_view_role_users + organization->admin
|
||||
permission alarm_assign_permission = alarm_assign + team->alarm_assign + organization->admin
|
||||
permission alarm_acknowledge_permission = alarm_acknowledge + team->alarm_acknowledge + organization->admin
|
||||
permission alarm_resolve_permission = alarm_resolve + team->alarm_resolve + organization->admin
|
||||
permission report_create_permission = report_create + team->report_create + organization->admin
|
||||
permission report_update_permission = report_update + team->report_update + organization->admin
|
||||
permission report_read_permission = report_read + team->report_read + organization->admin
|
||||
permission report_delete_permission = report_delete + team->report_delete + organization->admin
|
||||
permission report_manage_role_permission = report_manage_role + team->report_manage_role + organization->admin
|
||||
permission report_add_role_users_permission = report_add_role_users + team->report_add_role_users + organization->admin
|
||||
permission report_remove_role_users_permission = report_remove_role_users + team->report_remove_role_users + organization->admin
|
||||
permission report_view_role_users_permission = report_view_role_users + team->report_view_role_users + organization->admin
|
||||
|
||||
}
|
||||
|
||||
// Add this relation and permission in future while adding organization
|
||||
definition team {
|
||||
relation organization: organization
|
||||
relation parent_team: team
|
||||
|
||||
relation delete: role#member
|
||||
relation enable: role#member | team#member
|
||||
relation disable: role#member | team#member
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
|
||||
relation set_parent: role#member
|
||||
relation set_child: role#member
|
||||
|
||||
relation member: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
relation subteam_delete: role#member
|
||||
relation subteam_update: role#member
|
||||
relation subteam_read: role#member
|
||||
|
||||
relation subteam_member: role#member
|
||||
|
||||
relation subteam_set_child: role#member
|
||||
relation subteam_set_parent: role#member
|
||||
|
||||
relation subteam_manage_role: role#member
|
||||
relation subteam_add_role_users: role#member
|
||||
relation subteam_remove_role_users: role#member
|
||||
relation subteam_view_role_users: role#member
|
||||
|
||||
// Domain related permission
|
||||
|
||||
relation domain_update: role#member | team#member
|
||||
relation domain_read: role#member | team#member
|
||||
relation domain_membership: role#member | team#member
|
||||
relation domain_delete: role#member | team#member
|
||||
|
||||
relation domain_manage_role: role#member | team#member
|
||||
relation domain_add_role_users: role#member | team#member
|
||||
relation domain_remove_role_users: role#member | team#member
|
||||
relation domain_view_role_users: role#member | team#member
|
||||
|
||||
relation client_create: role#member | team#member
|
||||
relation channel_create: role#member | team#member
|
||||
relation group_create: role#member | team#member
|
||||
|
||||
relation client_update: role#member | team#member
|
||||
relation client_read: role#member | team#member
|
||||
relation client_delete: role#member | team#member
|
||||
relation client_set_parent_group: role#member | team#member
|
||||
relation client_connect_to_channel: role#member | team#member
|
||||
|
||||
relation client_manage_role: role#member | team#member
|
||||
relation client_add_role_users: role#member | team#member
|
||||
relation client_remove_role_users: role#member | team#member
|
||||
relation client_view_role_users: role#member | team#member
|
||||
|
||||
relation channel_update: role#member | team#member
|
||||
relation channel_read: role#member | team#member
|
||||
relation channel_delete: role#member | team#member
|
||||
relation channel_set_parent_group: role#member | team#member
|
||||
relation channel_connect_to_client: role#member | team#member
|
||||
relation channel_publish: role#member | team#member
|
||||
relation channel_subscribe: role#member | team#member
|
||||
|
||||
relation channel_manage_role: role#member | team#member
|
||||
relation channel_add_role_users: role#member | team#member
|
||||
relation channel_remove_role_users: role#member | team#member
|
||||
relation channel_view_role_users: role#member | team#member
|
||||
|
||||
relation group_update: role#member | team#member
|
||||
relation group_membership: role#member | team#member
|
||||
relation group_read: role#member | team#member
|
||||
relation group_delete: role#member | team#member
|
||||
relation group_set_child: role#member | team#member
|
||||
relation group_set_parent: role#member | team#member
|
||||
|
||||
relation group_manage_role: role#member | team#member
|
||||
relation group_add_role_users: role#member | team#member
|
||||
relation group_remove_role_users: role#member | team#member
|
||||
relation group_view_role_users: role#member | team#member
|
||||
|
||||
// Magistrala-specific relations
|
||||
relation alarm_update: role#member | team#member
|
||||
relation alarm_read: role#member | team#member
|
||||
relation alarm_delete: role#member | team#member
|
||||
relation rule_create: role#member | team#member
|
||||
relation rule_update: role#member | team#member
|
||||
relation rule_read: role#member | team#member
|
||||
relation rule_delete: role#member | team#member
|
||||
relation rule_manage_role: role#member | team#member
|
||||
relation rule_add_role_users: role#member | team#member
|
||||
relation rule_remove_role_users: role#member | team#member
|
||||
relation rule_view_role_users: role#member | team#member
|
||||
relation alarm_assign: role#member | team#member
|
||||
relation alarm_acknowledge: role#member | team#member
|
||||
relation alarm_resolve: role#member | team#member
|
||||
relation report_create: role#member | team#member
|
||||
relation report_update: role#member | team#member
|
||||
relation report_read: role#member | team#member
|
||||
relation report_delete: role#member | team#member
|
||||
relation report_manage_role: role#member | team#member
|
||||
relation report_add_role_users: role#member | team#member
|
||||
relation report_remove_role_users: role#member | team#member
|
||||
relation report_view_role_users: role#member | team#member
|
||||
|
||||
permission delete_permission = delete + organization->team_delete + parent_team->subteam_delete + organization->admin
|
||||
permission update_permission = update + organization->team_update + parent_team->subteam_update + organization->admin
|
||||
permission read_permission = read + organization->team_read + parent_team->subteam_read + organization->admin
|
||||
|
||||
permission set_parent_permission = set_parent + organization->team_set_parent + parent_team->subteam_set_parent + organization->admin
|
||||
permission set_child_permisssion = set_child + organization->team_set_child + parent_team->subteam_set_child + organization->admin
|
||||
|
||||
permission membership = member + organization->team_member + parent_team->subteam_member + organization->admin
|
||||
|
||||
permission manage_role_permission = manage_role + organization->team_manage_role + parent_team->subteam_manage_role + organization->admin
|
||||
permission add_role_users_permission = add_role_users + organization->team_add_role_users + parent_team->subteam_add_role_users + organization->admin
|
||||
permission remove_role_users_permission = remove_role_users + organization->team_remove_role_users + parent_team->subteam_remove_role_users + organization->admin
|
||||
permission view_role_users_permission = view_role_users + organization->team_view_role_users + parent_team->subteam_view_role_users + organization->admin
|
||||
}
|
||||
|
||||
|
||||
definition organization {
|
||||
relation platform: platform
|
||||
relation administrator: user
|
||||
|
||||
relation delete: role#member
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
|
||||
relation member: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
relation team_create: role#member
|
||||
|
||||
relation team_delete: role#member
|
||||
relation team_update: role#member
|
||||
relation team_read: role#member
|
||||
|
||||
relation team_member: role#member // Will be member of all the teams in the organization
|
||||
|
||||
relation team_set_child: role#member
|
||||
relation team_set_parent: role#member
|
||||
|
||||
relation team_manage_role: role#member
|
||||
relation team_add_role_users: role#member
|
||||
relation team_remove_role_users: role#member
|
||||
relation team_view_role_users: role#member
|
||||
|
||||
permission admin = administrator + platform->administrator
|
||||
permission delete_permission = admin + delete->member
|
||||
permission update_permission = admin + update->member
|
||||
permission read_permission = admin + read->member
|
||||
|
||||
permission membership = admin + member->member
|
||||
|
||||
permission team_create_permission = admin + team_create->member
|
||||
|
||||
permission manage_role_permission = admin + manage_role
|
||||
permission add_role_users_permisson = admin + add_role_users
|
||||
permission remove_role_users_permission = admin + remove_role_users
|
||||
permission view_role_users_permission = admin + view_role_users
|
||||
}
|
||||
|
||||
|
||||
definition platform {
|
||||
relation administrator: user
|
||||
relation member: user
|
||||
|
||||
permission admin = administrator
|
||||
permission membership = administrator + member
|
||||
}
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Merge documentation
|
||||
// - Source A (base): docker/supermq-docker/spicedb/schema.zed (SuperMQ upstream schema)
|
||||
// - Source B (overlay): docker/spicedb/override-schema.zed (Magistrala schema extensions)
|
||||
// - Merge script: scripts/combined-schema.sh
|
||||
// - Output: docker/spicedb/combined-schema.zed
|
||||
//
|
||||
// How merge works:
|
||||
// 1. The first `definition domain { ... }` block is treated as explicit domain overlay.
|
||||
// 2. The first `definition team { ... }` block is treated as explicit team overlay.
|
||||
// 3. Domain overlay relations/permissions are injected into SuperMQ `definition domain`.
|
||||
// 4. Team overlay relations are injected into SuperMQ `definition team`.
|
||||
// 5. `permission membership_extension = ...` from the domain overlay is injected into
|
||||
// SuperMQ `domain.permission membership` before `organization->admin`.
|
||||
// 6. Overlay `definition domain` and `definition team` blocks are removed before append,
|
||||
// so only merged SuperMQ domain/team definitions remain.
|
||||
// 7. Remaining definitions in this file (for example `alarm`, `rule`, `report`) are appended.
|
||||
//
|
||||
// Maintenance notes:
|
||||
// - Keep all custom domain/team merge lines inside the two overlay blocks below.
|
||||
// - Update `permission membership_extension` whenever domain membership additions change.
|
||||
// - Regenerate combined schema with: `sh scripts/combine-schema.sh`
|
||||
// - `scripts/supermq.sh` also regenerates combined schema after refreshing SuperMQ docker files.
|
||||
|
||||
// Overlay domain block consumed by scripts/combine-schema.sh during merge.
|
||||
|
||||
// Overlay team block consumed by scripts/combine-schema.sh during merge.
|
||||
|
||||
definition rule {
|
||||
relation domain: domain
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
relation alarm_read: role#member
|
||||
relation alarm_assign: role#member
|
||||
relation alarm_acknowledge: role#member
|
||||
relation alarm_resolve: role#member
|
||||
|
||||
permission update_permission = update + domain->rule_update_permission
|
||||
permission read_permission = read + domain->rule_read_permission
|
||||
permission delete_permission = delete + domain->rule_delete_permission
|
||||
|
||||
permission manage_role_permission = manage_role + domain->rule_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + domain->rule_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + domain->rule_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + domain->rule_view_role_users_permission
|
||||
|
||||
permission alarm_read_permission = alarm_read + domain->alarm_read_permission
|
||||
permission alarm_assign_permission = alarm_assign + domain->alarm_assign_permission
|
||||
permission alarm_acknowledge_permission = alarm_acknowledge + domain->alarm_acknowledge_permission
|
||||
permission alarm_resolve_permission = alarm_resolve + domain->alarm_resolve_permission
|
||||
}
|
||||
|
||||
definition report {
|
||||
relation domain: domain
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
permission update_permission = update + domain->report_update_permission
|
||||
permission read_permission = read + domain->report_read_permission
|
||||
permission delete_permission = delete + domain->report_delete_permission
|
||||
|
||||
permission manage_role_permission = manage_role + domain->report_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + domain->report_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + domain->report_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + domain->report_view_role_users_permission
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Merge documentation
|
||||
// - Source A (base): docker/supermq-docker/spicedb/schema.zed (SuperMQ upstream schema)
|
||||
// - Source B (overlay): docker/spicedb/override-schema.zed (Magistrala schema extensions)
|
||||
// - Merge script: scripts/combined-schema.sh
|
||||
// - Output: docker/spicedb/combined-schema.zed
|
||||
//
|
||||
// How merge works:
|
||||
// 1. The first `definition domain { ... }` block is treated as explicit domain overlay.
|
||||
// 2. The first `definition team { ... }` block is treated as explicit team overlay.
|
||||
// 3. Domain overlay relations/permissions are injected into SuperMQ `definition domain`.
|
||||
// 4. Team overlay relations are injected into SuperMQ `definition team`.
|
||||
// 5. `permission membership_extension = ...` from the domain overlay is injected into
|
||||
// SuperMQ `domain.permission membership` before `organization->admin`.
|
||||
// 6. Overlay `definition domain` and `definition team` blocks are removed before append,
|
||||
// so only merged SuperMQ domain/team definitions remain.
|
||||
// 7. Remaining definitions in this file (for example `alarm`, `rule`, `report`) are appended.
|
||||
//
|
||||
// Maintenance notes:
|
||||
// - Keep all custom domain/team merge lines inside the two overlay blocks below.
|
||||
// - Update `permission membership_extension` whenever domain membership additions change.
|
||||
// - Regenerate combined schema with: `sh scripts/combine-schema.sh`
|
||||
// - `scripts/supermq.sh` also regenerates combined schema after refreshing SuperMQ docker files.
|
||||
|
||||
// Overlay domain block consumed by scripts/combine-schema.sh during merge.
|
||||
definition domain {
|
||||
|
||||
// Magistrala-specific relations
|
||||
relation alarm_update: role#member | team#member
|
||||
relation alarm_read: role#member | team#member
|
||||
relation alarm_delete: role#member | team#member
|
||||
|
||||
relation rule_create: role#member | team#member
|
||||
relation rule_update: role#member | team#member
|
||||
relation rule_read: role#member | team#member
|
||||
relation rule_delete: role#member | team#member
|
||||
relation rule_manage_role: role#member | team#member
|
||||
relation rule_add_role_users: role#member | team#member
|
||||
relation rule_remove_role_users: role#member | team#member
|
||||
relation rule_view_role_users: role#member | team#member
|
||||
relation alarm_assign: role#member | team#member
|
||||
relation alarm_acknowledge: role#member | team#member
|
||||
relation alarm_resolve: role#member | team#member
|
||||
|
||||
relation report_create: role#member | team#member
|
||||
relation report_update: role#member | team#member
|
||||
relation report_read: role#member | team#member
|
||||
relation report_delete: role#member | team#member
|
||||
relation report_manage_role: role#member | team#member
|
||||
relation report_add_role_users: role#member | team#member
|
||||
relation report_remove_role_users: role#member | team#member
|
||||
relation report_view_role_users: role#member | team#member
|
||||
|
||||
// Magistrala-specific permissions
|
||||
permission alarm_update_permission = alarm_update + team->alarm_update + organization->admin
|
||||
permission alarm_read_permission = alarm_read + team->alarm_read + organization->admin
|
||||
permission alarm_delete_permission = alarm_delete + team->alarm_delete + organization->admin
|
||||
|
||||
permission rule_create_permission = rule_create + team->rule_create + organization->admin
|
||||
permission rule_update_permission = rule_update + team->rule_update + organization->admin
|
||||
permission rule_read_permission = rule_read + team->rule_read + organization->admin
|
||||
permission rule_delete_permission = rule_delete + team->rule_delete + organization->admin
|
||||
permission rule_manage_role_permission = rule_manage_role + team->rule_manage_role + organization->admin
|
||||
permission rule_add_role_users_permission = rule_add_role_users + team->rule_add_role_users + organization->admin
|
||||
permission rule_remove_role_users_permission = rule_remove_role_users + team->rule_remove_role_users + organization->admin
|
||||
permission rule_view_role_users_permission = rule_view_role_users + team->rule_view_role_users + organization->admin
|
||||
permission alarm_assign_permission = alarm_assign + team->alarm_assign + organization->admin
|
||||
permission alarm_acknowledge_permission = alarm_acknowledge + team->alarm_acknowledge + organization->admin
|
||||
permission alarm_resolve_permission = alarm_resolve + team->alarm_resolve + organization->admin
|
||||
|
||||
permission report_create_permission = report_create + team->report_create + organization->admin
|
||||
permission report_update_permission = report_update + team->report_update + organization->admin
|
||||
permission report_read_permission = report_read + team->report_read + organization->admin
|
||||
permission report_delete_permission = report_delete + team->report_delete + organization->admin
|
||||
permission report_manage_role_permission = report_manage_role + team->report_manage_role + organization->admin
|
||||
permission report_add_role_users_permission = report_add_role_users + team->report_add_role_users + organization->admin
|
||||
permission report_remove_role_users_permission = report_remove_role_users + team->report_remove_role_users + organization->admin
|
||||
permission report_view_role_users_permission = report_view_role_users + team->report_view_role_users + organization->admin
|
||||
|
||||
// Explicit extension injected into SuperMQ domain `permission membership`.
|
||||
permission membership_extension = alarm_update + alarm_read + alarm_delete + rule_create + rule_update + rule_read + rule_delete + rule_manage_role + rule_add_role_users + rule_remove_role_users + rule_view_role_users + alarm_assign + alarm_acknowledge + alarm_resolve + report_create + report_update + report_read + report_delete + report_manage_role + report_add_role_users + report_remove_role_users + report_view_role_users
|
||||
|
||||
}
|
||||
|
||||
// Overlay team block consumed by scripts/combine-schema.sh during merge.
|
||||
definition team {
|
||||
|
||||
relation alarm_update: role#member | team#member
|
||||
relation alarm_read: role#member | team#member
|
||||
relation alarm_delete: role#member | team#member
|
||||
|
||||
relation rule_create: role#member | team#member
|
||||
relation rule_update: role#member | team#member
|
||||
relation rule_read: role#member | team#member
|
||||
relation rule_delete: role#member | team#member
|
||||
relation rule_manage_role: role#member | team#member
|
||||
relation rule_add_role_users: role#member | team#member
|
||||
relation rule_remove_role_users: role#member | team#member
|
||||
relation rule_view_role_users: role#member | team#member
|
||||
relation alarm_assign: role#member | team#member
|
||||
relation alarm_acknowledge: role#member | team#member
|
||||
relation alarm_resolve: role#member | team#member
|
||||
|
||||
relation report_create: role#member | team#member
|
||||
relation report_update: role#member | team#member
|
||||
relation report_read: role#member | team#member
|
||||
relation report_delete: role#member | team#member
|
||||
relation report_manage_role: role#member | team#member
|
||||
relation report_add_role_users: role#member | team#member
|
||||
relation report_remove_role_users: role#member | team#member
|
||||
relation report_view_role_users: role#member | team#member
|
||||
}
|
||||
|
||||
definition rule {
|
||||
relation domain: domain
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
relation alarm_read: role#member
|
||||
relation alarm_assign: role#member
|
||||
relation alarm_acknowledge: role#member
|
||||
relation alarm_resolve: role#member
|
||||
|
||||
permission update_permission = update + domain->rule_update_permission
|
||||
permission read_permission = read + domain->rule_read_permission
|
||||
permission delete_permission = delete + domain->rule_delete_permission
|
||||
|
||||
permission manage_role_permission = manage_role + domain->rule_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + domain->rule_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + domain->rule_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + domain->rule_view_role_users_permission
|
||||
|
||||
permission alarm_read_permission = alarm_read + domain->alarm_read_permission
|
||||
permission alarm_assign_permission = alarm_assign + domain->alarm_assign_permission
|
||||
permission alarm_acknowledge_permission = alarm_acknowledge + domain->alarm_acknowledge_permission
|
||||
permission alarm_resolve_permission = alarm_resolve + domain->alarm_resolve_permission
|
||||
}
|
||||
|
||||
definition report {
|
||||
relation domain: domain
|
||||
|
||||
relation update: role#member
|
||||
relation read: role#member
|
||||
relation delete: role#member
|
||||
|
||||
relation manage_role: role#member
|
||||
relation add_role_users: role#member
|
||||
relation remove_role_users: role#member
|
||||
relation view_role_users: role#member
|
||||
|
||||
permission update_permission = update + domain->report_update_permission
|
||||
permission read_permission = read + domain->report_read_permission
|
||||
permission delete_permission = delete + domain->report_delete_permission
|
||||
|
||||
permission manage_role_permission = manage_role + domain->report_manage_role_permission
|
||||
permission add_role_users_permission = add_role_users + domain->report_add_role_users_permission
|
||||
permission remove_role_users_permission = remove_role_users + domain->report_remove_role_users_permission
|
||||
permission view_role_users_permission = view_role_users + domain->report_view_role_users_permission
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
services:
|
||||
|
||||
spicedb:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
spicedb-migrate:
|
||||
networks: !override
|
||||
@@ -17,7 +20,8 @@ services:
|
||||
auth-db:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
auth-redis:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
@@ -25,6 +29,8 @@ services:
|
||||
auth:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
domains-db:
|
||||
networks: !override
|
||||
@@ -37,6 +43,8 @@ services:
|
||||
domains:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
clients-db:
|
||||
networks: !override
|
||||
@@ -49,6 +57,8 @@ services:
|
||||
clients:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
channels-redis:
|
||||
networks: !override
|
||||
@@ -61,6 +71,8 @@ services:
|
||||
channels:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
users-db:
|
||||
networks: !override
|
||||
@@ -77,6 +89,8 @@ services:
|
||||
groups:
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
|
||||
|
||||
jaeger:
|
||||
networks: !override
|
||||
@@ -170,20 +184,20 @@ services:
|
||||
AM_CERTS_OPENBAO_UNSEAL_KEY_2: ${AM_CERTS_OPENBAO_UNSEAL_KEY_2}
|
||||
AM_CERTS_OPENBAO_UNSEAL_KEY_3: ${AM_CERTS_OPENBAO_UNSEAL_KEY_3}
|
||||
AM_CERTS_OPENBAO_ROOT_TOKEN: ${AM_CERTS_OPENBAO_ROOT_TOKEN}
|
||||
|
||||
|
||||
AM_JAEGER_URL: ${AM_JAEGER_URL}
|
||||
AM_JAEGER_TRACE_RATIO: ${AM_JAEGER_TRACE_RATIO}
|
||||
|
||||
|
||||
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
|
||||
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
|
||||
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
|
||||
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
|
||||
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT}
|
||||
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY}
|
||||
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS}
|
||||
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
|
||||
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
|
||||
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
|
||||
networks: !override
|
||||
- magistrala-base-net
|
||||
|
||||
@@ -194,10 +208,10 @@ services:
|
||||
- ../../docker/nginx/entrypoint.sh:/docker-entrypoint.d/entrypoint.sh
|
||||
- type: bind
|
||||
source: ${SMQ_NGINX_SERVER_CERT:-../../docker/ssl/certs/magistrala-server.crt}
|
||||
target: /etc/ssl/certs/magistrala-server.crt
|
||||
target: /etc/ssl/certs/supermq-server.crt
|
||||
- type: bind
|
||||
source: ${SMQ_NGINX_SERVER_KEY:-../../docker/ssl/certs/magistrala-server.key}
|
||||
target: /etc/ssl/private/magistrala-server.key
|
||||
target: /etc/ssl/private/supermq-server.key
|
||||
- type: bind
|
||||
source: ${SMQ_NGINX_SERVER_CLIENT_CA:-../../docker/ssl/certs/ca.crt}
|
||||
target: /etc/ssl/certs/ca.crt
|
||||
@@ -209,3 +223,4 @@ services:
|
||||
env_file: !override
|
||||
- ./.env
|
||||
- ../../docker/.env
|
||||
|
||||
|
||||
@@ -238,8 +238,6 @@ SMQ_USERS_ADMIN_USERNAME=admin
|
||||
SMQ_USERS_ADMIN_FIRST_NAME=super
|
||||
SMQ_USERS_ADMIN_LAST_NAME=admin
|
||||
SMQ_USERS_PASS_REGEX=^.{8,}$
|
||||
SMQ_USERS_ACCESS_TOKEN_DURATION=15m
|
||||
SMQ_USERS_REFRESH_TOKEN_DURATION=24h
|
||||
SMQ_USERS_HTTP_HOST=users
|
||||
SMQ_USERS_HTTP_PORT=9002
|
||||
SMQ_USERS_HTTP_SERVER_CERT=
|
||||
@@ -263,8 +261,6 @@ SMQ_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH
|
||||
SMQ_USERS_ADMIN_EMAIL=admin@example.com
|
||||
SMQ_USERS_ADMIN_PASSWORD=12345678
|
||||
SMQ_USERS_PASS_REGEX=^.{8,}$
|
||||
SMQ_USERS_ACCESS_TOKEN_DURATION=15m
|
||||
SMQ_USERS_REFRESH_TOKEN_DURATION=24h
|
||||
SMQ_USERS_ALLOW_SELF_REGISTER=true
|
||||
SMQ_OAUTH_UI_REDIRECT_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/tokens/secure
|
||||
SMQ_OAUTH_UI_ERROR_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/error
|
||||
@@ -418,6 +414,12 @@ SMQ_MQTT_ADAPTER_ES_DB=0
|
||||
SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS=200000
|
||||
SMQ_MQTT_ADAPTER_CACHE_MAX_COST=1048576
|
||||
SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS=64
|
||||
SMQ_MQTT_ADAPTER_CERT_FILE=
|
||||
SMQ_MQTT_ADAPTER_KEY_FILE=
|
||||
SMQ_MQTT_ADAPTER_SERVER_CA_FILE=
|
||||
SMQ_MQTT_ADAPTER_CLIENT_CA_FILE=
|
||||
SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS=
|
||||
SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL=
|
||||
|
||||
### CoAP
|
||||
## If enabled run make all inside docker/ssl directory to generate the DTLS certs
|
||||
@@ -494,6 +496,7 @@ AM_JAEGER_URL=http://jaeger:4318/v1/traces
|
||||
AM_JAEGER_TRACE_RATIO=1.0
|
||||
|
||||
#### Auth Client Config for Certs Service
|
||||
SMQ_ADDONS_CERTS_PATH_PREFIX=../../
|
||||
AM_AUTH_GRPC_URL=auth:7001
|
||||
AM_AUTH_GRPC_TIMEOUT=300s
|
||||
AM_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.26rc2-alpine3.22 AS builder
|
||||
FROM golang:1.26.1-alpine3.22 AS builder
|
||||
ARG SVC
|
||||
ARG GOARCH
|
||||
ARG GOARM
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.25-alpine AS builder
|
||||
FROM golang:1.26-alpine AS builder
|
||||
|
||||
ARG SVC
|
||||
ARG GOARCH
|
||||
|
||||
@@ -46,9 +46,9 @@ services:
|
||||
AM_CERTS_DB_SSL_MODE: ${AM_CERTS_DB_SSL_MODE}
|
||||
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
|
||||
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
|
||||
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
|
||||
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
|
||||
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
|
||||
@@ -65,6 +65,36 @@ services:
|
||||
- ${AM_CERTS_GRPC_PORT}:${AM_CERTS_GRPC_PORT}
|
||||
volumes:
|
||||
- openbao-data:/openbao:ro
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
certs-db:
|
||||
image: postgres:16.2-alpine
|
||||
|
||||
@@ -49,14 +49,14 @@ services:
|
||||
AM_JAEGER_TRACE_RATIO: ${AM_JAEGER_TRACE_RATIO}
|
||||
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
|
||||
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
|
||||
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
|
||||
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
|
||||
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
|
||||
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
|
||||
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
|
||||
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT}
|
||||
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY}
|
||||
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS}
|
||||
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
|
||||
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
|
||||
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
|
||||
networks: !override
|
||||
- supermq-base-net
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ services:
|
||||
- supermq-base-net
|
||||
volumes:
|
||||
- supermq-auth-redis-volume:/data
|
||||
- ./redis/redis.conf:/etc/redis/redis.conf:ro
|
||||
command: ["redis-server", "/etc/redis/redis.conf"]
|
||||
|
||||
auth:
|
||||
image: docker.io/supermq/auth:${SMQ_RELEASE_TAG}
|
||||
@@ -161,45 +163,45 @@ services:
|
||||
# Auth retiring private key file (optional, for key rotation)
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_KEYS_RETIRING_KEY_PATH:-ssl/certs/dummy/retiring_key}
|
||||
target: /keys/retiring${SMQ_AUTH_KEYS_RETIRING_KEY_PATH:+.key}
|
||||
target: /keys/retiring.key
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC mTLS server certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
|
||||
target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_CERT:+.crt}
|
||||
target: /auth-grpc-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
|
||||
target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_KEY:+.key}
|
||||
target: /auth-grpc-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /auth-grpc-client-ca${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth Callout Client Certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_CALLOUT_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-callout-client${SMQ_AUTH_CALLOUT_CLIENT_CERT:+.crt}
|
||||
target: /auth-callout-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_CALLOUT_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-callout-client${SMQ_AUTH_CALLOUT_CLIENT_KEY:+.key}
|
||||
target: /auth-callout-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_CALLOUT_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /auth-callout-client-ca${SMQ_AUTH_CALLOUT_CLIENT_CA_CERTS:+.crt}
|
||||
target: /auth-callout-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -313,86 +315,86 @@ services:
|
||||
# Auth gRPC mTLS server certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
|
||||
target: /domains-grpc-server${SMQ_DOMAINS_GRPC_SERVER_CERT:+.crt}
|
||||
target: /domains-grpc-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
|
||||
target: /domains-grpc-server${SMQ_DOMAINS_GRPC_SERVER_KEY:+.key}
|
||||
target: /domains-grpc-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /domains-grpc-client-ca${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Groups gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /groups-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /groups-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /groups-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Clients gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -550,86 +552,86 @@ services:
|
||||
# Clients gRPC server certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
|
||||
target: /clients-grpc-server${SMQ_CLIENTS_GRPC_SERVER_CERT:+.crt}
|
||||
target: /clients-grpc-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
|
||||
target: /clients-grpc-server${SMQ_CLIENTS_GRPC_SERVER_KEY:+.key}
|
||||
target: /clients-grpc-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /clients-grpc-client-ca${SMQ_CLIENTS_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channel gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Group gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /groups-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /groups-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /groups-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domain gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -743,86 +745,86 @@ services:
|
||||
# Channels gRPC server certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
|
||||
target: /channels-grpc-server${SMQ_CHANNELS_GRPC_SERVER_CERT:+.crt}
|
||||
target: /channels-grpc-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
|
||||
target: /channels-grpc-server${SMQ_CHANNELS_GRPC_SERVER_KEY:+.key}
|
||||
target: /channels-grpc-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /channels-grpc-client-ca${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Clients gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Groups gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /groups-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /groups-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /groups-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -860,8 +862,6 @@ services:
|
||||
SMQ_USERS_ADMIN_FIRST_NAME: ${SMQ_USERS_ADMIN_FIRST_NAME}
|
||||
SMQ_USERS_ADMIN_LAST_NAME: ${SMQ_USERS_ADMIN_LAST_NAME}
|
||||
SMQ_USERS_PASS_REGEX: ${SMQ_USERS_PASS_REGEX}
|
||||
SMQ_USERS_ACCESS_TOKEN_DURATION: ${SMQ_USERS_ACCESS_TOKEN_DURATION}
|
||||
SMQ_USERS_REFRESH_TOKEN_DURATION: ${SMQ_USERS_REFRESH_TOKEN_DURATION}
|
||||
SMQ_USERS_HTTP_HOST: ${SMQ_USERS_HTTP_HOST}
|
||||
SMQ_USERS_HTTP_PORT: ${SMQ_USERS_HTTP_PORT}
|
||||
SMQ_USERS_HTTP_SERVER_CERT: ${SMQ_USERS_HTTP_SERVER_CERT}
|
||||
@@ -933,33 +933,33 @@ services:
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -999,17 +999,17 @@ services:
|
||||
# Users gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_USERS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /users-grpc-client${SMQ_USERS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /users-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_USERS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /users-grpc-client${SMQ_USERS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /users-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_USERS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /users-grpc-server-ca${SMQ_USERS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /users-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -1112,86 +1112,86 @@ services:
|
||||
# Groups gRPC server certificates
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
|
||||
target: /groups-grpc-server${SMQ_GROUPS_GRPC_SERVER_CERT:+.crt}
|
||||
target: /groups-grpc-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
|
||||
target: /groups-grpc-server${SMQ_GROUPS_GRPC_SERVER_KEY:+.key}
|
||||
target: /groups-grpc-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
|
||||
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /groups-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_GROUPS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
|
||||
target: /groups-grpc-client-ca${SMQ_GROUPS_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /groups-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Clients gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -1234,6 +1234,12 @@ services:
|
||||
SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS: ${SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS}
|
||||
SMQ_MQTT_ADAPTER_CACHE_MAX_COST: ${SMQ_MQTT_ADAPTER_CACHE_MAX_COST}
|
||||
SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS: ${SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS}
|
||||
SMQ_MQTT_ADAPTER_CERT_FILE: ${SMQ_MQTT_ADAPTER_CERT_FILE:+/mqtt-adapter.crt}
|
||||
SMQ_MQTT_ADAPTER_KEY_FILE: ${SMQ_MQTT_ADAPTER_KEY_FILE:+/mqtt-adapter.key}
|
||||
SMQ_MQTT_ADAPTER_SERVER_CA_FILE: ${SMQ_MQTT_ADAPTER_SERVER_CA_FILE:+/mqtt-adapter-server-ca.crt}
|
||||
SMQ_MQTT_ADAPTER_CLIENT_CA_FILE: ${SMQ_MQTT_ADAPTER_CLIENT_CA_FILE:+/mqtt-adapter-client-ca.crt}
|
||||
SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS: ${SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS}
|
||||
SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL: ${SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL}
|
||||
SMQ_ES_URL: ${SMQ_ES_URL}
|
||||
SMQ_CLIENTS_GRPC_URL: ${SMQ_CLIENTS_GRPC_URL}
|
||||
SMQ_CLIENTS_GRPC_TIMEOUT: ${SMQ_CLIENTS_GRPC_TIMEOUT}
|
||||
@@ -1257,52 +1263,73 @@ services:
|
||||
networks:
|
||||
- supermq-base-net
|
||||
volumes:
|
||||
# TLS certificate for MQTT
|
||||
- type: bind
|
||||
source: ${SMQ_MQTT_ADAPTER_CERT_FILE:-ssl/certs/dummy/server_cert}
|
||||
target: /mqtt-adapter.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_MQTT_ADAPTER_KEY_FILE:-ssl/certs/dummy/server_key}
|
||||
target: /mqtt-adapter.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_MQTT_ADAPTER_SERVER_CA_FILE:-ssl/certs/dummy/server_ca}
|
||||
target: /mqtt-adapter-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_MQTT_ADAPTER_CLIENT_CA_FILE:-ssl/certs/dummy/client_ca}
|
||||
target: /mqtt-adapter-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Clients gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -1357,65 +1384,65 @@ services:
|
||||
# Clients gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Auth gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /auth-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
target: /auth-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /auth-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -1470,70 +1497,70 @@ services:
|
||||
# DTLS certificates for CoAP
|
||||
- type: bind
|
||||
source: ${SMQ_COAP_ADAPTER_SERVER_CERT_FILE:-ssl/certs/dummy/server_cert}
|
||||
target: /coap-server${SMQ_COAP_ADAPTER_SERVER_CERT_FILE:+.crt}
|
||||
target: /coap-server.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_COAP_ADAPTER_SERVER_KEY_FILE:-ssl/certs/dummy/server_key}
|
||||
target: /coap-server${SMQ_COAP_ADAPTER_SERVER_KEY_FILE:+.key}
|
||||
target: /coap-server.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_COAP_ADAPTER_SERVER_CA_FILE:-ssl/certs/dummy/server_ca}
|
||||
target: /coap-server-ca${SMQ_COAP_ADAPTER_SERVER_CA_FILE:+.crt}
|
||||
target: /coap-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Clients gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /clients-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /clients-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /clients-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Channels gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /channels-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /channels-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca}
|
||||
target: /channels-grpc-client-ca${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:+.crt}
|
||||
target: /channels-grpc-client-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
# Domains gRPC mTLS client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
|
||||
target: /domains-grpc-client.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
|
||||
target: /domains-grpc-client.key
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
target: /domains-grpc-server-ca.crt
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
|
||||
@@ -130,5 +130,3 @@ domains:
|
||||
- check_members_exists: view_role_users_permission
|
||||
- remove_members: remove_role_users_permission
|
||||
- remove_all_members: remove_role_users_permission
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Enable AOF persistence
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# Enable periodic snapshots
|
||||
save 300 10
|
||||
|
||||
# Persist data in Docker volume
|
||||
dir /data
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
module github.com/absmach/magistrala
|
||||
module github.com/absmach/magistrala-old
|
||||
|
||||
go 1.25.5
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/0x6flab/namegenerator v1.4.0
|
||||
github.com/absmach/callhome v0.18.2
|
||||
github.com/absmach/certs v0.18.4
|
||||
github.com/absmach/supermq v0.18.5
|
||||
github.com/authzed/authzed-go v1.7.0
|
||||
github.com/absmach/certs v0.18.5
|
||||
github.com/absmach/magistrala v0.19.1
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4
|
||||
github.com/authzed/authzed-go v1.8.0
|
||||
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/caarlos0/env/v11 v11.4.0
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba
|
||||
github.com/go-chi/chi/v5 v5.2.4
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/gookit/color v1.6.0
|
||||
@@ -27,31 +28,53 @@ require (
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/redis/go-redis/v9 v9.17.2
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/rubenv/sql-migrate v1.8.1
|
||||
github.com/slack-go/slack v0.17.3
|
||||
github.com/slack-go/slack v0.19.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/traefik/yaegi v0.16.1
|
||||
github.com/vadv/gopher-lua-libs v0.8.0
|
||||
github.com/yuin/gopher-lua v1.1.1
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
|
||||
go.opentelemetry.io/otel v1.39.0
|
||||
go.opentelemetry.io/otel/trace v1.39.0
|
||||
golang.org/x/sync v0.19.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
go.opentelemetry.io/otel/trace v1.42.0
|
||||
golang.org/x/sync v0.20.0
|
||||
gonum.org/v1/gonum v0.17.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/grpc v1.79.2
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/authzed/cel-go v0.20.2 // indirect
|
||||
github.com/authzed/spicedb v1.49.2 // indirect
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-logr/zerologr v1.2.3 // indirect
|
||||
github.com/moby/moby/api v1.53.0 // indirect
|
||||
github.com/moby/moby/client v0.2.2 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect
|
||||
sigs.k8s.io/controller-runtime v0.22.4 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
@@ -63,11 +86,10 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/docker/cli v27.4.1+incompatible // indirect
|
||||
github.com/docker/docker v28.0.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/cli v29.2.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
@@ -81,43 +103,40 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jzelinskie/stringz v0.0.3 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/lib/pq v1.11.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.3.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.48.0
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nats.go v1.49.0
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runc v1.2.8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
@@ -130,20 +149,20 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.122.0 h1:0JTLGrcSIs3HIGsgVPvTx3cfyFSP/k9CI8vLPHTd6Wc=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/0x6flab/namegenerator v1.4.0 h1:QnkI813SZsI/hYnKD9pg3mkIlcYzCx0N4hnzb0YYME4=
|
||||
github.com/0x6flab/namegenerator v1.4.0/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gBdD5oAU8hd0SyY=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
@@ -24,23 +29,31 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/absmach/callhome v0.18.2 h1:dmopRHm2qTheHN1hdUKRRYpKwRrj7X9d8AWCFrb+K6s=
|
||||
github.com/absmach/callhome v0.18.2/go.mod h1:LEXKhES9JJtj3tBgTZv7VPNjOi5ukJQB0mFic0QP60Q=
|
||||
github.com/absmach/certs v0.18.4 h1:45nocGNOt+IUqJHEM/lS3XpOYWYWdyOb61R6VPbxsFA=
|
||||
github.com/absmach/certs v0.18.4/go.mod h1:ixBMssgL8e6NjN6BGiLYBIYGDe24DqgN0UnHgFJ2fNE=
|
||||
github.com/absmach/certs v0.18.5 h1:eYlvitou+LoDtt7ETVLTp6d/1xCejGL3EmVOg+rHGTU=
|
||||
github.com/absmach/certs v0.18.5/go.mod h1:31dtVe1VYF16W+IvjAE/uPAIz4f3uLHgh+moBezjqIc=
|
||||
github.com/absmach/magistrala v0.19.1 h1:WLT1B3WOOO8KL43HxZOWKnt1JJQh65uGiFhwQB4DzC8=
|
||||
github.com/absmach/magistrala v0.19.1/go.mod h1:5pNI533Mf2tN1ihDETR4lOhRdGuijmRuAhxVw7+pKCI=
|
||||
github.com/absmach/mgate v0.5.0 h1:RV2Aalra3xIm+XTs13TM7iE7v4WTL2SKhKcPbKr22Ac=
|
||||
github.com/absmach/mgate v0.5.0/go.mod h1:0KVq7mxM0wayosmyXPPxp1EL0c2d9kRp5V8NZCKdetA=
|
||||
github.com/absmach/senml v1.0.8 h1:+opem/r4g6c6eA/JLyCIuksyEhj7eBdysY3pEmy1mqo=
|
||||
github.com/absmach/senml v1.0.8/go.mod h1:DRhzHLgvQoIUHroBgpFrSWso+bJZO9E96RlHAHy+VRI=
|
||||
github.com/absmach/supermq v0.18.5 h1:mBWfhzCDWzWZ4GaiKB8y4h17qddCBKSZhlBGMsTZNHs=
|
||||
github.com/absmach/supermq v0.18.5/go.mod h1:wBPp1RSd8Rf4sC+2ljiD1aXJpgwacZVnU7WQEvGLmBU=
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 h1:533pRc6R7perWDqJuZq+ofBQfYfmyj7n49V4LFY4zpo=
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4/go.mod h1:xDAX/O3VcOsHWCx2fk85VD7FI17hAUOvoOhho7DA7g0=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/authzed/authzed-go v1.7.0 h1:mgBC1dZLRan+t6oKvrf0RMfpZx5ggXCH16OkodtAbVw=
|
||||
github.com/authzed/authzed-go v1.7.0/go.mod h1:2PVaUUQavKGsjK22dFdovY5djkeHAiByiySRwbWR8tU=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/authzed/authzed-go v1.8.0 h1:cRka8J8QXGl+nyNrhsiPSFJUluIG1tuTXnG8ad2LZ1Y=
|
||||
github.com/authzed/authzed-go v1.8.0/go.mod h1:WC3x/SuVvclBlDYMg9V7e5c/J/KGGwG+cSw2WQBbodk=
|
||||
github.com/authzed/cel-go v0.20.2 h1:GlmLecGry7Z8HU0k+hmaHHUV05ZHrsFxduXHtIePvck=
|
||||
github.com/authzed/cel-go v0.20.2/go.mod h1:pJHVFWbqUHV1J+klQoZubdKswlbxcsbojda3mye9kiU=
|
||||
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8 h1:y17oq4U8n+k1OcIGGDsjYdIdp4QywGcE7ZphIvtfEbo=
|
||||
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8/go.mod h1:Pf1ZSi41EePvx1GC1DeEJw5dn35iUcxZHqpHuG1Rpic=
|
||||
github.com/authzed/spicedb v1.49.2 h1:6LKOxiNN7K18x4xs2NB0dhnDNDypbpuOP7s06AwjCH8=
|
||||
github.com/authzed/spicedb v1.49.2/go.mod h1:I9t8PtFBxUHsSZKfrkK6bxbMy8La7LYjkpgw6UpNHQs=
|
||||
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI=
|
||||
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
@@ -53,10 +66,12 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc=
|
||||
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/cbroglie/mustache v1.0.1 h1:ivMg8MguXq/rrz2eu3tw6g3b16+PQhoTn6EZAhst2mw=
|
||||
github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
@@ -76,32 +91,37 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
|
||||
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
|
||||
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
@@ -109,6 +129,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -129,8 +151,10 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
|
||||
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -150,22 +174,26 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
|
||||
github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -191,6 +219,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
@@ -206,8 +236,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
@@ -289,8 +319,10 @@ github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfU
|
||||
github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -321,12 +353,14 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
@@ -334,6 +368,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
@@ -347,10 +383,14 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
|
||||
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
|
||||
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -360,18 +400,22 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
|
||||
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
|
||||
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@@ -382,15 +426,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
|
||||
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
@@ -414,8 +457,8 @@ github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvM
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
@@ -424,22 +467,23 @@ github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
|
||||
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
@@ -452,8 +496,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/slack-go/slack v0.19.0 h1:J8lL/nGTsIUX53HU8YxZeI3PDkA+sxZsFrI2Dew7h44=
|
||||
github.com/slack-go/slack v0.19.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE=
|
||||
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
|
||||
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
@@ -473,6 +517,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/sqids/sqids-go v0.4.1 h1:eQKYzmAZbLlRwHeHYPF35QhgxwZHLnlmVj9AkIj/rrw=
|
||||
github.com/sqids/sqids-go v0.4.1/go.mod h1:EMwHuPQgSNFS0A49jESTfIQS+066XQTVhukrzEPScl8=
|
||||
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
|
||||
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
@@ -518,27 +564,29 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
@@ -546,6 +594,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
@@ -575,8 +625,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw=
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
@@ -590,6 +640,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -609,12 +661,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -624,8 +676,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -655,12 +707,14 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -676,8 +730,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
@@ -698,6 +752,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -711,17 +767,17 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -757,10 +813,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
|
||||
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
|
||||
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package policies
|
||||
|
||||
const (
|
||||
RulesType = "rules"
|
||||
ReportsType = "reports"
|
||||
AlarmsType = "alarms"
|
||||
)
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
|
||||
)
|
||||
|
||||
var (
|
||||
errDecodeAddRuleEvent = errors.New("failed to decode rule add event")
|
||||
errDecodeUpdateRuleEvent = errors.New("failed to decode rule update event")
|
||||
errDecodeUpdateRuleTagsEvent = errors.New("failed to decode rule update tags event")
|
||||
errDecodeUpdateRuleScheduleEvent = errors.New("failed to decode rule update schedule event")
|
||||
errDecodeEnableRuleEvent = errors.New("failed to decode rule enable event")
|
||||
errDecodeDisableRuleEvent = errors.New("failed to decode rule disable event")
|
||||
errDecodeRemoveRuleEvent = errors.New("failed to decode rule remove event")
|
||||
|
||||
errID = errors.New("missing or invalid 'id'")
|
||||
errName = errors.New("missing or invalid 'name'")
|
||||
errTags = errors.New("invalid 'tags'")
|
||||
errStatus = errors.New("missing or invalid 'status'")
|
||||
errConvertStatus = errors.New("failed to convert status")
|
||||
errCreatedBy = errors.New("missing or invalid 'created_by'")
|
||||
errCreatedAt = errors.New("failed to parse 'created_at' time")
|
||||
errUpdatedAt = errors.New("failed to parse 'updated_at' time")
|
||||
errDecodeLogic = errors.New("failed to decode 'logic'")
|
||||
errDecodeSchedule = errors.New("failed to decode 'schedule'")
|
||||
)
|
||||
|
||||
// ToRule decodes a map[string]any event payload into a re.Rule.
|
||||
func ToRule(data map[string]any) (re.Rule, error) {
|
||||
var r re.Rule
|
||||
|
||||
id, ok := data["id"].(string)
|
||||
if !ok {
|
||||
return re.Rule{}, errID
|
||||
}
|
||||
r.ID = id
|
||||
|
||||
name, ok := data["name"].(string)
|
||||
if !ok {
|
||||
return re.Rule{}, errName
|
||||
}
|
||||
r.Name = name
|
||||
|
||||
stat, ok := data["status"].(string)
|
||||
if !ok {
|
||||
return re.Rule{}, errStatus
|
||||
}
|
||||
st, err := re.ToStatus(stat)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errConvertStatus, err)
|
||||
}
|
||||
r.Status = st
|
||||
|
||||
cby, ok := data["created_by"].(string)
|
||||
if !ok {
|
||||
return re.Rule{}, errCreatedBy
|
||||
}
|
||||
r.CreatedBy = cby
|
||||
|
||||
cat, ok := data["created_at"].(string)
|
||||
if !ok {
|
||||
return re.Rule{}, errCreatedAt
|
||||
}
|
||||
ct, err := time.Parse(re.TimeLayout, cat)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errCreatedAt, err)
|
||||
}
|
||||
r.CreatedAt = ct
|
||||
|
||||
if domain, ok := data["domain"].(string); ok {
|
||||
r.DomainID = domain
|
||||
}
|
||||
|
||||
if itags, ok := data["tags"].([]any); ok {
|
||||
tags, err := rconsumer.ToStrings(itags)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errTags, err)
|
||||
}
|
||||
r.Tags = tags
|
||||
}
|
||||
|
||||
if meta, ok := data["metadata"].(map[string]any); ok {
|
||||
r.Metadata = meta
|
||||
}
|
||||
|
||||
if uby, ok := data["updated_by"].(string); ok {
|
||||
r.UpdatedBy = uby
|
||||
}
|
||||
|
||||
if uat, ok := data["updated_at"].(string); ok {
|
||||
ut, err := time.Parse(re.TimeLayout, uat)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errUpdatedAt, err)
|
||||
}
|
||||
r.UpdatedAt = ut
|
||||
}
|
||||
|
||||
if ic, ok := data["input_channel"].(string); ok {
|
||||
r.InputChannel = ic
|
||||
}
|
||||
|
||||
if it, ok := data["input_topic"].(string); ok {
|
||||
r.InputTopic = it
|
||||
}
|
||||
|
||||
if rawLogic, ok := data["logic"].(map[string]any); ok {
|
||||
b, err := json.Marshal(rawLogic)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
|
||||
}
|
||||
if err := json.Unmarshal(b, &r.Logic); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
|
||||
}
|
||||
}
|
||||
|
||||
if rawSched, ok := data["schedule"].(map[string]any); ok {
|
||||
b, err := json.Marshal(rawSched)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
|
||||
}
|
||||
var sched schedule.Schedule
|
||||
if err := json.Unmarshal(b, &sched); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
|
||||
}
|
||||
r.Schedule = sched
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeAddRuleEvent(data map[string]any) (re.Rule, []roles.RoleProvision, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
|
||||
}
|
||||
|
||||
var rps []roles.RoleProvision
|
||||
if irps, ok := data["roles_provisioned"].([]any); ok {
|
||||
rps, err = rconsumer.ToRoleProvisions(irps)
|
||||
if err != nil {
|
||||
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
|
||||
}
|
||||
}
|
||||
|
||||
return r, rps, nil
|
||||
}
|
||||
|
||||
func decodeUpdateRuleEvent(data map[string]any) (re.Rule, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleEvent, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeUpdateRuleTagsEvent(data map[string]any) (re.Rule, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleTagsEvent, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeUpdateRuleScheduleEvent(data map[string]any) (re.Rule, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleScheduleEvent, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeEnableRuleEvent(data map[string]any) (re.Rule, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeEnableRuleEvent, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeDisableRuleEvent(data map[string]any) (re.Rule, error) {
|
||||
r, err := ToRule(data)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDecodeDisableRuleEvent, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func decodeRemoveRuleEvent(data map[string]any) (string, error) {
|
||||
id, ok := data["id"].(string)
|
||||
if !ok {
|
||||
return "", errors.Wrap(errDecodeRemoveRuleEvent, errID)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package consumer contains events consumer for events
|
||||
// published by the Rules Engine service.
|
||||
package consumer
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
"github.com/absmach/supermq/pkg/events/store"
|
||||
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
|
||||
)
|
||||
|
||||
const (
|
||||
stream = "events.supermq.rule.*"
|
||||
|
||||
create = "rule.create"
|
||||
update = "rule.update"
|
||||
updateTags = "rule.update_tags"
|
||||
updateSchedule = "rule.update_schedule"
|
||||
enable = "rule.enable"
|
||||
disable = "rule.disable"
|
||||
remove = "rule.remove"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoOperationKey = errors.New("operation key is not found in event message")
|
||||
errAddRuleEvent = errors.New("failed to consume rule create event")
|
||||
errUpdateRuleEvent = errors.New("failed to consume rule update event")
|
||||
errUpdateRuleTagsEvent = errors.New("failed to consume rule update tags event")
|
||||
errUpdateRuleScheduleEvent = errors.New("failed to consume rule update schedule event")
|
||||
errEnableRuleEvent = errors.New("failed to consume rule enable event")
|
||||
errDisableRuleEvent = errors.New("failed to consume rule disable event")
|
||||
errRemoveRuleEvent = errors.New("failed to consume rule remove event")
|
||||
)
|
||||
|
||||
type eventHandler struct {
|
||||
repo re.Repository
|
||||
rolesEventHandler rconsumer.EventHandler
|
||||
}
|
||||
|
||||
func RulesEventsSubscribe(ctx context.Context, repo re.Repository, esURL, esConsumerName string, logger *slog.Logger) error {
|
||||
subscriber, err := store.NewSubscriber(ctx, esURL, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subConfig := events.SubscriberConfig{
|
||||
Stream: stream,
|
||||
Consumer: esConsumerName,
|
||||
Handler: NewEventHandler(repo),
|
||||
Ordered: true,
|
||||
}
|
||||
return subscriber.Subscribe(ctx, subConfig)
|
||||
}
|
||||
|
||||
// NewEventHandler returns new event store handler.
|
||||
func NewEventHandler(repo re.Repository) events.EventHandler {
|
||||
reh := rconsumer.NewEventHandler("rule", repo)
|
||||
return &eventHandler{
|
||||
repo: repo,
|
||||
rolesEventHandler: reh,
|
||||
}
|
||||
}
|
||||
|
||||
func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
|
||||
msg, err := event.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op, ok := msg["operation"]
|
||||
if !ok {
|
||||
return errNoOperationKey
|
||||
}
|
||||
|
||||
switch op {
|
||||
case create:
|
||||
return es.addRuleHandler(ctx, msg)
|
||||
case update:
|
||||
return es.updateRuleHandler(ctx, msg)
|
||||
case updateTags:
|
||||
return es.updateRuleTagsHandler(ctx, msg)
|
||||
case updateSchedule:
|
||||
return es.updateRuleScheduleHandler(ctx, msg)
|
||||
case enable:
|
||||
return es.enableRuleHandler(ctx, msg)
|
||||
case disable:
|
||||
return es.disableRuleHandler(ctx, msg)
|
||||
case remove:
|
||||
return es.removeRuleHandler(ctx, msg)
|
||||
}
|
||||
|
||||
return es.rolesEventHandler.Handle(ctx, op, msg)
|
||||
}
|
||||
|
||||
func (es *eventHandler) addRuleHandler(ctx context.Context, data map[string]any) error {
|
||||
r, rps, err := decodeAddRuleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errAddRuleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.AddRule(ctx, r); err != nil {
|
||||
return errors.Wrap(errAddRuleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.AddRoles(ctx, rps); err != nil {
|
||||
return errors.Wrap(errAddRuleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) updateRuleHandler(ctx context.Context, data map[string]any) error {
|
||||
r, err := decodeUpdateRuleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errUpdateRuleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.UpdateRule(ctx, r); err != nil {
|
||||
return errors.Wrap(errUpdateRuleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) updateRuleTagsHandler(ctx context.Context, data map[string]any) error {
|
||||
r, err := decodeUpdateRuleTagsEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errUpdateRuleTagsEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.UpdateRuleTags(ctx, r); err != nil {
|
||||
return errors.Wrap(errUpdateRuleTagsEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) updateRuleScheduleHandler(ctx context.Context, data map[string]any) error {
|
||||
r, err := decodeUpdateRuleScheduleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errUpdateRuleScheduleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.UpdateRuleSchedule(ctx, r); err != nil {
|
||||
return errors.Wrap(errUpdateRuleScheduleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) enableRuleHandler(ctx context.Context, data map[string]any) error {
|
||||
r, err := decodeEnableRuleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errEnableRuleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
|
||||
return errors.Wrap(errEnableRuleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) disableRuleHandler(ctx context.Context, data map[string]any) error {
|
||||
r, err := decodeDisableRuleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errDisableRuleEvent, err)
|
||||
}
|
||||
|
||||
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
|
||||
return errors.Wrap(errDisableRuleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *eventHandler) removeRuleHandler(ctx context.Context, data map[string]any) error {
|
||||
id, err := decodeRemoveRuleEvent(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(errRemoveRuleEvent, err)
|
||||
}
|
||||
|
||||
if err := es.repo.RemoveRule(ctx, id); err != nil {
|
||||
return errors.Wrap(errRemoveRuleEvent, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
)
|
||||
|
||||
const alarmsEndpoint = "alarms"
|
||||
|
||||
// Alarm represents an alarm instance.
|
||||
type Alarm struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
RuleID string `json:"rule_id,omitempty"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Subtopic string `json:"subtopic,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Measurement string `json:"measurement,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Unit string `json:"unit,omitempty"`
|
||||
Threshold string `json:"threshold,omitempty"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
Severity uint8 `json:"severity,omitempty"`
|
||||
AssigneeID string `json:"assignee_id,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
AssignedAt time.Time `json:"assigned_at,omitempty"`
|
||||
AssignedBy string `json:"assigned_by,omitempty"`
|
||||
AcknowledgedAt time.Time `json:"acknowledged_at,omitempty"`
|
||||
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
|
||||
ResolvedAt time.Time `json:"resolved_at,omitempty"`
|
||||
ResolvedBy string `json:"resolved_by,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type AlarmsPage struct {
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Total uint64 `json:"total"`
|
||||
Alarms []Alarm `json:"alarms"`
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError) {
|
||||
data, err := json.Marshal(alarm)
|
||||
if err != nil {
|
||||
return Alarm{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, alarm.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Alarm{}, sdkerr
|
||||
}
|
||||
|
||||
var a Alarm
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Alarm{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Alarm{}, sdkerr
|
||||
}
|
||||
|
||||
var a Alarm
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Alarm{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError) {
|
||||
endpoint := fmt.Sprintf("%s/%s", domainID, alarmsEndpoint)
|
||||
url, err := sdk.withQueryParams(sdk.alarmsURL, endpoint, pm)
|
||||
if err != nil {
|
||||
return AlarmsPage{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return AlarmsPage{}, sdkerr
|
||||
}
|
||||
|
||||
var ap AlarmsPage
|
||||
if err := json.Unmarshal(body, &ap); err != nil {
|
||||
return AlarmsPage{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
|
||||
|
||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
|
||||
return sdkerr
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/api"
|
||||
amocks "github.com/absmach/magistrala/alarms/mocks"
|
||||
"github.com/absmach/magistrala/pkg/sdk"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const alarmID = "alarm-1"
|
||||
|
||||
var testAlarm = sdk.Alarm{
|
||||
ID: alarmID,
|
||||
RuleID: "rule-1",
|
||||
DomainID: domainID,
|
||||
ChannelID: "chan-1",
|
||||
ClientID: "client-1",
|
||||
Subtopic: "subtopic",
|
||||
Status: "active",
|
||||
Measurement: "temperature",
|
||||
Value: "30.5",
|
||||
Unit: "C",
|
||||
Threshold: "25",
|
||||
Cause: "threshold_exceeded",
|
||||
Severity: 80,
|
||||
AssigneeID: "user-1",
|
||||
Metadata: sdk.Metadata{"key": "value"},
|
||||
}
|
||||
|
||||
func setupAlarms() (*httptest.Server, *amocks.Service, *authnmocks.Authentication) {
|
||||
asvc := new(amocks.Service)
|
||||
logger := smqlog.NewMock()
|
||||
authn := new(authnmocks.Authentication)
|
||||
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
|
||||
idp := uuid.NewMock()
|
||||
mux := api.MakeHandler(asvc, logger, idp, "", am)
|
||||
return httptest.NewServer(mux), asvc, authn
|
||||
}
|
||||
|
||||
func TestUpdateAlarm(t *testing.T) {
|
||||
as, asvc, auth := setupAlarms()
|
||||
defer as.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
AlarmsURL: as.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
updated := testAlarm
|
||||
updated.Status = "cleared"
|
||||
|
||||
svcAlarm := alarms.Alarm{
|
||||
ID: alarmID,
|
||||
RuleID: "rule-1",
|
||||
DomainID: domainID,
|
||||
ChannelID: "chan-1",
|
||||
ClientID: "client-1",
|
||||
Subtopic: "subtopic",
|
||||
Status: alarms.ClearedStatus,
|
||||
Measurement: "temperature",
|
||||
Value: "30.5",
|
||||
Unit: "C",
|
||||
Threshold: "25",
|
||||
Cause: "threshold_exceeded",
|
||||
Severity: 80,
|
||||
AssigneeID: "user-1",
|
||||
Metadata: alarms.Metadata{"key": "value"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm sdk.Alarm
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes alarms.Alarm
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
resp sdk.Alarm
|
||||
}{
|
||||
{
|
||||
desc: "update alarm successfully",
|
||||
alarm: updated,
|
||||
token: validToken,
|
||||
svcRes: svcAlarm,
|
||||
resp: testAlarm,
|
||||
},
|
||||
{
|
||||
desc: "update alarm with empty token",
|
||||
alarm: updated,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "update non-existent alarm",
|
||||
alarm: sdk.Alarm{ID: "non-existent"},
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := asvc.On("UpdateAlarm", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateAlarm(context.Background(), tc.alarm, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewAlarm(t *testing.T) {
|
||||
as, asvc, auth := setupAlarms()
|
||||
defer as.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
AlarmsURL: as.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcAlarm := alarms.Alarm{
|
||||
ID: alarmID,
|
||||
RuleID: "rule-1",
|
||||
DomainID: domainID,
|
||||
ChannelID: "chan-1",
|
||||
ClientID: "client-1",
|
||||
Subtopic: "subtopic",
|
||||
Status: alarms.ActiveStatus,
|
||||
Measurement: "temperature",
|
||||
Value: "30.5",
|
||||
Unit: "C",
|
||||
Threshold: "25",
|
||||
Cause: "threshold_exceeded",
|
||||
Severity: 80,
|
||||
AssigneeID: "user-1",
|
||||
Metadata: alarms.Metadata{"key": "value"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes alarms.Alarm
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "view alarm successfully",
|
||||
id: alarmID,
|
||||
token: validToken,
|
||||
svcRes: svcAlarm,
|
||||
},
|
||||
{
|
||||
desc: "view alarm with empty token",
|
||||
id: alarmID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "view non-existent alarm",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := asvc.On("ViewAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ViewAlarm(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAlarms(t *testing.T) {
|
||||
as, asvc, auth := setupAlarms()
|
||||
defer as.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
AlarmsURL: as.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcAlarm := alarms.Alarm{
|
||||
ID: alarmID,
|
||||
RuleID: "rule-1",
|
||||
DomainID: domainID,
|
||||
ChannelID: "chan-1",
|
||||
ClientID: "client-1",
|
||||
Subtopic: "subtopic",
|
||||
Status: alarms.ActiveStatus,
|
||||
Measurement: "temperature",
|
||||
Value: "30.5",
|
||||
Unit: "C",
|
||||
Threshold: "25",
|
||||
Cause: "threshold_exceeded",
|
||||
Severity: 80,
|
||||
AssigneeID: "user-1",
|
||||
Metadata: alarms.Metadata{"key": "value"},
|
||||
}
|
||||
|
||||
svcAlarmsPage := alarms.AlarmsPage{
|
||||
Total: 2,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Alarms: []alarms.Alarm{svcAlarm},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
pm sdk.PageMetadata
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes alarms.AlarmsPage
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "list alarms successfully",
|
||||
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
|
||||
token: validToken,
|
||||
svcRes: svcAlarmsPage,
|
||||
},
|
||||
{
|
||||
desc: "list alarms with status and entity filters",
|
||||
pm: sdk.PageMetadata{
|
||||
Limit: 5,
|
||||
Status: "active",
|
||||
ChannelID: "chan-1",
|
||||
ClientID: "client-1",
|
||||
RuleID: "rule-1",
|
||||
AssigneeID: "user-1",
|
||||
Severity: 80,
|
||||
},
|
||||
token: validToken,
|
||||
svcRes: svcAlarmsPage,
|
||||
},
|
||||
{
|
||||
desc: "list alarms with time range and sorting",
|
||||
pm: sdk.PageMetadata{
|
||||
Limit: 10,
|
||||
CreatedFrom: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
CreatedTo: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
Order: "created_at",
|
||||
Dir: "asc",
|
||||
},
|
||||
token: validToken,
|
||||
svcRes: svcAlarmsPage,
|
||||
},
|
||||
{
|
||||
desc: "list alarms with actor filters",
|
||||
pm: sdk.PageMetadata{
|
||||
Limit: 10,
|
||||
UpdatedBy: "user-2",
|
||||
AssignedBy: "user-3",
|
||||
AcknowledgedBy: "user-4",
|
||||
ResolvedBy: "user-5",
|
||||
Subtopic: "subtopic-1",
|
||||
},
|
||||
token: validToken,
|
||||
svcRes: svcAlarmsPage,
|
||||
},
|
||||
{
|
||||
desc: "list alarms with empty metadata excludes severity",
|
||||
pm: sdk.PageMetadata{},
|
||||
token: validToken,
|
||||
svcRes: alarms.AlarmsPage{},
|
||||
},
|
||||
{
|
||||
desc: "list alarms with zero severity excluded",
|
||||
pm: sdk.PageMetadata{Status: "active", Severity: 0},
|
||||
token: validToken,
|
||||
svcRes: alarms.AlarmsPage{},
|
||||
},
|
||||
{
|
||||
desc: "list alarms with empty token",
|
||||
pm: sdk.PageMetadata{Limit: 10},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := asvc.On("ListAlarms", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ListAlarms(context.Background(), tc.pm, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.Equal(t, tc.svcRes.Total, result.Total)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAlarm(t *testing.T) {
|
||||
as, asvc, auth := setupAlarms()
|
||||
defer as.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
AlarmsURL: as.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "delete alarm successfully",
|
||||
id: alarmID,
|
||||
token: validToken,
|
||||
},
|
||||
{
|
||||
desc: "delete alarm with empty token",
|
||||
id: alarmID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "delete non-existent alarm",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := asvc.On("DeleteAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
err := mgsdk.DeleteAlarm(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,302 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
reportsEndpoint = "reports"
|
||||
configsEndpointReports = "configs"
|
||||
)
|
||||
|
||||
// ReportConfig represents a report configuration.
|
||||
type ReportConfig struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Schedule any `json:"schedule,omitempty"`
|
||||
Config any `json:"config,omitempty"`
|
||||
Email any `json:"email,omitempty"`
|
||||
Metrics any `json:"metrics,omitempty"`
|
||||
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
type ReportTemplate any
|
||||
|
||||
type ReportFile struct {
|
||||
Name string
|
||||
Format string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ReportPage struct {
|
||||
Total uint64 `json:"total"`
|
||||
From time.Time `json:"from,omitempty"`
|
||||
To time.Time `json:"to,omitempty"`
|
||||
Aggregation any `json:"aggregation,omitempty"`
|
||||
Reports any `json:"reports,omitempty"`
|
||||
File any `json:"file,omitempty"`
|
||||
}
|
||||
|
||||
type ReportConfigPage struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
ReportConfigs []ReportConfig `json:"report_configs"`
|
||||
}
|
||||
|
||||
type ReportAction string
|
||||
|
||||
const (
|
||||
ViewReportAction ReportAction = "view"
|
||||
DownloadReportAction ReportAction = "download"
|
||||
EmailReportAction ReportAction = "email"
|
||||
)
|
||||
|
||||
func (sdk mgSDK) AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
data, err := json.Marshal(map[string]any{"schedule": cfg.Schedule})
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/schedule", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
|
||||
return sdkerr
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError) {
|
||||
endpoint := fmt.Sprintf("%s/%s/%s", domainID, reportsEndpoint, configsEndpointReports)
|
||||
url, err := sdk.withQueryParams(sdk.reportsURL, endpoint, pm)
|
||||
if err != nil {
|
||||
return ReportConfigPage{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfigPage{}, sdkerr
|
||||
}
|
||||
|
||||
var rcp ReportConfigPage
|
||||
if err := json.Unmarshal(body, &rcp); err != nil {
|
||||
return ReportConfigPage{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rcp, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/enable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/disable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return ReportConfig{}, sdkerr
|
||||
}
|
||||
|
||||
var rc ReportConfig
|
||||
if err := json.Unmarshal(body, &rc); err != nil {
|
||||
return ReportConfig{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError {
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
|
||||
|
||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusNoContent)
|
||||
return sdkerr
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return "", sdkerr
|
||||
}
|
||||
|
||||
var rt ReportTemplate
|
||||
if err := json.Unmarshal(body, &rt); err != nil {
|
||||
return "", errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
|
||||
|
||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
|
||||
return sdkerr
|
||||
}
|
||||
|
||||
func (sdk mgSDK) GenerateReport(
|
||||
ctx context.Context,
|
||||
config ReportConfig,
|
||||
action ReportAction,
|
||||
domainID,
|
||||
token string,
|
||||
) (ReportPage, *ReportFile, errors.SDKError) {
|
||||
data, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return ReportPage{}, nil, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s?action=%s",
|
||||
sdk.reportsURL,
|
||||
domainID,
|
||||
reportsEndpoint,
|
||||
action,
|
||||
)
|
||||
|
||||
headers, body, sdkerr := sdk.processRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
url,
|
||||
token,
|
||||
data,
|
||||
nil,
|
||||
http.StatusOK,
|
||||
)
|
||||
if sdkerr != nil {
|
||||
return ReportPage{}, nil, sdkerr
|
||||
}
|
||||
|
||||
// ✅ Handle Download Action
|
||||
if action == DownloadReportAction {
|
||||
file := &ReportFile{
|
||||
Name: extractFilename(headers.Get("Content-Disposition")),
|
||||
Format: "pdf",
|
||||
Data: body,
|
||||
}
|
||||
return ReportPage{}, file, nil
|
||||
}
|
||||
|
||||
// ✅ Handle JSON response (view/email)
|
||||
var rp ReportPage
|
||||
if err := json.Unmarshal(body, &rp); err != nil {
|
||||
return ReportPage{}, nil, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return rp, nil, nil
|
||||
}
|
||||
|
||||
func extractFilename(contentDisposition string) string {
|
||||
const prefix = "filename="
|
||||
if idx := strings.Index(contentDisposition, prefix); idx != -1 {
|
||||
return strings.Trim(contentDisposition[idx+len(prefix):], `"`)
|
||||
}
|
||||
return "report"
|
||||
}
|
||||
@@ -0,0 +1,867 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pkgSch "github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/magistrala/reports/api"
|
||||
rmocks "github.com/absmach/magistrala/reports/mocks"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
reportConfigID = "report-config-1"
|
||||
name = "daily-report"
|
||||
updatedName = "updated daily-report"
|
||||
description = "Daily temperature report"
|
||||
updatedDescription = "updated Daily temperature report"
|
||||
validTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{$.Title}}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.header { background-color: #f0f0f0; padding: 10px; }
|
||||
.content { padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>{{$.Title}}</h1>
|
||||
<p>Generated on: {{$.GeneratedDate}}</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Messages</h2>
|
||||
{{range .Messages}}
|
||||
<div class="message">
|
||||
<p>Time: {{formatTime .Time}}</p>
|
||||
<p>Value: {{formatValue .}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
)
|
||||
|
||||
var (
|
||||
now = time.Now().UTC().Truncate(time.Minute)
|
||||
future = now.Add(1 * time.Hour)
|
||||
schedule = pkgSch.Schedule{
|
||||
StartDateTime: future,
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: future,
|
||||
}
|
||||
metrics = []reports.ReqMetric{
|
||||
{
|
||||
ChannelID: "channel1",
|
||||
ClientIDs: []string{"client1"},
|
||||
Name: "metric_name",
|
||||
},
|
||||
}
|
||||
config = reports.MetricConfig{
|
||||
From: "now()-1h",
|
||||
To: "now()",
|
||||
Title: "test_title",
|
||||
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
|
||||
}
|
||||
email = reports.EmailSetting{
|
||||
To: []string{"test@example.com"},
|
||||
Subject: "Test Report",
|
||||
}
|
||||
|
||||
testReportConfig = sdk.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
DomainID: domainID,
|
||||
Status: "enabled",
|
||||
Schedule: schedule,
|
||||
Metrics: metrics,
|
||||
Config: &config,
|
||||
Email: &email,
|
||||
}
|
||||
)
|
||||
|
||||
func setupReports() (*httptest.Server, *rmocks.Service, *authnmocks.Authentication) {
|
||||
rsvc := new(rmocks.Service)
|
||||
log := smqlog.NewMock()
|
||||
authn := new(authnmocks.Authentication)
|
||||
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
|
||||
mux := chi.NewRouter()
|
||||
_ = api.MakeHandler(rsvc, am, mux, log, "")
|
||||
return httptest.NewServer(mux), rsvc, authn
|
||||
}
|
||||
|
||||
func TestAddReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: "daily-report",
|
||||
Description: "Daily temperature report",
|
||||
DomainID: domainID,
|
||||
Status: reports.EnabledStatus,
|
||||
Schedule: schedule,
|
||||
Metrics: []reports.ReqMetric{
|
||||
{
|
||||
ChannelID: "channel1",
|
||||
ClientIDs: []string{"client1"},
|
||||
Name: "metric_name",
|
||||
},
|
||||
},
|
||||
Config: &reports.MetricConfig{
|
||||
From: "now()-1h",
|
||||
To: "now()",
|
||||
Title: "test_title",
|
||||
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
|
||||
},
|
||||
Email: &reports.EmailSetting{
|
||||
To: []string{"test@example.com"},
|
||||
Subject: "Test Report",
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg sdk.ReportConfig
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "add report config successfully",
|
||||
cfg: testReportConfig,
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "add report config with empty token",
|
||||
cfg: sdk.ReportConfig{Name: "daily-report"},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
svcErr: errors.New("missing or invalid bearer user token"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("AddReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.AddReportConfig(context.Background(), tc.cfg, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
DomainID: domainID,
|
||||
Status: reports.EnabledStatus,
|
||||
Metrics: metrics,
|
||||
Config: &config,
|
||||
Email: &email,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "view report config successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "view report config with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "view non-existent report config",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("ViewReportConfig", mock.Anything, tc.session, tc.id, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ViewReportConfig(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
updatedConfig := testReportConfig
|
||||
updatedConfig.Name = updatedName
|
||||
updatedConfig.Description = updatedDescription
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: updatedName,
|
||||
Description: updatedDescription,
|
||||
DomainID: domainID,
|
||||
Status: reports.EnabledStatus,
|
||||
Metrics: metrics,
|
||||
Config: &config,
|
||||
Email: &email,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg sdk.ReportConfig
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update report config successfully",
|
||||
cfg: updatedConfig,
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "update report config with empty token",
|
||||
cfg: sdk.ReportConfig{ID: reportConfigID, Name: "updated-report"},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateReportConfig(context.Background(), tc.cfg, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportSchedule(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: name,
|
||||
Status: reports.EnabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg sdk.ReportConfig
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update report schedule successfully",
|
||||
cfg: sdk.ReportConfig{ID: reportConfigID, Schedule: map[string]any{"cron": "0 9 * * *"}},
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "update report schedule with empty token",
|
||||
cfg: sdk.ReportConfig{ID: reportConfigID},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateReportSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateReportSchedule(context.Background(), tc.cfg, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "remove report config successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
},
|
||||
{
|
||||
desc: "remove report config with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "remove non-existent report config",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
err := mgsdk.RemoveReportConfig(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReportsConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcPage := reports.ReportConfigPage{}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
pm sdk.PageMetadata
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfigPage
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "list reports config successfully",
|
||||
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with filters",
|
||||
pm: sdk.PageMetadata{
|
||||
Limit: 10,
|
||||
Name: "daily",
|
||||
Status: "enabled",
|
||||
Dir: "desc",
|
||||
Order: "created_at",
|
||||
},
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with empty metadata excludes filter params",
|
||||
pm: sdk.PageMetadata{},
|
||||
token: validToken,
|
||||
svcRes: reports.ReportConfigPage{},
|
||||
},
|
||||
{
|
||||
desc: "list reports config with empty token",
|
||||
pm: sdk.PageMetadata{Limit: 10},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ListReportsConfig(context.Background(), tc.pm, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Status: reports.EnabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "enable report config successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.EnableReportConfig(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableReportConfig(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcCfg := reports.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Status: reports.DisabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "disable report config successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
svcRes: svcCfg,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.DisableReportConfig(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportTemplate(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg sdk.ReportConfig
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update report template successfully",
|
||||
cfg: sdk.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
ReportTemplate: validTemplate,
|
||||
},
|
||||
token: validToken,
|
||||
},
|
||||
{
|
||||
desc: "update report template with empty token",
|
||||
cfg: sdk.ReportConfig{ID: reportConfigID},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateReportTemplate", mock.Anything, tc.session, mock.Anything).Return(tc.svcErr)
|
||||
err := mgsdk.UpdateReportTemplate(context.Background(), tc.cfg, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportTemplate(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcTmpl := reports.ReportTemplate(validTemplate)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportTemplate
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "view report template successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
svcRes: svcTmpl,
|
||||
},
|
||||
{
|
||||
desc: "view report template with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("ViewReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ViewReportTemplate(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteReportTemplate(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "delete report template successfully",
|
||||
id: reportConfigID,
|
||||
token: validToken,
|
||||
},
|
||||
{
|
||||
desc: "delete report template with empty token",
|
||||
id: reportConfigID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("DeleteReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
err := mgsdk.DeleteReportTemplate(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateReport(t *testing.T) {
|
||||
rs, rsvc, auth := setupReports()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ReportsURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcPage := reports.ReportPage{}
|
||||
|
||||
config := sdk.ReportConfig{
|
||||
ID: reportConfigID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
DomainID: domainID,
|
||||
Metrics: metrics,
|
||||
Config: &config,
|
||||
ReportTemplate: reports.ReportTemplate(validTemplate),
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg sdk.ReportConfig
|
||||
action sdk.ReportAction
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes reports.ReportPage
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "generate report successfully",
|
||||
cfg: config,
|
||||
action: sdk.ViewReportAction,
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "generate report with download action",
|
||||
cfg: config,
|
||||
action: sdk.DownloadReportAction,
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "generate report with empty token",
|
||||
cfg: config,
|
||||
action: sdk.ViewReportAction,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{
|
||||
DomainUserID: domainID + "_" + validID,
|
||||
UserID: validID,
|
||||
DomainID: domainID,
|
||||
}
|
||||
}
|
||||
|
||||
authCall := auth.On(
|
||||
"Authenticate",
|
||||
mock.Anything,
|
||||
tc.token,
|
||||
).Return(tc.session, tc.authenticateErr)
|
||||
|
||||
svcCall := rsvc.On(
|
||||
"GenerateReport",
|
||||
mock.Anything,
|
||||
tc.session,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(tc.svcRes, tc.svcErr)
|
||||
|
||||
page, file, err := mgsdk.GenerateReport(
|
||||
context.Background(),
|
||||
tc.cfg,
|
||||
tc.action,
|
||||
domainID,
|
||||
tc.token,
|
||||
)
|
||||
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
|
||||
if !tc.wantErr {
|
||||
if tc.action == sdk.DownloadReportAction {
|
||||
// download should return file
|
||||
assert.NotNil(t, file)
|
||||
} else {
|
||||
// view/email should return page
|
||||
assert.Equal(t, tc.svcRes.Total, page.Total)
|
||||
}
|
||||
}
|
||||
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
)
|
||||
|
||||
const rulesEndpoint = "rules"
|
||||
|
||||
// Rule represents a rule configuration.
|
||||
type Rule struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DomainID string `json:"domain,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
InputChannel string `json:"input_channel,omitempty"`
|
||||
InputTopic string `json:"input_topic,omitempty"`
|
||||
Logic any `json:"logic,omitempty"`
|
||||
Outputs any `json:"outputs,omitempty"`
|
||||
Schedule any `json:"schedule,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Total uint64 `json:"total"`
|
||||
Rules []Rule `json:"rules"`
|
||||
}
|
||||
|
||||
func (sdk mgSDK) AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
|
||||
data, err := json.Marshal(map[string]any{"tags": r.Tags})
|
||||
if err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/tags", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
|
||||
data, err := json.Marshal(map[string]any{"schedule": r.Schedule})
|
||||
if err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/schedule", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError) {
|
||||
endpoint := fmt.Sprintf("%s/%s", domainID, rulesEndpoint)
|
||||
url, err := sdk.withQueryParams(sdk.rulesEngineURL, endpoint, pm)
|
||||
if err != nil {
|
||||
return Page{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Page{}, sdkerr
|
||||
}
|
||||
|
||||
var ap Page
|
||||
if err := json.Unmarshal(body, &ap); err != nil {
|
||||
return Page{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
|
||||
|
||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
|
||||
return sdkerr
|
||||
}
|
||||
|
||||
func (sdk mgSDK) EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/enable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/disable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
|
||||
|
||||
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return Rule{}, sdkerr
|
||||
}
|
||||
|
||||
var a Rule
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return Rule{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/magistrala/re/api"
|
||||
remocks "github.com/absmach/magistrala/re/mocks"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const ruleID = "rule-1"
|
||||
|
||||
var testRule = sdk.Rule{
|
||||
ID: ruleID,
|
||||
Name: "temperature-rule",
|
||||
InputChannel: "chan-1",
|
||||
InputTopic: "sensors/temperature",
|
||||
Status: "enabled",
|
||||
Tags: []string{"temperature", "alerts"},
|
||||
}
|
||||
|
||||
func setupRules() (*httptest.Server, *remocks.Service, *authnmocks.Authentication) {
|
||||
rsvc := new(remocks.Service)
|
||||
log := smqlog.NewMock()
|
||||
authn := new(authnmocks.Authentication)
|
||||
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
|
||||
mux := chi.NewRouter()
|
||||
_ = api.MakeHandler(rsvc, am, mux, log, "")
|
||||
return httptest.NewServer(mux), rsvc, authn
|
||||
}
|
||||
|
||||
func TestAddRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Name: "temperature-rule",
|
||||
InputChannel: "chan-1",
|
||||
Status: re.EnabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
rule sdk.Rule
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "add rule successfully",
|
||||
rule: sdk.Rule{Name: "temp-rule", InputChannel: "chan-1"},
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "add rule with empty token",
|
||||
rule: sdk.Rule{Name: "temp-rule"},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "add rule with bad request",
|
||||
rule: sdk.Rule{},
|
||||
token: validToken,
|
||||
svcErr: errors.New("bad request"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("AddRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, []roles.RoleProvision(nil), tc.svcErr)
|
||||
result, err := mgsdk.AddRule(context.Background(), tc.rule, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Name: "temperature-rule",
|
||||
InputChannel: "chan-1",
|
||||
Status: re.EnabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "view rule successfully",
|
||||
id: ruleID,
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "view rule with empty token",
|
||||
id: ruleID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "view non-existent rule",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("ViewRule", mock.Anything, tc.session, tc.id, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ViewRule(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
updatedRule := testRule
|
||||
updatedRule.Name = "updated-rule"
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Name: "updated-rule",
|
||||
InputChannel: "chan-1",
|
||||
InputTopic: "sensors/temperature",
|
||||
Status: re.EnabledStatus,
|
||||
Tags: []string{"temperature", "alerts"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
rule sdk.Rule
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update rule successfully",
|
||||
rule: updatedRule,
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "update rule with empty token",
|
||||
rule: updatedRule,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateRule(context.Background(), tc.rule, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRuleTags(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Tags: []string{"new-tag"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
rule sdk.Rule
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update rule tags successfully",
|
||||
rule: sdk.Rule{ID: ruleID, Tags: []string{"new-tag"}},
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "update rule tags with empty token",
|
||||
rule: sdk.Rule{ID: ruleID},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateRuleTags", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateRuleTags(context.Background(), tc.rule, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRuleSchedule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
rule sdk.Rule
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "update rule schedule successfully",
|
||||
rule: sdk.Rule{ID: ruleID, Schedule: map[string]any{"cron": "0 * * * *"}},
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "update rule schedule with empty token",
|
||||
rule: sdk.Rule{ID: ruleID},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("UpdateRuleSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.UpdateRuleSchedule(context.Background(), tc.rule, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRules(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcPage := re.Page{}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
pm sdk.PageMetadata
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Page
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "list rules successfully",
|
||||
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "list rules with filters",
|
||||
pm: sdk.PageMetadata{
|
||||
Limit: 5,
|
||||
Name: "temp",
|
||||
Status: "enabled",
|
||||
InputChannel: "chan-1",
|
||||
Tag: "temperature",
|
||||
Dir: "desc",
|
||||
Order: "created_at",
|
||||
},
|
||||
token: validToken,
|
||||
svcRes: svcPage,
|
||||
},
|
||||
{
|
||||
desc: "list rules with empty metadata excludes filter params",
|
||||
pm: sdk.PageMetadata{},
|
||||
token: validToken,
|
||||
svcRes: re.Page{},
|
||||
},
|
||||
{
|
||||
desc: "list rules with empty token",
|
||||
pm: sdk.PageMetadata{Limit: 10},
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("ListRules", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.ListRules(context.Background(), tc.pm, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Status: re.EnabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "enable rule successfully",
|
||||
id: ruleID,
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "enable rule with empty token",
|
||||
id: ruleID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("EnableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.EnableRule(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
svcRule := re.Rule{
|
||||
ID: ruleID,
|
||||
Status: re.DisabledStatus,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcRes re.Rule
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "disable rule successfully",
|
||||
id: ruleID,
|
||||
token: validToken,
|
||||
svcRes: svcRule,
|
||||
},
|
||||
{
|
||||
desc: "disable rule with empty token",
|
||||
id: ruleID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("DisableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
result, err := mgsdk.DisableRule(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
if !tc.wantErr {
|
||||
assert.NotEmpty(t, result.ID)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRule(t *testing.T) {
|
||||
rs, rsvc, auth := setupRules()
|
||||
defer rs.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
RulesEngineURL: rs.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
authenticateErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "remove rule successfully",
|
||||
id: ruleID,
|
||||
token: validToken,
|
||||
},
|
||||
{
|
||||
desc: "remove rule with empty token",
|
||||
id: ruleID,
|
||||
token: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "remove non-existent rule",
|
||||
id: "non-existent",
|
||||
token: validToken,
|
||||
svcErr: errors.New("not found"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := rsvc.On("RemoveRule", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
err := mgsdk.RemoveRule(context.Background(), tc.id, domainID, tc.token)
|
||||
assert.Equal(t, tc.wantErr, err != nil)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
+171
-10
@@ -15,6 +15,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
smqSDK "github.com/absmach/supermq/pkg/sdk"
|
||||
@@ -26,16 +27,33 @@ var _ SDK = (*mgSDK)(nil)
|
||||
type Metadata map[string]any
|
||||
|
||||
type PageMetadata struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
Contact string `json:"contact,omitempty"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Level uint64 `json:"level,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
Contact string `json:"contact,omitempty"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Level uint64 `json:"level,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Dir string `json:"dir,omitempty"`
|
||||
Order string `json:"order,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
InputChannel string `json:"input_channel,omitempty"`
|
||||
RuleID string `json:"rule_id,omitempty"`
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Subtopic string `json:"subtopic,omitempty"`
|
||||
AssigneeID string `json:"assignee_id,omitempty"`
|
||||
Severity uint8 `json:"severity,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
AssignedBy string `json:"assigned_by,omitempty"`
|
||||
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
|
||||
ResolvedBy string `json:"resolved_by,omitempty"`
|
||||
CreatedFrom time.Time `json:"created_from,omitempty"`
|
||||
CreatedTo time.Time `json:"created_to,omitempty"`
|
||||
}
|
||||
|
||||
type MessagePageMetadata struct {
|
||||
@@ -190,12 +208,95 @@ type SDK interface {
|
||||
// err := sdk.DeleteSubscription(ctx, "id", "token")
|
||||
// fmt.Println(err)
|
||||
DeleteSubscription(ctx context.Context, id, token string) errors.SDKError
|
||||
|
||||
// Alarms API
|
||||
|
||||
// UpdateAlarm updates an existing alarm.
|
||||
UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError)
|
||||
|
||||
// ViewAlarm retrieves an alarm by its ID.
|
||||
ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError)
|
||||
|
||||
// ListAlarms retrieves a page of alarms.
|
||||
ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError)
|
||||
|
||||
// DeleteAlarm deletes an alarm.
|
||||
DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError
|
||||
|
||||
// Reports API
|
||||
|
||||
// AddReportConfig creates a new report configuration.
|
||||
AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// ViewReportConfig retrieves a report config by its ID.
|
||||
ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// UpdateReportConfig updates an existing report configuration.
|
||||
UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// UpdateReportSchedule updates an existing report configuration's schedule.
|
||||
UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// RemoveReportConfig deletes a report config.
|
||||
RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError
|
||||
|
||||
// ListReportsConfig retrieves a page of report configs.
|
||||
ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError)
|
||||
|
||||
// EnableReportConfig enables a report config.
|
||||
EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// DisableReportConfig disables a report config.
|
||||
DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
|
||||
|
||||
// UpdateReportTemplate updates a report template.
|
||||
UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError
|
||||
|
||||
// ViewReportTemplate retrieves a report template.
|
||||
ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError)
|
||||
|
||||
// DeleteReportTemplate deletes a report template.
|
||||
DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError
|
||||
|
||||
// GenerateReport generates a report from a configuration.
|
||||
GenerateReport(ctx context.Context, config ReportConfig, action ReportAction, domainID, token string) (ReportPage, *ReportFile, errors.SDKError)
|
||||
// Rules Engine API
|
||||
|
||||
// AddRule creates a new rule.
|
||||
AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// ViewRule retrieves a rule by its ID.
|
||||
ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// UpdateRule updates an existing rule.
|
||||
UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// UpdateRuleTags updates an existing rule's tags.
|
||||
UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// UpdateRuleSchedule updates an existing rule's schedule.
|
||||
UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// ListRules retrieves a page of rules.
|
||||
ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError)
|
||||
|
||||
// RemoveRule deletes a rule.
|
||||
RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError
|
||||
|
||||
// EnableRule enables a rule.
|
||||
EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
|
||||
|
||||
// DisableRule disables a rule.
|
||||
DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
|
||||
}
|
||||
|
||||
type mgSDK struct {
|
||||
bootstrapURL string
|
||||
readersURL string
|
||||
usersURL string
|
||||
alarmsURL string
|
||||
reportsURL string
|
||||
rulesEngineURL string
|
||||
client *http.Client
|
||||
curlFlag bool
|
||||
msgContentType smqSDK.ContentType
|
||||
@@ -216,6 +317,9 @@ type Config struct {
|
||||
DomainsURL string
|
||||
JournalURL string
|
||||
HostURL string
|
||||
AlarmsURL string
|
||||
ReportsURL string
|
||||
RulesEngineURL string
|
||||
|
||||
MsgContentType smqSDK.ContentType
|
||||
TLSVerification bool
|
||||
@@ -244,6 +348,9 @@ func NewSDK(conf Config) SDK {
|
||||
bootstrapURL: conf.BootstrapURL,
|
||||
readersURL: conf.ReaderURL,
|
||||
usersURL: conf.UsersURL,
|
||||
alarmsURL: conf.AlarmsURL,
|
||||
reportsURL: conf.ReportsURL,
|
||||
rulesEngineURL: conf.RulesEngineURL,
|
||||
msgContentType: conf.MsgContentType,
|
||||
|
||||
client: &http.Client{
|
||||
@@ -347,6 +454,60 @@ func (pm PageMetadata) query() (string, error) {
|
||||
if pm.Level != 0 {
|
||||
q.Add("level", strconv.FormatUint(pm.Level, 10))
|
||||
}
|
||||
if pm.Name != "" {
|
||||
q.Add("name", pm.Name)
|
||||
}
|
||||
if pm.Status != "" {
|
||||
q.Add("status", pm.Status)
|
||||
}
|
||||
if pm.Dir != "" {
|
||||
q.Add("dir", pm.Dir)
|
||||
}
|
||||
if pm.Order != "" {
|
||||
q.Add("order", pm.Order)
|
||||
}
|
||||
if pm.Tag != "" {
|
||||
q.Add("tag", pm.Tag)
|
||||
}
|
||||
if pm.InputChannel != "" {
|
||||
q.Add("input_channel", pm.InputChannel)
|
||||
}
|
||||
if pm.RuleID != "" {
|
||||
q.Add("rule_id", pm.RuleID)
|
||||
}
|
||||
if pm.ChannelID != "" {
|
||||
q.Add("channel_id", pm.ChannelID)
|
||||
}
|
||||
if pm.ClientID != "" {
|
||||
q.Add("client_id", pm.ClientID)
|
||||
}
|
||||
if pm.Subtopic != "" {
|
||||
q.Add("subtopic", pm.Subtopic)
|
||||
}
|
||||
if pm.AssigneeID != "" {
|
||||
q.Add("assignee_id", pm.AssigneeID)
|
||||
}
|
||||
if pm.Severity != 0 {
|
||||
q.Add("severity", strconv.FormatUint(uint64(pm.Severity), 10))
|
||||
}
|
||||
if pm.UpdatedBy != "" {
|
||||
q.Add("updated_by", pm.UpdatedBy)
|
||||
}
|
||||
if pm.AssignedBy != "" {
|
||||
q.Add("assigned_by", pm.AssignedBy)
|
||||
}
|
||||
if pm.AcknowledgedBy != "" {
|
||||
q.Add("acknowledged_by", pm.AcknowledgedBy)
|
||||
}
|
||||
if pm.ResolvedBy != "" {
|
||||
q.Add("resolved_by", pm.ResolvedBy)
|
||||
}
|
||||
if !pm.CreatedFrom.IsZero() {
|
||||
q.Add("created_from", pm.CreatedFrom.UTC().Format(time.RFC3339))
|
||||
}
|
||||
if !pm.CreatedTo.IsZero() {
|
||||
q.Add("created_to", pm.CreatedTo.UTC().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
return q.Encode(), nil
|
||||
}
|
||||
|
||||
+43
-43
@@ -1,52 +1,52 @@
|
||||
# Provision service
|
||||
|
||||
Provision service provides an HTTP API to create initial SuperMQ resources for gateways or edge deployments. It can create clients and channels based on a configurable layout, optionally create bootstrap configurations, whitelist clients, and issue X.509 certificates for mTLS.
|
||||
Provision service provides an HTTP API to create initial Magistrala resources for gateways or edge deployments. It can create clients and channels based on a configurable layout, optionally create bootstrap configurations, whitelist clients, and issue X.509 certificates for mTLS.
|
||||
|
||||
For gateways to communicate with [SuperMQ][supermq], configuration is required (MQTT host, client, channels, certificates). A gateway can fetch bootstrap configuration from the [Bootstrap][bootstrap] service using its `<external_id>` and `<external_key>`. The [Agent][agent] service is typically used on gateways to retrieve that configuration.
|
||||
For gateways to communicate with [Magistrala][magistrala], configuration is required (MQTT host, client, channels, certificates). A gateway can fetch bootstrap configuration from the [Bootstrap][bootstrap] service using its `<external_id>` and `<external_key>`. The [Agent][agent] service is typically used on gateways to retrieve that configuration.
|
||||
|
||||
You can create bootstrap configuration directly via [Bootstrap][bootstrap] or through Provision. [SuperMQ UI][mgxui] uses the Bootstrap service; Provision is intended to automate gateway setups where one physical gateway may require multiple clients and channels (for example, [Agent][agent] and [Export][export]). This setup is defined as a **provision layout**.
|
||||
You can create bootstrap configuration directly via [Bootstrap][bootstrap] or through Provision. [Magistrala UI][mgxui] uses the Bootstrap service; Provision is intended to automate gateway setups where one physical gateway may require multiple clients and channels (for example, [Agent][agent] and [Export][export]). This setup is defined as a **provision layout**.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using environment variables and/or a TOML config file. Defaults below are from `provision/config.go`. Docker add-on examples are in `docker/addons/provision/docker-compose.yaml` and [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). The binary reads `SMQ_PROVISION_*` variables; the add-on compose file uses `MG_PROVISION_*`, so ensure the container receives the expected names.
|
||||
The service is configured using environment variables and/or a TOML config file. Defaults below are from `provision/config.go`. Docker add-on examples are in `docker/addons/provision/docker-compose.yaml` and [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). The binary reads `MG_PROVISION_*` variables; the add-on compose file uses `MG_PROVISION_*`, so ensure the container receives the expected names.
|
||||
|
||||
### Core service
|
||||
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `SMQ_PROVISION_HTTP_PORT` | Provision service listening port | `9016` |
|
||||
| `SMQ_PROVISION_LOG_LEVEL` | Service log level | `info` |
|
||||
| `SMQ_PROVISION_ENV_CLIENTS_TLS` | SDK TLS verification | `false` |
|
||||
| `SMQ_PROVISION_SERVER_CERT` | HTTPS server certificate | "" |
|
||||
| `SMQ_PROVISION_SERVER_KEY` | HTTPS server key | "" |
|
||||
| `SMQ_SEND_TELEMETRY` | Send telemetry to SuperMQ call-home server | `true` |
|
||||
| `SMQ_MQTT_ADAPTER_INSTANCE_ID` | Instance ID used in health output | "" |
|
||||
| `MG_PROVISION_HTTP_PORT` | Provision service listening port | `9016` |
|
||||
| `MG_PROVISION_LOG_LEVEL` | Service log level | `info` |
|
||||
| `MG_PROVISION_ENV_CLIENTS_TLS` | SDK TLS verification | `false` |
|
||||
| `MG_PROVISION_SERVER_CERT` | HTTPS server certificate | "" |
|
||||
| `MG_PROVISION_SERVER_KEY` | HTTPS server key | "" |
|
||||
| `MG_SEND_TELEMETRY` | Send telemetry to Magistrala call-home server | `true` |
|
||||
| `MG_MQTT_ADAPTER_INSTANCE_ID` | Instance ID used in health output | "" |
|
||||
|
||||
### SuperMQ endpoints and credentials
|
||||
### Magistrala endpoints and credentials
|
||||
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `SMQ_PROVISION_USERS_LOCATION` | Users service URL | `http://localhost` |
|
||||
| `SMQ_PROVISION_CLIENTS_LOCATION` | Clients service URL | `http://localhost` |
|
||||
| `SMQ_PROVISION_CERTS_LOCATION` | Certs service URL (certs SDK) | `http://localhost` |
|
||||
| `SMQ_PROVISION_BS_SVC_URL` | Bootstrap service URL | `http://localhost:9000` |
|
||||
| `SMQ_PROVISION_CERTS_SVC_URL` | Certs service URL (Magistrala SDK) | `http://localhost:9019` |
|
||||
| `SMQ_PROVISION_USERNAME` | SuperMQ username | `user` |
|
||||
| `SMQ_PROVISION_PASS` | SuperMQ password | `test` |
|
||||
| `SMQ_PROVISION_API_KEY` | SuperMQ authentication token | "" |
|
||||
| `SMQ_PROVISION_EMAIL` | SuperMQ user email | `test@example.com` |
|
||||
| `SMQ_PROVISION_DOMAIN_ID` | Default domain ID (unused by HTTP API) | "" |
|
||||
| `MG_PROVISION_USERS_LOCATION` | Users service URL | `http://localhost` |
|
||||
| `MG_PROVISION_CLIENTS_LOCATION` | Clients service URL | `http://localhost` |
|
||||
| `MG_PROVISION_CERTS_LOCATION` | Certs service URL (certs SDK) | `http://localhost` |
|
||||
| `MG_PROVISION_BS_SVC_URL` | Bootstrap service URL | `http://localhost:9000` |
|
||||
| `MG_PROVISION_CERTS_SVC_URL` | Certs service URL (Magistrala SDK) | `http://localhost:9019` |
|
||||
| `MG_PROVISION_USERNAME` | Magistrala username | `user` |
|
||||
| `MG_PROVISION_PASS` | Magistrala password | `test` |
|
||||
| `MG_PROVISION_API_KEY` | Magistrala authentication token | "" |
|
||||
| `MG_PROVISION_EMAIL` | Magistrala user email | `test@example.com` |
|
||||
| `MG_PROVISION_DOMAIN_ID` | Default domain ID (unused by HTTP API) | "" |
|
||||
|
||||
### Provisioning behavior
|
||||
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `SMQ_PROVISION_CONFIG_FILE` | Provision config file | `config.toml` |
|
||||
| `SMQ_PROVISION_X509_PROVISIONING` | Issue client certificates during provisioning | `false` |
|
||||
| `SMQ_PROVISION_BS_CONFIG_PROVISIONING` | Save client config in Bootstrap | `true` |
|
||||
| `SMQ_PROVISION_BS_AUTO_WHITELIST` | Auto-whitelist client | `true` |
|
||||
| `SMQ_PROVISION_BS_CONTENT` | Bootstrap config content (JSON string) | "" |
|
||||
| `SMQ_PROVISION_CERTS_HOURS_VALID` | Client cert validity period | `2400h` |
|
||||
| `MG_PROVISION_CONFIG_FILE` | Provision config file | `config.toml` |
|
||||
| `MG_PROVISION_X509_PROVISIONING` | Issue client certificates during provisioning | `false` |
|
||||
| `MG_PROVISION_BS_CONFIG_PROVISIONING` | Save client config in Bootstrap | `true` |
|
||||
| `MG_PROVISION_BS_AUTO_WHITELIST` | Auto-whitelist client | `true` |
|
||||
| `MG_PROVISION_BS_CONTENT` | Bootstrap config content (JSON string) | "" |
|
||||
| `MG_PROVISION_CERTS_HOURS_VALID` | Client cert validity period | `2400h` |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -66,7 +66,7 @@ Notes:
|
||||
|
||||
- At least one client must include `external_id` in metadata. This value is replaced with the `external_id` from the provisioning request and is used for bootstrap creation.
|
||||
- Channel metadata `type` is reserved for `control`, `data`, and `export` and is used to enrich gateway metadata.
|
||||
- Bootstrap content can be provided via `bootstrap.content` in the TOML file or as JSON through `SMQ_PROVISION_BS_CONTENT`.
|
||||
- Bootstrap content can be provided via `bootstrap.content` in the TOML file or as JSON through `MG_PROVISION_BS_CONTENT`.
|
||||
|
||||
Example layout:
|
||||
|
||||
@@ -98,11 +98,11 @@ Example layout:
|
||||
|
||||
## Authentication
|
||||
|
||||
Provision uses SuperMQ APIs and requires a valid token. There are three ways to provide it:
|
||||
Provision uses Magistrala APIs and requires a valid token. There are three ways to provide it:
|
||||
|
||||
- `Authorization: Bearer <token>` on each request.
|
||||
- `SMQ_PROVISION_API_KEY` in env or TOML (used when no header token is provided).
|
||||
- `SMQ_PROVISION_USERNAME` and `SMQ_PROVISION_PASS` in env or TOML (used to create an access token when no header token is provided).
|
||||
- `MG_PROVISION_API_KEY` in env or TOML (used when no header token is provided).
|
||||
- `MG_PROVISION_USERNAME` and `MG_PROVISION_PASS` in env or TOML (used to create an access token when no header token is provided).
|
||||
|
||||
`POST /{domainID}/mapping` can create its own token using API key or username/password if no `Authorization` header is provided. The `Authorization` header takes precedence when present. `GET /{domainID}/mapping` always requires a bearer token.
|
||||
|
||||
@@ -126,10 +126,10 @@ Standalone:
|
||||
```bash
|
||||
make provision
|
||||
|
||||
SMQ_PROVISION_BS_SVC_URL=http://localhost:9013 \
|
||||
SMQ_PROVISION_CLIENTS_LOCATION=http://localhost:9006 \
|
||||
SMQ_PROVISION_USERS_LOCATION=http://localhost:9002 \
|
||||
SMQ_PROVISION_CONFIG_FILE=provision/configs/config.toml \
|
||||
MG_PROVISION_BS_SVC_URL=http://localhost:9013 \
|
||||
MG_PROVISION_CLIENTS_LOCATION=http://localhost:9006 \
|
||||
MG_PROVISION_USERS_LOCATION=http://localhost:9002 \
|
||||
MG_PROVISION_CONFIG_FILE=provision/configs/config.toml \
|
||||
./build/provision
|
||||
```
|
||||
|
||||
@@ -154,7 +154,7 @@ The Provision service exposes the following endpoints:
|
||||
When credentials are available via env/config, you can omit the `Authorization` header. `Content-Type` must be exactly `application/json`.
|
||||
|
||||
```bash
|
||||
curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
curl -s -S -X POST http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name": "gateway-a", "external_id": "33:52:77:99:43", "external_key": "223334fw2"}'
|
||||
```
|
||||
@@ -162,7 +162,7 @@ curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping
|
||||
If you want to supply a token explicitly:
|
||||
|
||||
```bash
|
||||
curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
curl -s -S -X POST http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
-H "Authorization: Bearer <token|api_key>" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name": "gateway-a", "external_id": "<external_id>", "external_key": "<external_key>"}'
|
||||
@@ -207,14 +207,14 @@ Response contains created clients, channels, and optional certificate data:
|
||||
### Example: Read bootstrap mapping
|
||||
|
||||
```bash
|
||||
curl -s -S -X GET http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
curl -s -S -X GET http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
|
||||
-H "Authorization: Bearer <token|api_key>" \
|
||||
-H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## Certificates
|
||||
|
||||
When `SMQ_PROVISION_X509_PROVISIONING=true`, the provisioning flow issues certificates for each client and returns them in the response as `client_cert`, `client_key`, and `ca_cert`. The certificate TTL is controlled by `SMQ_PROVISION_CERTS_HOURS_VALID`.
|
||||
When `MG_PROVISION_X509_PROVISIONING=true`, the provisioning flow issues certificates for each client and returns them in the response as `client_cert`, `client_key`, and `ca_cert`. The certificate TTL is controlled by `MG_PROVISION_CERTS_HOURS_VALID`.
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -225,8 +225,8 @@ go test ./provision/...
|
||||
For an in-depth explanation of our Provision Service, see the [official documentation][doc].
|
||||
|
||||
[doc]: https://docs.magistrala.absmach.eu/dev-guide/provision/
|
||||
[supermq]: https://github.com/absmach/supermq
|
||||
[bootstrap]: https://github.com/absmach/supermq/tree/main/bootstrap
|
||||
[magistrala]: https://github.com/absmach/magistrala
|
||||
[bootstrap]: https://github.com/absmach/magistrala/tree/main/bootstrap
|
||||
[export]: https://github.com/absmach/export
|
||||
[agent]: https://github.com/absmach/agent
|
||||
[mgxui]: https://github.com/absmach/supermq/ui
|
||||
[mgxui]: https://github.com/absmach/magistrala/ui
|
||||
|
||||
+31
-10
@@ -8,18 +8,24 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/provision"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
func doProvision(svc provision.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
req := request.(provisionReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
res, err := svc.Provision(ctx, req.domainID, req.token, req.Name, req.ExternalID, req.ExternalKey)
|
||||
res, err := svc.Provision(ctx, session.DomainID, req.token, req.Name, req.ExternalID, req.ExternalKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,16 +45,31 @@ func doProvision(svc provision.Service) endpoint.Endpoint {
|
||||
|
||||
func getMapping(svc provision.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(mappingReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
res, err := svc.Mapping(ctx, req.token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := svc.Mapping()
|
||||
|
||||
return mappingRes{Data: res}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func issueCert(svc provision.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
req := request.(certReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
cert, key, err := svc.Cert(ctx, session.DomainID, req.token, req.ClientID, req.TTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return certRes{
|
||||
Certificate: cert,
|
||||
Key: key,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
+129
-24
@@ -16,7 +16,11 @@ import (
|
||||
"github.com/absmach/magistrala/provision/api"
|
||||
mocks "github.com/absmach/magistrala/provision/mocks"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/auth"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -26,6 +30,13 @@ var (
|
||||
validToken = "valid"
|
||||
validContenType = "application/json"
|
||||
validID = testsutil.GenerateUUID(&testing.T{})
|
||||
userID = testsutil.GenerateUUID(&testing.T{})
|
||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||
validSession = smqauthn.Session{
|
||||
DomainUserID: auth.EncodeDomainUserID(domainID, userID),
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
}
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
@@ -54,16 +65,18 @@ func (tr testRequest) make() (*http.Response, error) {
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newProvisionServer() (*httptest.Server, *mocks.Service) {
|
||||
func newProvisionServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
|
||||
logger := smqlog.NewMock()
|
||||
mux := api.MakeHandler(svc, logger, "test")
|
||||
return httptest.NewServer(mux), svc
|
||||
authn := new(authnmocks.Authentication)
|
||||
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
|
||||
mux := api.MakeHandler(svc, am, logger, "test")
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func TestProvision(t *testing.T) {
|
||||
is, svc := newProvisionServer()
|
||||
is, svc, authn := newProvisionServer()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -72,6 +85,8 @@ func TestProvision(t *testing.T) {
|
||||
data string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
@@ -81,6 +96,7 @@ func TestProvision(t *testing.T) {
|
||||
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
|
||||
status: http.StatusCreated,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -90,7 +106,7 @@ func TestProvision(t *testing.T) {
|
||||
data: fmt.Sprintf(`{"name": "test", "external_key": "%s"}`, validID),
|
||||
status: http.StatusBadRequest,
|
||||
contentType: validContenType,
|
||||
svcErr: nil,
|
||||
authnRes: validSession,
|
||||
},
|
||||
{
|
||||
desc: "request with empty external key",
|
||||
@@ -99,6 +115,7 @@ func TestProvision(t *testing.T) {
|
||||
data: fmt.Sprintf(`{"name": "test", "external_id": "%s"}`, validID),
|
||||
status: http.StatusUnauthorized,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -106,8 +123,10 @@ func TestProvision(t *testing.T) {
|
||||
token: "",
|
||||
domainID: validID,
|
||||
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
|
||||
status: http.StatusCreated,
|
||||
status: http.StatusUnauthorized,
|
||||
contentType: validContenType,
|
||||
authnRes: smqauthn.Session{},
|
||||
authnErr: errors.ErrAuthentication,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -117,6 +136,7 @@ func TestProvision(t *testing.T) {
|
||||
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
contentType: "text/plain",
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -126,6 +146,7 @@ func TestProvision(t *testing.T) {
|
||||
data: `data`,
|
||||
status: http.StatusBadRequest,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -135,12 +156,14 @@ func TestProvision(t *testing.T) {
|
||||
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
|
||||
status: http.StatusForbidden,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
repocall := svc.On("Provision", mock.Anything, validID, tc.token, "test", validID, validID).Return(provision.Result{}, tc.svcErr)
|
||||
req := testRequest{
|
||||
client: is.Client(),
|
||||
@@ -154,13 +177,14 @@ func TestProvision(t *testing.T) {
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, tc.desc)
|
||||
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
|
||||
authCall.Unset()
|
||||
repocall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
is, svc := newProvisionServer()
|
||||
is, svc, authn := newProvisionServer()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -168,6 +192,8 @@ func TestMapping(t *testing.T) {
|
||||
domainID string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
@@ -177,6 +203,8 @@ func TestMapping(t *testing.T) {
|
||||
status: http.StatusOK,
|
||||
contentType: validContenType,
|
||||
svcErr: nil,
|
||||
authnRes: validSession,
|
||||
authnErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "empty token",
|
||||
@@ -185,28 +213,15 @@ func TestMapping(t *testing.T) {
|
||||
status: http.StatusUnauthorized,
|
||||
contentType: validContenType,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid content type",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
contentType: "text/plain",
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "service error",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
status: http.StatusForbidden,
|
||||
contentType: validContenType,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
authnRes: smqauthn.Session{},
|
||||
authnErr: errors.ErrAuthentication,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repocall := svc.On("Mapping", mock.Anything, tc.token).Return(map[string]any{}, tc.svcErr)
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
repocall := svc.On("Mapping").Return(map[string]any{}, tc.svcErr)
|
||||
req := testRequest{
|
||||
client: is.Client(),
|
||||
method: http.MethodGet,
|
||||
@@ -218,6 +233,96 @@ func TestMapping(t *testing.T) {
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, tc.desc)
|
||||
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
|
||||
authCall.Unset()
|
||||
repocall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
is, svc, authn := newProvisionServer()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
domainID string
|
||||
data string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
desc: "valid request",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
|
||||
status: http.StatusCreated,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "empty token",
|
||||
token: "",
|
||||
domainID: validID,
|
||||
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
|
||||
status: http.StatusUnauthorized,
|
||||
contentType: validContenType,
|
||||
authnRes: smqauthn.Session{},
|
||||
authnErr: errors.ErrAuthentication,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid content type",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
contentType: "text/plain",
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid request",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
data: `data`,
|
||||
status: http.StatusBadRequest,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "service error",
|
||||
token: validToken,
|
||||
domainID: validID,
|
||||
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
|
||||
status: http.StatusForbidden,
|
||||
contentType: validContenType,
|
||||
authnRes: validSession,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
repocall := svc.On("Cert", mock.Anything, validID, tc.token, validID, "1h").Return("cert", "key", tc.svcErr)
|
||||
req := testRequest{
|
||||
client: is.Client(),
|
||||
method: http.MethodPost,
|
||||
url: is.URL + fmt.Sprintf("/%s/cert", tc.domainID),
|
||||
token: tc.token,
|
||||
contentType: tc.contentType,
|
||||
body: strings.NewReader(tc.data),
|
||||
}
|
||||
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, tc.desc)
|
||||
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
|
||||
authCall.Unset()
|
||||
repocall.Unset()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
type provisionReq struct {
|
||||
token string
|
||||
domainID string
|
||||
Name string `json:"name"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
@@ -19,9 +18,6 @@ func (req provisionReq) validate() error {
|
||||
if req.ExternalID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
if req.domainID == "" {
|
||||
return apiutil.ErrMissingDomainID
|
||||
}
|
||||
|
||||
if req.ExternalKey == "" {
|
||||
return apiutil.ErrBearerKey
|
||||
@@ -34,17 +30,16 @@ func (req provisionReq) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mappingReq struct {
|
||||
type certReq struct {
|
||||
token string
|
||||
domainID string
|
||||
ClientID string `json:"client_id"`
|
||||
TTL string `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (req mappingReq) validate() error {
|
||||
if req.token == "" {
|
||||
return apiutil.ErrBearerToken
|
||||
}
|
||||
if req.domainID == "" {
|
||||
return apiutil.ErrMissingDomainID
|
||||
func (req certReq) validate() error {
|
||||
if req.ClientID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ func TestProvisioReq(t *testing.T) {
|
||||
desc: "valid request",
|
||||
req: provisionReq{
|
||||
token: "token",
|
||||
domainID: testsutil.GenerateUUID(t),
|
||||
Name: "name",
|
||||
ExternalID: testsutil.GenerateUUID(t),
|
||||
ExternalKey: testsutil.GenerateUUID(t),
|
||||
@@ -34,29 +33,16 @@ func TestProvisioReq(t *testing.T) {
|
||||
desc: "empty external id",
|
||||
req: provisionReq{
|
||||
token: "token",
|
||||
domainID: testsutil.GenerateUUID(t),
|
||||
Name: "name",
|
||||
ExternalID: "",
|
||||
ExternalKey: testsutil.GenerateUUID(t),
|
||||
},
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
{
|
||||
desc: "empty domain id",
|
||||
req: provisionReq{
|
||||
token: "token",
|
||||
domainID: "",
|
||||
Name: "name",
|
||||
ExternalID: testsutil.GenerateUUID(t),
|
||||
ExternalKey: testsutil.GenerateUUID(t),
|
||||
},
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "empty external key",
|
||||
req: provisionReq{
|
||||
token: "token",
|
||||
domainID: testsutil.GenerateUUID(t),
|
||||
Name: "name",
|
||||
ExternalID: testsutil.GenerateUUID(t),
|
||||
ExternalKey: "",
|
||||
@@ -70,41 +56,3 @@ func TestProvisioReq(t *testing.T) {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappingReq(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
req mappingReq
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid request",
|
||||
req: mappingReq{
|
||||
token: "token",
|
||||
domainID: testsutil.GenerateUUID(t),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "empty token",
|
||||
req: mappingReq{
|
||||
token: "",
|
||||
domainID: testsutil.GenerateUUID(t),
|
||||
},
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "empty domain id",
|
||||
req: mappingReq{
|
||||
token: "token",
|
||||
domainID: "",
|
||||
},
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := tc.req.validate()
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,23 @@ func (res mappingRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type certRes struct {
|
||||
Certificate string `json:"certificate"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (res certRes) Code() int {
|
||||
return http.StatusCreated
|
||||
}
|
||||
|
||||
func (res certRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res certRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (res mappingRes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(res.Data)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/absmach/supermq"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
@@ -24,7 +25,7 @@ const (
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler {
|
||||
func MakeHandler(svc provision.Service, authn smqauthn.AuthNMiddleware, logger *slog.Logger, instanceID string) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
@@ -32,6 +33,7 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string)
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Route("/{domainID}", func(r chi.Router) {
|
||||
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
|
||||
r.Route("/mapping", func(r chi.Router) {
|
||||
r.Post("/", kithttp.NewServer(
|
||||
doProvision(svc),
|
||||
@@ -46,6 +48,12 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string)
|
||||
opts...,
|
||||
).ServeHTTP)
|
||||
})
|
||||
r.Post("/cert", kithttp.NewServer(
|
||||
issueCert(svc),
|
||||
decodeCertRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
).ServeHTTP)
|
||||
})
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
r.Get("/health", supermq.Health("provision", instanceID))
|
||||
@@ -59,8 +67,7 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
}
|
||||
|
||||
req := provisionReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
domainID: chi.URLParam(r, "domainID"),
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||
@@ -70,13 +77,19 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
}
|
||||
|
||||
func decodeMappingRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func decodeCertRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if r.Header.Get("Content-Type") != contentType {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
|
||||
req := mappingReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
domainID: chi.URLParam(r, "domainID"),
|
||||
req := certReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
|
||||
+17
-18
@@ -17,22 +17,21 @@ var errFailedToReadConfig = errors.New("failed to read config file")
|
||||
|
||||
// ServiceConf represents service config.
|
||||
type ServiceConf struct {
|
||||
Port string `toml:"port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"`
|
||||
LogLevel string `toml:"log_level" env:"SMQ_PROVISION_LOG_LEVEL" envDefault:"info"`
|
||||
TLS bool `toml:"tls" env:"SMQ_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"`
|
||||
ServerCert string `toml:"server_cert" env:"SMQ_PROVISION_SERVER_CERT" envDefault:""`
|
||||
ServerKey string `toml:"server_key" env:"SMQ_PROVISION_SERVER_KEY" envDefault:""`
|
||||
ClientsURL string `toml:"clients_url" env:"SMQ_PROVISION_CLIENTS_LOCATION" envDefault:"http://localhost"`
|
||||
UsersURL string `toml:"users_url" env:"SMQ_PROVISION_USERS_LOCATION" envDefault:"http://localhost"`
|
||||
CertsURL string `toml:"certs_url" env:"SMQ_PROVISION_CERTS_LOCATION" envDefault:"http://localhost"`
|
||||
HTTPPort string `toml:"http_port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"`
|
||||
MgEmail string `toml:"smq_email" env:"SMQ_PROVISION_EMAIL" envDefault:"test@example.com"`
|
||||
MgUsername string `toml:"smq_username" env:"SMQ_PROVISION_USERNAME" envDefault:"user"`
|
||||
MgPass string `toml:"smq_pass" env:"SMQ_PROVISION_PASS" envDefault:"test"`
|
||||
MgDomainID string `toml:"smq_domain_id" env:"SMQ_PROVISION_DOMAIN_ID" envDefault:""`
|
||||
MgAPIKey string `toml:"smq_api_key" env:"SMQ_PROVISION_API_KEY" envDefault:""`
|
||||
MgBSURL string `toml:"smq_bs_url" env:"SMQ_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"`
|
||||
MgCertsURL string `toml:"smq_certs_url" env:"SMQ_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"`
|
||||
Port string `toml:"port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"`
|
||||
LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"`
|
||||
TLS bool `toml:"tls" env:"MG_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"`
|
||||
ServerCert string `toml:"server_cert" env:"MG_PROVISION_SERVER_CERT" envDefault:""`
|
||||
ServerKey string `toml:"server_key" env:"MG_PROVISION_SERVER_KEY" envDefault:""`
|
||||
ClientsURL string `toml:"clients_url" env:"MG_PROVISION_CLIENTS_URL" envDefault:"http://localhost"`
|
||||
ChannelsURL string `toml:"channels_url" env:"MG_PROVISION_CHANNELS_URL" envDefault:"http://localhost"`
|
||||
UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_URL" envDefault:"http://localhost"`
|
||||
CertsURL string `toml:"certs_url" env:"MG_PROVISION_CERTS_URL" envDefault:"http://localhost"`
|
||||
MgEmail string `toml:"mg_email" env:"MG_PROVISION_EMAIL" envDefault:"test@example.com"`
|
||||
MgUsername string `toml:"mg_username" env:"MG_PROVISION_USERNAME" envDefault:"user"`
|
||||
MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"`
|
||||
MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""`
|
||||
MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""`
|
||||
MgBSURL string `toml:"mg_bs_url" env:"MG_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"`
|
||||
}
|
||||
|
||||
// Bootstrap represetns the Bootstrap config.
|
||||
@@ -61,13 +60,13 @@ type Cert struct {
|
||||
|
||||
// Config struct of Provision.
|
||||
type Config struct {
|
||||
File string `toml:"file" env:"SMQ_PROVISION_CONFIG_FILE" envDefault:"config.toml"`
|
||||
File string `toml:"file" env:"MG_PROVISION_CONFIG_FILE" envDefault:"config.toml"`
|
||||
Server ServiceConf `toml:"server" mapstructure:"server"`
|
||||
Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"`
|
||||
Clients []clients.Client `toml:"clients" mapstructure:"clients"`
|
||||
Channels []channels.Channel `toml:"channels" mapstructure:"channels"`
|
||||
Cert Cert `toml:"cert" mapstructure:"cert"`
|
||||
BSContent string `env:"SMQ_PROVISION_BS_CONTENT" envDefault:""`
|
||||
BSContent string `env:"MG_PROVISION_BS_CONTENT" envDefault:""`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""`
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ var (
|
||||
Metadata: map[string]any{
|
||||
"test": "test",
|
||||
},
|
||||
PrivateMetadata: clients.Metadata{},
|
||||
Actions: []string{},
|
||||
AccessProviderRoleActions: []string{},
|
||||
ConnectionTypes: []connections.ConnType{},
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
|
||||
package api
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -20,8 +18,8 @@ type loggingMiddleware struct {
|
||||
svc provision.Service
|
||||
}
|
||||
|
||||
// NewLoggingMiddleware adds logging facilities to the core service.
|
||||
func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service {
|
||||
// NewLogging adds logging facilities to the core service.
|
||||
func NewLogging(svc provision.Service, logger *slog.Logger) provision.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
@@ -33,7 +31,7 @@ func (lm *loggingMiddleware) Provision(ctx context.Context, domainID, token, nam
|
||||
slog.String("external_id", externalID),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Provision failed", args...)
|
||||
return
|
||||
}
|
||||
@@ -51,8 +49,8 @@ func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID
|
||||
slog.String("ttl", duration),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("Client certificate failed to create successfully", args...)
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Client certificate creation failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Client certificate created successfully", args...)
|
||||
@@ -61,18 +59,13 @@ func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID
|
||||
return lm.svc.Cert(ctx, domainID, token, clientID, duration)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Mapping(ctx context.Context, token string) (res map[string]any, err error) {
|
||||
func (lm *loggingMiddleware) Mapping() (res map[string]any) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("Mapping failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Mapping completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Mapping(ctx, token)
|
||||
return lm.svc.Mapping()
|
||||
}
|
||||
+12
-34
@@ -133,31 +133,22 @@ func (_c *Service_Cert_Call) RunAndReturn(run func(ctx context.Context, domainID
|
||||
}
|
||||
|
||||
// Mapping provides a mock function for the type Service
|
||||
func (_mock *Service) Mapping(ctx context.Context, token string) (map[string]any, error) {
|
||||
ret := _mock.Called(ctx, token)
|
||||
func (_mock *Service) Mapping() map[string]any {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Mapping")
|
||||
}
|
||||
|
||||
var r0 map[string]any
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (map[string]any, error)); ok {
|
||||
return returnFunc(ctx, token)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) map[string]any); ok {
|
||||
r0 = returnFunc(ctx, token)
|
||||
if returnFunc, ok := ret.Get(0).(func() map[string]any); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]any)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, token)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_Mapping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mapping'
|
||||
@@ -166,36 +157,23 @@ type Service_Mapping_Call struct {
|
||||
}
|
||||
|
||||
// Mapping is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - token string
|
||||
func (_e *Service_Expecter) Mapping(ctx interface{}, token interface{}) *Service_Mapping_Call {
|
||||
return &Service_Mapping_Call{Call: _e.mock.On("Mapping", ctx, token)}
|
||||
func (_e *Service_Expecter) Mapping() *Service_Mapping_Call {
|
||||
return &Service_Mapping_Call{Call: _e.mock.On("Mapping")}
|
||||
}
|
||||
|
||||
func (_c *Service_Mapping_Call) Run(run func(ctx context.Context, token string)) *Service_Mapping_Call {
|
||||
func (_c *Service_Mapping_Call) Run(run func()) *Service_Mapping_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_Mapping_Call) Return(stringToV map[string]any, err error) *Service_Mapping_Call {
|
||||
_c.Call.Return(stringToV, err)
|
||||
func (_c *Service_Mapping_Call) Return(stringToV map[string]any) *Service_Mapping_Call {
|
||||
_c.Call.Return(stringToV)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_Mapping_Call) RunAndReturn(run func(ctx context.Context, token string) (map[string]any, error)) *Service_Mapping_Call {
|
||||
func (_c *Service_Mapping_Call) RunAndReturn(run func() map[string]any) *Service_Mapping_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
+22
-34
@@ -27,25 +27,22 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("unauthorized access")
|
||||
ErrFailedToCreateToken = errors.New("failed to create access token")
|
||||
ErrEmptyClientsList = errors.New("clients list in configuration empty")
|
||||
ErrClientUpdate = errors.New("failed to update client")
|
||||
ErrEmptyChannelsList = errors.New("channels list in configuration is empty")
|
||||
ErrFailedChannelCreation = errors.New("failed to create channel")
|
||||
ErrFailedChannelRetrieval = errors.New("failed to retrieve channel")
|
||||
ErrFailedClientCreation = errors.New("failed to create client")
|
||||
ErrFailedClientRetrieval = errors.New("failed to retrieve client")
|
||||
ErrMissingCredentials = errors.New("missing credentials")
|
||||
ErrFailedBootstrapRetrieval = errors.New("failed to retrieve bootstrap")
|
||||
ErrFailedCertCreation = errors.New("failed to create certificates")
|
||||
ErrFailedCertView = errors.New("failed to view certificate")
|
||||
ErrFailedBootstrap = errors.New("failed to create bootstrap config")
|
||||
ErrFailedBootstrapValidate = errors.New("failed to validate bootstrap config creation")
|
||||
ErrGatewayUpdate = errors.New("failed to updated gateway metadata")
|
||||
|
||||
limit uint = 10
|
||||
offset uint = 0
|
||||
ErrUnauthorized = errors.NewAuthNError("unauthorized access")
|
||||
ErrFailedToCreateToken = errors.NewAuthNError("failed to create access token")
|
||||
ErrEmptyClientsList = errors.NewRequestError("clients list in configuration empty")
|
||||
ErrClientUpdate = errors.NewRequestError("failed to update client")
|
||||
ErrEmptyChannelsList = errors.NewRequestError("channels list in configuration is empty")
|
||||
ErrFailedChannelCreation = errors.NewRequestError("failed to create channel")
|
||||
ErrFailedChannelRetrieval = errors.NewRequestError("failed to retrieve channel")
|
||||
ErrFailedClientCreation = errors.NewRequestError("failed to create client")
|
||||
ErrFailedClientRetrieval = errors.NewRequestError("failed to retrieve client")
|
||||
ErrMissingCredentials = errors.NewRequestError("missing credentials")
|
||||
ErrFailedBootstrapRetrieval = errors.NewServiceError("failed to retrieve bootstrap")
|
||||
ErrFailedCertCreation = errors.NewServiceError("failed to create certificates")
|
||||
ErrFailedCertView = errors.NewServiceError("failed to view certificate")
|
||||
ErrFailedBootstrap = errors.NewServiceError("failed to create bootstrap config")
|
||||
ErrFailedBootstrapValidate = errors.NewServiceError("failed to validate bootstrap config creation")
|
||||
ErrGatewayUpdate = errors.NewServiceError("failed to update gateway metadata")
|
||||
)
|
||||
|
||||
var _ Service = (*provisionService)(nil)
|
||||
@@ -63,7 +60,7 @@ type Service interface {
|
||||
// Mapping returns current configuration used for provision
|
||||
// useful for using in ui to create configuration that matches
|
||||
// one created with Provision method.
|
||||
Mapping(ctx context.Context, token string) (map[string]any, error)
|
||||
Mapping() map[string]any
|
||||
|
||||
// Certs creates certificate for clients that communicate over mTLS
|
||||
// A duration string is a possibly signed sequence of decimal numbers,
|
||||
@@ -101,17 +98,8 @@ func New(cfg Config, mgsdk sdk.SDK, certsSdk csdk.SDK, logger *slog.Logger) Serv
|
||||
}
|
||||
|
||||
// Mapping retrieves current configuration.
|
||||
func (ps *provisionService) Mapping(ctx context.Context, token string) (map[string]any, error) {
|
||||
pm := smqSDK.PageMetadata{
|
||||
Offset: uint64(offset),
|
||||
Limit: uint64(limit),
|
||||
}
|
||||
|
||||
if _, err := ps.sdk.Users(ctx, pm, token); err != nil {
|
||||
return map[string]any{}, errors.Wrap(ErrUnauthorized, err)
|
||||
}
|
||||
|
||||
return ps.conf.Bootstrap.Content, nil
|
||||
func (ps *provisionService) Mapping() map[string]any {
|
||||
return ps.conf.Bootstrap.Content
|
||||
}
|
||||
|
||||
// Provision is provision method for creating setup according to
|
||||
@@ -119,7 +107,7 @@ func (ps *provisionService) Mapping(ctx context.Context, token string) (map[stri
|
||||
func (ps *provisionService) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res Result, err error) {
|
||||
var channels []smqSDK.Channel
|
||||
var clients []smqSDK.Client
|
||||
defer ps.recover(ctx, &err, &clients, &channels, &domainID, &token)
|
||||
defer ps.recover(ctx, &err, &clients, &channels, domainID, token)
|
||||
|
||||
token, err = ps.createTokenIfEmpty(ctx, token)
|
||||
if err != nil {
|
||||
@@ -360,11 +348,11 @@ func clean(ctx context.Context, ps *provisionService, clients []smqSDK.Client, c
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, dm, tkn *string) {
|
||||
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, domainID, token string) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
clients, channels, domainID, token, err := *ths, *chs, *dm, *tkn, *e
|
||||
clients, channels, err := *ths, *chs, *e
|
||||
|
||||
if errors.Contains(err, ErrFailedClientRetrieval) || errors.Contains(err, ErrFailedChannelCreation) {
|
||||
for _, c := range clients {
|
||||
|
||||
@@ -31,35 +31,22 @@ func TestMapping(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
content map[string]any
|
||||
sdkerr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid token",
|
||||
token: validToken,
|
||||
desc: "valid request",
|
||||
content: validConfig.Bootstrap.Content,
|
||||
sdkerr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid token",
|
||||
token: "invalid",
|
||||
content: map[string]any{},
|
||||
sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401),
|
||||
err: provision.ErrUnauthorized,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
pm := smqSDK.PageMetadata{Offset: uint64(0), Limit: uint64(10)}
|
||||
repocall := mgsdk.On("Users", mock.Anything, pm, c.token).Return(smqSDK.UsersPage{}, c.sdkerr)
|
||||
content, err := svc.Mapping(context.Background(), c.token)
|
||||
assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected error %v, got %v", c.err, err))
|
||||
content := svc.Mapping()
|
||||
assert.Equal(t, c.content, content)
|
||||
repocall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ func addRuleEndpoint(s re.Service) endpoint.Endpoint {
|
||||
if err := req.validate(); err != nil {
|
||||
return addRuleRes{}, err
|
||||
}
|
||||
rule, err := s.AddRule(ctx, session, req.Rule)
|
||||
rule, _, err := s.AddRule(ctx, session, req.Rule)
|
||||
if err != nil {
|
||||
return addRuleRes{}, err
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func viewRuleEndpoint(s re.Service) endpoint.Endpoint {
|
||||
if err := req.validate(); err != nil {
|
||||
return viewRuleRes{}, err
|
||||
}
|
||||
rule, err := s.ViewRule(ctx, session, req.id)
|
||||
rule, err := s.ViewRule(ctx, session, req.id, req.withRoles)
|
||||
if err != nil {
|
||||
return viewRuleRes{}, err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -236,7 +237,7 @@ func TestAddRuleEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("AddRule", mock.Anything, tc.authnRes, tc.rule).Return(tc.svcRes, tc.svcErr)
|
||||
svcCall := svc.On("AddRule", mock.Anything, tc.authnRes, tc.rule).Return(tc.svcRes, []roles.RoleProvision{}, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
@@ -334,7 +335,7 @@ func TestViewRuleEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("ViewRule", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
svcCall := svc.On("ViewRule", mock.Anything, tc.authnRes, tc.id, false).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ func (req addRuleReq) validate() error {
|
||||
}
|
||||
|
||||
type viewRuleReq struct {
|
||||
id string
|
||||
id string
|
||||
withRoles bool
|
||||
}
|
||||
|
||||
func (req viewRuleReq) validate() error {
|
||||
|
||||
+12
-1
@@ -16,6 +16,7 @@ import (
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
roleManagerHttp "github.com/absmach/supermq/pkg/roles/rolemanager/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
@@ -36,6 +37,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
|
||||
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
|
||||
r.Route("/{domainID}", func(r chi.Router) {
|
||||
r.Route("/rules", func(r chi.Router) {
|
||||
d := roleManagerHttp.NewDecoder("ruleID")
|
||||
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
addRuleEndpoint(svc),
|
||||
decodeAddRuleRequest,
|
||||
@@ -50,6 +53,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
|
||||
opts...,
|
||||
), "list_rules").ServeHTTP)
|
||||
|
||||
r = roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts)
|
||||
|
||||
r.Route("/{ruleID}", func(r chi.Router) {
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewRuleEndpoint(svc),
|
||||
@@ -99,6 +104,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "disable_rule").ServeHTTP)
|
||||
|
||||
roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -123,7 +130,11 @@ func decodeAddRuleRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
|
||||
func decodeViewRuleRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
id := chi.URLParam(r, ruleIdKey)
|
||||
return viewRuleReq{id: id}, nil
|
||||
withRoles, err := apiutil.ReadBoolQuery(r, api.RolesKey, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
return viewRuleReq{id: id, withRoles: withRoles}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateRuleRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
|
||||
import "github.com/absmach/supermq/pkg/roles"
|
||||
|
||||
const BuiltInRoleAdmin roles.BuiltInRoleName = "admin"
|
||||
+4
-1
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,7 +60,8 @@ func (bre baseRuleEvent) Encode() map[string]any {
|
||||
}
|
||||
|
||||
type createRuleEvent struct {
|
||||
rule re.Rule
|
||||
rule re.Rule
|
||||
rolesProvisioned []roles.RoleProvision
|
||||
baseRuleEvent
|
||||
}
|
||||
|
||||
@@ -70,6 +72,7 @@ func (cre createRuleEvent) Encode() (map[string]any, error) {
|
||||
}
|
||||
maps.Copy(val, cre.baseRuleEvent.Encode())
|
||||
val["operation"] = ruleCreate
|
||||
val["roles_provisioned"] = cre.rolesProvisioned
|
||||
return val, nil
|
||||
}
|
||||
|
||||
|
||||
+18
-11
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
"github.com/absmach/supermq/pkg/events/store"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rmEvents "github.com/absmach/supermq/pkg/roles/rolemanager/events"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
@@ -32,6 +34,7 @@ var _ re.Service = (*eventStore)(nil)
|
||||
type eventStore struct {
|
||||
events.Publisher
|
||||
svc re.Service
|
||||
rmEvents.RoleManagerEventStore
|
||||
}
|
||||
|
||||
// NewEventStoreMiddleware returns wrapper around rules service that sends
|
||||
@@ -42,25 +45,29 @@ func NewEventStoreMiddleware(ctx context.Context, svc re.Service, url string) (r
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := rmEvents.NewRoleManagerEventStore("rules", rulePrefix, svc, publisher)
|
||||
|
||||
return &eventStore{
|
||||
svc: svc,
|
||||
Publisher: publisher,
|
||||
svc: svc,
|
||||
Publisher: publisher,
|
||||
RoleManagerEventStore: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (es *eventStore) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
rule, err := es.svc.AddRule(ctx, session, r)
|
||||
func (es *eventStore) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
|
||||
rule, rps, err := es.svc.AddRule(ctx, session, r)
|
||||
if err != nil {
|
||||
return rule, err
|
||||
return rule, rps, err
|
||||
}
|
||||
event := createRuleEvent{
|
||||
rule: rule,
|
||||
baseRuleEvent: newBaseRuleEvent(session, middleware.GetReqID(ctx)),
|
||||
rule: rule,
|
||||
rolesProvisioned: rps,
|
||||
baseRuleEvent: newBaseRuleEvent(session, middleware.GetReqID(ctx)),
|
||||
}
|
||||
if err := es.Publish(ctx, CreateStream, event); err != nil {
|
||||
return rule, err
|
||||
return rule, rps, err
|
||||
}
|
||||
return rule, nil
|
||||
return rule, rps, nil
|
||||
}
|
||||
|
||||
func (es *eventStore) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
|
||||
@@ -78,8 +85,8 @@ func (es *eventStore) ListRules(ctx context.Context, session authn.Session, pm r
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (es *eventStore) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
rule, err := es.svc.ViewRule(ctx, session, id)
|
||||
func (es *eventStore) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
|
||||
rule, err := es.svc.ViewRule(ctx, session, id, withRoles)
|
||||
if err != nil {
|
||||
return rule, err
|
||||
}
|
||||
|
||||
+18
-2
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
@@ -19,6 +20,11 @@ import (
|
||||
|
||||
const logicFunction = "main.logicFunction"
|
||||
|
||||
var (
|
||||
goKeywordRegex = regexp.MustCompile(`\bgo\s+func\s*\(|^\s*go\s+\w+\(|[;\s{]go\s+func\s*\(|[;\s{]go\s+\w+\(`)
|
||||
panicRegex = regexp.MustCompile(`\bpanic\s*\(`)
|
||||
)
|
||||
|
||||
// Type message is an SMQ message with payload replaces by JSON deserialized payload.
|
||||
type message struct {
|
||||
Channel string `json:"channel,omitempty"`
|
||||
@@ -30,7 +36,17 @@ type message struct {
|
||||
Payload any `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *messaging.Message) pkglog.RunInfo {
|
||||
func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *messaging.Message) (ret pkglog.RunInfo) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = pkglog.RunInfo{
|
||||
Level: slog.LevelError,
|
||||
Details: details,
|
||||
Message: fmt.Sprintf("panic in Go script: %v", r),
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
i := golang.New(golang.Options{})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
return pkglog.RunInfo{Level: slog.LevelError, Details: details, Message: err.Error()}
|
||||
@@ -77,7 +93,7 @@ func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *m
|
||||
err = errors.Wrap(e, err)
|
||||
}
|
||||
}
|
||||
ret := pkglog.RunInfo{Level: slog.LevelInfo, Details: details, Message: "rule processed successfully"}
|
||||
ret = pkglog.RunInfo{Level: slog.LevelInfo, Details: details, Message: "rule processed successfully"}
|
||||
if err != nil {
|
||||
ret.Level = slog.LevelError
|
||||
ret.Message = fmt.Sprintf("failed to handle rule output: %s", err)
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ func (re *re) Handle(msg *messaging.Message) error {
|
||||
Scheduled: &scheduledFalse,
|
||||
}
|
||||
ctx := context.Background()
|
||||
page, err := re.repo.ListRules(ctx, pm)
|
||||
page, err := re.repo.ListAllRules(ctx, pm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
|
||||
ScheduledBefore: &due,
|
||||
}
|
||||
|
||||
page, err := re.repo.ListRules(ctx, pm)
|
||||
page, err := re.repo.ListAllRules(ctx, pm)
|
||||
if err != nil {
|
||||
re.runInfo <- pkglog.RunInfo{
|
||||
Level: slog.LevelError,
|
||||
|
||||
@@ -7,12 +7,16 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/magistrala/re/operations"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
smqauthz "github.com/absmach/supermq/pkg/authz"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rolemgr "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,36 +27,47 @@ var (
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc re.Service
|
||||
authz smqauthz.Authorization
|
||||
svc re.Service
|
||||
authz smqauthz.Authorization
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
rolemgr.RoleManagerAuthorizationMiddleware
|
||||
}
|
||||
|
||||
// AuthorizationMiddleware adds authorization to the re service.
|
||||
func AuthorizationMiddleware(svc re.Service, authz smqauthz.Authorization) (re.Service, error) {
|
||||
func AuthorizationMiddleware(svc re.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (re.Service, error) {
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ram, err := rolemgr.NewAuthorization(operations.EntityType, svc, authz, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
entitiesOps: entitiesOps,
|
||||
RoleManagerAuthorizationMiddleware: ram,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpAddRule, session); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainCreateRules, err)
|
||||
func (am *authorizationMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
|
||||
if err := am.authorize(ctx, operations.OpAddRule, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return re.Rule{}, nil, errors.Wrap(errDomainCreateRules, err)
|
||||
}
|
||||
|
||||
return am.svc.AddRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpViewRule, session); err != nil {
|
||||
func (am *authorizationMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, operations.OpViewRule, session, operations.EntityType, id); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainViewRules, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewRule(ctx, session, id)
|
||||
return am.svc.ViewRule(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpUpdateRule, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpUpdateRule, session, operations.EntityType, r.ID); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
|
||||
}
|
||||
|
||||
@@ -60,7 +75,7 @@ func (am *authorizationMiddleware) UpdateRule(ctx context.Context, session authn
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpUpdateRuleTags, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpUpdateRuleTags, session, operations.EntityType, r.ID); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
|
||||
}
|
||||
|
||||
@@ -68,7 +83,7 @@ func (am *authorizationMiddleware) UpdateRuleTags(ctx context.Context, session a
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpUpdateRuleSchedule, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpUpdateRuleSchedule, session, operations.EntityType, r.ID); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
|
||||
}
|
||||
|
||||
@@ -76,15 +91,19 @@ func (am *authorizationMiddleware) UpdateRuleSchedule(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
|
||||
if err := am.authorize(ctx, re.OpListRules, session); err != nil {
|
||||
return re.Page{}, errors.Wrap(errDomainViewRules, err)
|
||||
switch err := am.checkSuperAdmin(ctx, session); {
|
||||
case err == nil:
|
||||
session.SuperAdmin = true
|
||||
case errors.Contains(err, svcerr.ErrSuperAdminAction):
|
||||
default:
|
||||
return re.Page{}, err
|
||||
}
|
||||
|
||||
return am.svc.ListRules(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, re.OpRemoveRule, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpRemoveRule, session, operations.EntityType, id); err != nil {
|
||||
return errors.Wrap(errDomainDeleteRules, err)
|
||||
}
|
||||
|
||||
@@ -92,7 +111,7 @@ func (am *authorizationMiddleware) RemoveRule(ctx context.Context, session authn
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpEnableRule, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpEnableRule, session, operations.EntityType, id); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
|
||||
}
|
||||
|
||||
@@ -100,7 +119,7 @@ func (am *authorizationMiddleware) EnableRule(ctx context.Context, session authn
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
if err := am.authorize(ctx, re.OpDisableRule, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpDisableRule, session, operations.EntityType, id); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
|
||||
}
|
||||
|
||||
@@ -119,23 +138,54 @@ func (am *authorizationMiddleware) Cancel() error {
|
||||
return am.svc.Cancel()
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session) error {
|
||||
perm, err := re.GetPermission(op)
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr := smqauthz.PolicyReq{
|
||||
TokenType: session.Type,
|
||||
UserID: session.UserID,
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: perm,
|
||||
Object: obj,
|
||||
ObjectType: objType,
|
||||
Permission: perm.String(),
|
||||
}
|
||||
|
||||
return am.authz.Authorize(ctx, pr)
|
||||
var pat *smqauthz.PATReq
|
||||
if session.PatID != "" {
|
||||
opName := am.entitiesOps.OperationName(operations.EntityType, op)
|
||||
pat = &smqauthz.PATReq{
|
||||
UserID: session.UserID,
|
||||
PatID: session.PatID,
|
||||
EntityID: session.DomainID,
|
||||
EntityType: operations.EntityType,
|
||||
Operation: opName,
|
||||
Domain: session.DomainID,
|
||||
}
|
||||
}
|
||||
|
||||
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
|
||||
if session.Role != authn.SuperAdminRole {
|
||||
return svcerr.ErrSuperAdminAction
|
||||
}
|
||||
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
|
||||
SubjectType: policies.UserType,
|
||||
Subject: session.UserID,
|
||||
Permission: policies.AdminPermission,
|
||||
ObjectType: policies.PlatformType,
|
||||
Object: policies.SuperMQObject,
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+38
-20
@@ -7,52 +7,70 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
mgPolicies "github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/magistrala/re/operations"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/callout"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var _ re.Service = (*calloutMiddleware)(nil)
|
||||
|
||||
type calloutMiddleware struct {
|
||||
svc re.Service
|
||||
callout callout.Callout
|
||||
svc re.Service
|
||||
callout callout.Callout
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
rolemw.RoleManagerCalloutMiddleware
|
||||
}
|
||||
|
||||
const entityType = "rule"
|
||||
|
||||
func NewCallout(svc re.Service, callout callout.Callout) (re.Service, error) {
|
||||
func NewCallout(svc re.Service, callout callout.Callout, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (re.Service, error) {
|
||||
call, err := rolemw.NewCallout(mgPolicies.RulesType, svc, callout, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &calloutMiddleware{
|
||||
svc: svc,
|
||||
callout: callout,
|
||||
svc: svc,
|
||||
callout: callout,
|
||||
entitiesOps: entitiesOps,
|
||||
RoleManagerCalloutMiddleware: call,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
func (cm *calloutMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
|
||||
params := map[string]any{
|
||||
"entities": r,
|
||||
"count": 1,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpAddRuleStr, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
if err := cm.callOut(ctx, session, operations.OpAddRule, params); err != nil {
|
||||
return re.Rule{}, nil, err
|
||||
}
|
||||
|
||||
return cm.svc.AddRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
func (cm *calloutMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpViewRuleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpViewRule, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
return cm.svc.ViewRule(ctx, session, id)
|
||||
return cm.svc.ViewRule(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
@@ -60,7 +78,7 @@ func (cm *calloutMiddleware) UpdateRule(ctx context.Context, session authn.Sessi
|
||||
"entity_id": r.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpUpdateRuleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateRule, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
@@ -72,7 +90,7 @@ func (cm *calloutMiddleware) UpdateRuleTags(ctx context.Context, session authn.S
|
||||
"entity_id": r.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpUpdateRuleTagsStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateRuleTags, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
@@ -84,7 +102,7 @@ func (cm *calloutMiddleware) UpdateRuleSchedule(ctx context.Context, session aut
|
||||
"entity_id": r.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpUpdateRuleScheduleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateRuleSchedule, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
@@ -96,7 +114,7 @@ func (cm *calloutMiddleware) ListRules(ctx context.Context, session authn.Sessio
|
||||
"pagemeta": pm,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpListRulesStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpListRules, params); err != nil {
|
||||
return re.Page{}, err
|
||||
}
|
||||
|
||||
@@ -108,7 +126,7 @@ func (cm *calloutMiddleware) RemoveRule(ctx context.Context, session authn.Sessi
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpRemoveRuleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpRemoveRule, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -120,7 +138,7 @@ func (cm *calloutMiddleware) EnableRule(ctx context.Context, session authn.Sessi
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpEnableRuleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpEnableRule, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
@@ -132,7 +150,7 @@ func (cm *calloutMiddleware) DisableRule(ctx context.Context, session authn.Sess
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, re.OpDisableRuleStr, params); err != nil {
|
||||
if err := cm.callOut(ctx, session, operations.OpDisableRule, params); err != nil {
|
||||
return re.Rule{}, err
|
||||
}
|
||||
|
||||
@@ -151,7 +169,7 @@ func (cm *calloutMiddleware) Cancel() error {
|
||||
return cm.svc.Cancel()
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session, op string, pld map[string]any) error {
|
||||
func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session, op permissions.Operation, pld map[string]any) error {
|
||||
var entityID string
|
||||
if id, ok := pld["entity_id"].(string); ok {
|
||||
entityID = id
|
||||
@@ -159,7 +177,7 @@ func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session,
|
||||
|
||||
req := callout.Request{
|
||||
BaseRequest: callout.BaseRequest{
|
||||
Operation: op,
|
||||
Operation: cm.entitiesOps.OperationName(entityType, op),
|
||||
EntityType: entityType,
|
||||
EntityID: entityID,
|
||||
CallerID: session.UserID,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var _ re.Service = (*loggingMiddleware)(nil)
|
||||
@@ -19,13 +23,18 @@ var _ re.Service = (*loggingMiddleware)(nil)
|
||||
type loggingMiddleware struct {
|
||||
logger *slog.Logger
|
||||
svc re.Service
|
||||
rolemw.RoleManagerLoggingMiddleware
|
||||
}
|
||||
|
||||
func LoggingMiddleware(svc re.Service, logger *slog.Logger) re.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
return &loggingMiddleware{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
RoleManagerLoggingMiddleware: rolemw.NewLogging("re", svc, logger),
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, err error) {
|
||||
func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, rps []roles.RoleProvision, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
@@ -39,10 +48,11 @@ func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session,
|
||||
}
|
||||
lm.logger.Info("Add rule completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.AddRule(ctx, session, r)
|
||||
res, rps, err = lm.svc.AddRule(ctx, session, r)
|
||||
return
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (res re.Rule, err error) {
|
||||
func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (res re.Rule, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
@@ -59,7 +69,7 @@ func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session
|
||||
}
|
||||
lm.logger.Info("View rule completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ViewRule(ctx, session, id)
|
||||
return lm.svc.ViewRule(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, err error) {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
service re.Service
|
||||
rolemw.RoleManagerMetricsMiddleware
|
||||
}
|
||||
|
||||
var _ re.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
func NewMetricsMiddleware(counter metrics.Counter, latency metrics.Histogram, service re.Service) re.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
service: service,
|
||||
RoleManagerMetricsMiddleware: rolemw.NewMetrics("re", service, counter, latency),
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "add_rule").Add(1)
|
||||
mm.latency.With("method", "add_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.AddRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "view_rule").Add(1)
|
||||
mm.latency.With("method", "view_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ViewRule(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_rule").Add(1)
|
||||
mm.latency.With("method", "update_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_rule_tags").Add(1)
|
||||
mm.latency.With("method", "update_rule_tags").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateRuleTags(ctx, session, r)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_rule_schedule").Add(1)
|
||||
mm.latency.With("method", "update_rule_schedule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateRuleSchedule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "list_rules").Add(1)
|
||||
mm.latency.With("method", "list_rules").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ListRules(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "remove_rule").Add(1)
|
||||
mm.latency.With("method", "remove_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.RemoveRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "enable_rule").Add(1)
|
||||
mm.latency.With("method", "enable_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.EnableRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "disable_rule").Add(1)
|
||||
mm.latency.With("method", "disable_rule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.DisableRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) Handle(msg *messaging.Message) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "handle").Add(1)
|
||||
mm.latency.With("method", "handle").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.Handle(msg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) StartScheduler(ctx context.Context) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "start_scheduler").Add(1)
|
||||
mm.latency.With("method", "start_scheduler").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) Cancel() error {
|
||||
return mm.service.Cancel()
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
smqTracing "github.com/absmach/supermq/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type tracingMiddleware struct {
|
||||
tracer trace.Tracer
|
||||
svc re.Service
|
||||
rolemw.RoleManagerTracing
|
||||
}
|
||||
|
||||
var _ re.Service = (*tracingMiddleware)(nil)
|
||||
|
||||
func NewTracingMiddleware(tracer trace.Tracer, svc re.Service) re.Service {
|
||||
return &tracingMiddleware{
|
||||
tracer: tracer,
|
||||
svc: svc,
|
||||
RoleManagerTracing: rolemw.NewTracing("re", svc, tracer),
|
||||
}
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "add_rule", trace.WithAttributes(
|
||||
attribute.String("name", r.Name),
|
||||
attribute.String("domain_id", r.DomainID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.AddRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "view_rule", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ViewRule(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule", trace.WithAttributes(
|
||||
attribute.String("id", r.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateRule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule_tags", trace.WithAttributes(
|
||||
attribute.String("id", r.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateRuleTags(ctx, session, r)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule_schedule", trace.WithAttributes(
|
||||
attribute.String("id", r.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateRuleSchedule(ctx, session, r)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "list_rules", trace.WithAttributes(
|
||||
attribute.Int("offset", int(pm.Offset)),
|
||||
attribute.Int("limit", int(pm.Limit)),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ListRules(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "remove_rule", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.RemoveRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "enable_rule", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.EnableRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "disable_rule", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.DisableRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) Handle(msg *messaging.Message) error {
|
||||
_, span := smqTracing.StartSpan(context.Background(), tm.tracer, "handle", trace.WithAttributes(
|
||||
attribute.String("channel", msg.Channel),
|
||||
attribute.String("subtopic", msg.Subtopic),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.Handle(msg)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) StartScheduler(ctx context.Context) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "start_scheduler")
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) Cancel() error {
|
||||
return tm.svc.Cancel()
|
||||
}
|
||||
+1509
-11
File diff suppressed because it is too large
Load Diff
+1517
-21
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
)
|
||||
|
||||
const (
|
||||
OpAddRule permissions.Operation = iota
|
||||
OpViewRule
|
||||
OpUpdateRule
|
||||
OpUpdateRuleTags
|
||||
OpUpdateRuleSchedule
|
||||
OpListRules
|
||||
OpRemoveRule
|
||||
OpEnableRule
|
||||
OpDisableRule
|
||||
)
|
||||
|
||||
const (
|
||||
OpAddRuleStr = "OpAddRule"
|
||||
OpViewRuleStr = "OpViewRule"
|
||||
OpUpdateRuleStr = "OpUpdateRule"
|
||||
OpUpdateRuleTagsStr = "OpUpdateRuleTags"
|
||||
OpUpdateRuleScheduleStr = "OpUpdateRuleSchedule"
|
||||
OpListRulesStr = "OpListRules"
|
||||
OpRemoveRuleStr = "OpRemoveRule"
|
||||
OpEnableRuleStr = "OpEnableRule"
|
||||
OpDisableRuleStr = "OpDisableRule"
|
||||
)
|
||||
|
||||
func GetPermission(op permissions.Operation) (string, error) {
|
||||
if op < OpAddRule || op > OpDisableRule {
|
||||
return "", errors.New("invalid operation")
|
||||
}
|
||||
return policies.MembershipPermission, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package operations
|
||||
|
||||
import "github.com/absmach/supermq/pkg/permissions"
|
||||
|
||||
const EntityType = "rule"
|
||||
|
||||
// Rule Operations.
|
||||
const (
|
||||
OpAddRule permissions.Operation = iota
|
||||
OpViewRule
|
||||
OpUpdateRule
|
||||
OpUpdateRuleTags
|
||||
OpUpdateRuleSchedule
|
||||
OpRemoveRule
|
||||
OpListRules
|
||||
OpEnableRule
|
||||
OpDisableRule
|
||||
)
|
||||
|
||||
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
|
||||
return map[permissions.Operation]permissions.OperationDetails{
|
||||
OpAddRule: {
|
||||
Name: "add",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpViewRule: {
|
||||
Name: "view",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateRule: {
|
||||
Name: "update",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateRuleTags: {
|
||||
Name: "update_tags",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateRuleSchedule: {
|
||||
Name: "update_schedule",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpRemoveRule: {
|
||||
Name: "delete",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpListRules: {
|
||||
Name: "list",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpEnableRule: {
|
||||
Name: "enable",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpDisableRule: {
|
||||
Name: "disable",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
+33
-2
@@ -4,12 +4,20 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
func Migration() *migrate.MemoryMigrationSource {
|
||||
return &migrate.MemoryMigrationSource{
|
||||
func Migration() (*migrate.MemoryMigrationSource, error) {
|
||||
rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName)
|
||||
if err != nil {
|
||||
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
|
||||
}
|
||||
rulesMigration := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "rules_01",
|
||||
@@ -50,6 +58,29 @@ func Migration() *migrate.MemoryMigrationSource {
|
||||
`ALTER TABLE rules DROP COLUMN tags;`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "rules_03",
|
||||
Up: []string{
|
||||
`UPDATE rules
|
||||
SET metadata = (COALESCE(metadata, '{}'::jsonb) - 'ui') || jsonb_build_object('flow', metadata->'ui')
|
||||
WHERE metadata ? 'ui' AND jsonb_typeof(metadata->'ui') = 'string'`,
|
||||
},
|
||||
Down: []string{
|
||||
`UPDATE rules
|
||||
SET metadata = (COALESCE(metadata, '{}'::jsonb) - 'flow') || jsonb_build_object('ui', metadata->'flow')
|
||||
WHERE metadata ? 'flow' AND jsonb_typeof(metadata->'flow') = 'string'`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulesMigration.Migrations = append(rulesMigration.Migrations, rolesMigration.Migrations...)
|
||||
|
||||
domainsMigration, err := dpostgres.Migration()
|
||||
if err != nil {
|
||||
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
|
||||
}
|
||||
rulesMigration.Migrations = append(rulesMigration.Migrations, domainsMigration.Migrations...)
|
||||
|
||||
return rulesMigration, nil
|
||||
}
|
||||
|
||||
+251
-27
@@ -10,19 +10,32 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mgPolicies "github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/re"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
"github.com/absmach/supermq/pkg/postgres"
|
||||
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
|
||||
)
|
||||
|
||||
const (
|
||||
rolesTableNamePrefix = "rules"
|
||||
entityTableName = "rules"
|
||||
entityIDColumnName = "id"
|
||||
)
|
||||
|
||||
type PostgresRepository struct {
|
||||
DB postgres.Database
|
||||
rolesPostgres.Repository
|
||||
}
|
||||
|
||||
func NewRepository(db postgres.Database) re.Repository {
|
||||
return &PostgresRepository{DB: db}
|
||||
rolesRepo := rolesPostgres.NewRepository(db, mgPolicies.RulesType, rolesTableNamePrefix, entityTableName, entityIDColumnName)
|
||||
return &PostgresRepository{
|
||||
DB: db,
|
||||
Repository: rolesRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) AddRule(ctx context.Context, r re.Rule) (re.Rule, error) {
|
||||
@@ -82,6 +95,157 @@ func (repo *PostgresRepository) ViewRule(ctx context.Context, id string) (re.Rul
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) RetrieveByIDWithRoles(ctx context.Context, id, memberID string) (re.Rule, error) {
|
||||
query := `
|
||||
WITH selected_rule AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.domain_id
|
||||
FROM
|
||||
rules r
|
||||
WHERE
|
||||
r.id = :id
|
||||
LIMIT 1
|
||||
),
|
||||
selected_rule_roles AS (
|
||||
SELECT
|
||||
rr.entity_id AS rule_id,
|
||||
rrm.member_id AS member_id,
|
||||
rr.id AS role_id,
|
||||
rr."name" AS role_name,
|
||||
jsonb_agg(DISTINCT rra."action") AS actions,
|
||||
'direct' AS access_type,
|
||||
'' AS access_provider_id
|
||||
FROM
|
||||
rules_roles rr
|
||||
JOIN
|
||||
rules_role_members rrm ON rr.id = rrm.role_id
|
||||
JOIN
|
||||
rules_role_actions rra ON rr.id = rra.role_id
|
||||
JOIN
|
||||
selected_rule sr ON sr.id = rr.entity_id
|
||||
AND rrm.member_id = :member_id
|
||||
GROUP BY
|
||||
rr.entity_id, rr.id, rr.name, rrm.member_id
|
||||
),
|
||||
selected_domain_roles AS (
|
||||
SELECT
|
||||
sr.id AS rule_id,
|
||||
drm.member_id AS member_id,
|
||||
dr.id AS role_id,
|
||||
dr."name" AS role_name,
|
||||
jsonb_agg(DISTINCT all_actions."action") AS actions,
|
||||
'domain' AS access_type,
|
||||
dr.entity_id AS access_provider_id
|
||||
FROM
|
||||
domains d
|
||||
JOIN
|
||||
selected_rule sr ON sr.domain_id = d.id
|
||||
JOIN
|
||||
domains_roles dr ON dr.entity_id = d.id
|
||||
JOIN
|
||||
domains_role_members drm ON dr.id = drm.role_id
|
||||
JOIN
|
||||
domains_role_actions dra ON dr.id = dra.role_id
|
||||
JOIN
|
||||
domains_role_actions all_actions ON dr.id = all_actions.role_id
|
||||
WHERE
|
||||
drm.member_id = :member_id
|
||||
AND dra."action" LIKE 'rule%'
|
||||
GROUP BY
|
||||
sr.id, dr.entity_id, dr.id, dr."name", drm.member_id
|
||||
),
|
||||
all_roles AS (
|
||||
SELECT
|
||||
srr.rule_id,
|
||||
srr.member_id,
|
||||
srr.role_id,
|
||||
srr.role_name,
|
||||
srr.actions,
|
||||
srr.access_type,
|
||||
srr.access_provider_id
|
||||
FROM
|
||||
selected_rule_roles srr
|
||||
UNION
|
||||
SELECT
|
||||
sdr.rule_id,
|
||||
sdr.member_id,
|
||||
sdr.role_id,
|
||||
sdr.role_name,
|
||||
sdr.actions,
|
||||
sdr.access_type,
|
||||
sdr.access_provider_id
|
||||
FROM
|
||||
selected_domain_roles sdr
|
||||
),
|
||||
final_roles AS (
|
||||
SELECT
|
||||
ar.rule_id,
|
||||
ar.member_id,
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'role_id', ar.role_id,
|
||||
'role_name', ar.role_name,
|
||||
'actions', ar.actions,
|
||||
'access_type', ar.access_type,
|
||||
'access_provider_id', ar.access_provider_id
|
||||
)
|
||||
) AS roles
|
||||
FROM all_roles ar
|
||||
GROUP BY
|
||||
ar.rule_id, ar.member_id
|
||||
)
|
||||
SELECT
|
||||
r2.id,
|
||||
r2."name",
|
||||
r2.domain_id,
|
||||
r2.tags,
|
||||
r2.metadata,
|
||||
r2.input_channel,
|
||||
r2.input_topic,
|
||||
r2.outputs,
|
||||
r2.status,
|
||||
r2.logic_type,
|
||||
r2.logic_value,
|
||||
r2.time,
|
||||
r2.recurring,
|
||||
r2.recurring_period,
|
||||
r2.start_datetime,
|
||||
r2.created_at,
|
||||
r2.created_by,
|
||||
r2.updated_at,
|
||||
r2.updated_by,
|
||||
fr.member_id,
|
||||
fr.roles
|
||||
FROM rules r2
|
||||
JOIN final_roles fr ON fr.rule_id = r2.id
|
||||
`
|
||||
parameters := map[string]any{
|
||||
"id": id,
|
||||
"member_id": memberID,
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, query, parameters)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
dbrule := dbRule{}
|
||||
if !row.Next() {
|
||||
return re.Rule{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
if err := row.StructScan(&dbrule); err != nil {
|
||||
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
r, err := dbToRule(dbrule)
|
||||
if err != nil {
|
||||
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateRuleStatus(ctx context.Context, r re.Rule) (re.Rule, error) {
|
||||
q := `UPDATE rules
|
||||
SET status = :status, updated_at = :updated_at, updated_by = :updated_by
|
||||
@@ -193,33 +357,10 @@ func (repo *PostgresRepository) RemoveRule(ctx context.Context, id string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ListRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
|
||||
pgData := ""
|
||||
if pm.Limit != 0 {
|
||||
pgData = "LIMIT :limit"
|
||||
}
|
||||
if pm.Offset != 0 {
|
||||
pgData += " OFFSET :offset"
|
||||
}
|
||||
func (repo *PostgresRepository) ListAllRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
|
||||
pq := pageRulesQuery(pm)
|
||||
|
||||
dir := api.DescDir
|
||||
if pm.Dir == api.AscDir {
|
||||
dir = api.AscDir
|
||||
}
|
||||
|
||||
orderClause := ""
|
||||
|
||||
switch pm.Order {
|
||||
case api.NameKey:
|
||||
orderClause = fmt.Sprintf("ORDER BY name %s, id %s", dir, dir)
|
||||
case api.CreatedAtOrder:
|
||||
orderClause = fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
|
||||
case api.UpdatedAtOrder:
|
||||
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
|
||||
default:
|
||||
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
|
||||
}
|
||||
orderClause := rulesOrderClause(pm)
|
||||
pgData := rulesPageData(pm)
|
||||
|
||||
q := fmt.Sprintf(`
|
||||
SELECT id, name, domain_id, tags, input_channel, input_topic, logic_type, logic_value, outputs,
|
||||
@@ -261,6 +402,62 @@ func (repo *PostgresRepository) ListRules(ctx context.Context, pm re.PageMeta) (
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ListUserRules(ctx context.Context, userID string, pm re.PageMeta) (re.Page, error) {
|
||||
pm.UserID = userID
|
||||
pq := pageRulesQuery(pm)
|
||||
orderClause := rulesOrderClause(pm)
|
||||
pgData := rulesPageData(pm)
|
||||
|
||||
userJoin := `
|
||||
INNER JOIN rules_roles rr ON rr.entity_id = r.id
|
||||
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
|
||||
`
|
||||
|
||||
innerQ := fmt.Sprintf(`
|
||||
SELECT DISTINCT r.id, r.name, r.domain_id, r.tags, r.input_channel, r.input_topic, r.logic_type, r.logic_value, r.outputs,
|
||||
r.start_datetime, r.time, r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status
|
||||
FROM rules r
|
||||
%s
|
||||
%s
|
||||
`, userJoin, pq)
|
||||
|
||||
q := fmt.Sprintf(`
|
||||
SELECT * FROM (%s) AS sub %s %s;
|
||||
`, innerQ, orderClause, pgData)
|
||||
|
||||
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
return re.Page{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var rules []re.Rule
|
||||
for rows.Next() {
|
||||
var r dbRule
|
||||
if err := rows.StructScan(&r); err != nil {
|
||||
return re.Page{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
ret, err := dbToRule(r)
|
||||
if err != nil {
|
||||
return re.Page{}, err
|
||||
}
|
||||
rules = append(rules, ret)
|
||||
}
|
||||
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM (%s) AS count_sub;`, innerQ)
|
||||
total, err := postgres.Total(ctx, repo.DB, cq, pm)
|
||||
if err != nil {
|
||||
return re.Page{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return re.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Rules: rules,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateRuleDue(ctx context.Context, id string, due time.Time) (re.Rule, error) {
|
||||
q := `
|
||||
UPDATE rules
|
||||
@@ -296,6 +493,33 @@ func (repo *PostgresRepository) UpdateRuleDue(ctx context.Context, id string, du
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func rulesOrderClause(pm re.PageMeta) string {
|
||||
dir := api.DescDir
|
||||
if pm.Dir == api.AscDir {
|
||||
dir = api.AscDir
|
||||
}
|
||||
|
||||
switch pm.Order {
|
||||
case api.NameKey:
|
||||
return fmt.Sprintf("ORDER BY name %s, id %s", dir, dir)
|
||||
case api.CreatedAtOrder:
|
||||
return fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
|
||||
default:
|
||||
return fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
|
||||
}
|
||||
}
|
||||
|
||||
func rulesPageData(pm re.PageMeta) string {
|
||||
pgData := ""
|
||||
if pm.Limit != 0 {
|
||||
pgData = "LIMIT :limit"
|
||||
}
|
||||
if pm.Offset != 0 {
|
||||
pgData += " OFFSET :offset"
|
||||
}
|
||||
return pgData
|
||||
}
|
||||
|
||||
func pageRulesQuery(pm re.PageMeta) string {
|
||||
var query []string
|
||||
if pm.InputChannel != "" {
|
||||
|
||||
@@ -890,7 +890,7 @@ func TestListRules(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
page, err := repo.ListRules(context.Background(), tc.pm)
|
||||
page, err := repo.ListAllRules(context.Background(), tc.pm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
return
|
||||
@@ -935,6 +935,192 @@ func TestListRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUserRules(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM rules")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewRepository(database)
|
||||
|
||||
domainID := generateUUID(t)
|
||||
userID := generateUUID(t)
|
||||
otherUserID := generateUUID(t)
|
||||
channelID := generateUUID(t)
|
||||
|
||||
// Create 10 rules; assign the first 4 to userID via a role.
|
||||
var allRules []re.Rule
|
||||
for i := range 10 {
|
||||
r := re.Rule{
|
||||
ID: generateUUID(t),
|
||||
Name: namegen.Generate(),
|
||||
DomainID: domainID,
|
||||
InputChannel: channelID,
|
||||
Logic: re.Script{Type: re.LuaType, Value: "return true"},
|
||||
Status: re.EnabledStatus,
|
||||
CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute).Truncate(time.Microsecond),
|
||||
CreatedBy: generateUUID(t),
|
||||
UpdatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute).Truncate(time.Microsecond),
|
||||
UpdatedBy: generateUUID(t),
|
||||
}
|
||||
rule, err := repo.AddRule(context.Background(), r)
|
||||
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
allRules = append(allRules, rule)
|
||||
}
|
||||
|
||||
// Assign userID to the first 4 rules via direct role INSERT.
|
||||
for i := range 4 {
|
||||
roleID := generateUUID(t)
|
||||
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", allRules[i].ID)
|
||||
assert.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, allRules[i].ID)
|
||||
assert.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
userID string
|
||||
pm re.PageMeta
|
||||
count int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list user rules returns only accessible rules",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 4,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules with offset",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 2,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 2,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules with limit",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 2,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 2,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules with domain filter",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Domain: domainID,
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 4,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules with channel filter",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
InputChannel: channelID,
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 4,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules with non-existing domain returns 0",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Domain: generateUUID(t),
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list rules for user with no role assignments returns 0",
|
||||
userID: otherUserID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules ordered by name ascending",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
Order: nameOrder,
|
||||
Dir: ascDir,
|
||||
},
|
||||
count: 4,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules ordered by created_at descending",
|
||||
userID: userID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
Order: createdAtOrder,
|
||||
Dir: descDir,
|
||||
},
|
||||
count: 4,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
page, err := repo.ListUserRules(context.Background(), tc.userID, tc.pm)
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
return
|
||||
}
|
||||
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
assert.Equal(t, tc.count, len(page.Rules), fmt.Sprintf("%s: expected %d rules, got %d", tc.desc, tc.count, len(page.Rules)))
|
||||
if len(page.Rules) > 1 {
|
||||
switch tc.pm.Order {
|
||||
case nameOrder:
|
||||
if tc.pm.Dir == ascDir {
|
||||
assert.True(t, sort.SliceIsSorted(page.Rules, func(i, j int) bool {
|
||||
return page.Rules[i].Name <= page.Rules[j].Name
|
||||
}), "Expected names to be sorted ascending")
|
||||
}
|
||||
case createdAtOrder:
|
||||
if tc.pm.Dir == descDir {
|
||||
assert.True(t, sort.SliceIsSorted(page.Rules, func(i, j int) bool {
|
||||
return page.Rules[i].CreatedAt.After(page.Rules[j].CreatedAt)
|
||||
}), "Expected created_at to be sorted descending")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRule(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM rules")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/jackc/pgtype"
|
||||
)
|
||||
|
||||
@@ -35,6 +36,8 @@ type dbRule struct {
|
||||
CreatedBy string `db:"created_by"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
UpdatedBy string `db:"updated_by"`
|
||||
MemberID string `db:"member_id,omitempty"`
|
||||
Roles json.RawMessage `db:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func ruleToDb(r re.Rule) (dbRule, error) {
|
||||
@@ -108,6 +111,13 @@ func dbToRule(dto dbRule) (re.Rule, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var roles []roles.MemberRoleActions
|
||||
if dto.Roles != nil {
|
||||
if err := json.Unmarshal(dto.Roles, &roles); err != nil {
|
||||
return re.Rule{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
return re.Rule{
|
||||
ID: dto.ID,
|
||||
Name: dto.Name,
|
||||
@@ -132,6 +142,7 @@ func dbToRule(dto dbRule) (re.Rule, error) {
|
||||
CreatedBy: dto.CreatedBy,
|
||||
UpdatedAt: dto.UpdatedAt,
|
||||
UpdatedBy: dto.UpdatedBy,
|
||||
Roles: roles,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,11 @@ func TestMain(m *testing.M) {
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
if db, err = postgres.Setup(dbConfig, *repostgres.Migration()); err != nil {
|
||||
migration, err := repostgres.Migration()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get migration: %s", err)
|
||||
}
|
||||
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
|
||||
log.Fatalf("Could not setup test DB connection: %s", err)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user