mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 15:25:48 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 880634e009 | |||
| 168e8b90cb | |||
| 6429f16233 | |||
| d40757f36f | |||
| c09020a29c |
@@ -15,14 +15,10 @@ on:
|
||||
- "clients/api/http/**"
|
||||
- "domains/api/http/**"
|
||||
- "groups/api/http/**"
|
||||
- "journal/api/**"
|
||||
- "users/api/**"
|
||||
- "bootstrap/api/**"
|
||||
- "certs/api/http/**"
|
||||
- "readers/api/http/**"
|
||||
- "re/**"
|
||||
- "alarms/**"
|
||||
- "reports/**"
|
||||
- "apidocs/openapi/**"
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -35,14 +31,10 @@ on:
|
||||
- "clients/api/http/**"
|
||||
- "domains/api/http/**"
|
||||
- "groups/api/http/**"
|
||||
- "journal/api/**"
|
||||
- "users/api/**"
|
||||
- "bootstrap/api/**"
|
||||
- "certs/api/http/**"
|
||||
- "readers/api/http/**"
|
||||
- "re/**"
|
||||
- "alarms/**"
|
||||
- "reports/**"
|
||||
- "apidocs/openapi/**"
|
||||
|
||||
concurrency:
|
||||
@@ -59,13 +51,9 @@ env:
|
||||
CLIENTS_URL: http://localhost
|
||||
CHANNELS_URL: http://localhost
|
||||
GROUPS_URL: http://localhost
|
||||
JOURNAL_URL: http://localhost:9021
|
||||
BOOTSTRAP_URL: http://localhost:9013
|
||||
CERTS_URL: http://localhost:9019
|
||||
READERS_URL: http://localhost:9011
|
||||
RE_URL: http://localhost:9008
|
||||
ALARMS_URL: http://localhost:8050
|
||||
REPORTS_URL: http://localhost:9017
|
||||
|
||||
jobs:
|
||||
api-test:
|
||||
@@ -77,7 +65,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -90,10 +78,6 @@ jobs:
|
||||
workflow:
|
||||
- ".github/workflows/api-tests.yaml"
|
||||
|
||||
journal:
|
||||
- "apidocs/openapi/journal.yaml"
|
||||
- "journal/api/**"
|
||||
|
||||
domains:
|
||||
- "apidocs/openapi/domains.yaml"
|
||||
- "internal/atom/**"
|
||||
@@ -131,24 +115,6 @@ jobs:
|
||||
- "apidocs/openapi/readers.yaml"
|
||||
- "readers/api/http/**"
|
||||
|
||||
re:
|
||||
- "apidocs/openapi/rules.yaml"
|
||||
- "re/**"
|
||||
- "cmd/re/**"
|
||||
- "internal/atom/**"
|
||||
|
||||
alarms:
|
||||
- "apidocs/openapi/alarms.yaml"
|
||||
- "alarms/**"
|
||||
- "cmd/alarms/**"
|
||||
- "internal/atom/**"
|
||||
|
||||
reports:
|
||||
- "apidocs/openapi/reports.yaml"
|
||||
- "reports/**"
|
||||
- "cmd/reports/**"
|
||||
- "internal/atom/**"
|
||||
|
||||
- name: Build images
|
||||
run: make all -j $(nproc) && make dockers_dev -j $(nproc)
|
||||
|
||||
@@ -240,17 +206,6 @@ jobs:
|
||||
coverage-artifact-name: schema-coverage-domains
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Run Journal API tests
|
||||
if: steps.changes.outputs.journal == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
uses: schemathesis/action@v3.0.0
|
||||
with:
|
||||
schema: apidocs/openapi/journal.yaml
|
||||
base-url: ${{ env.JOURNAL_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
coverage-artifact-name: schema-coverage-journal
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Run Bootstrap API tests
|
||||
if: (steps.changes.outputs.bootstrap == 'true' || steps.changes.outputs.workflow == 'true') && hashFiles('bootstrap/api/**') != ''
|
||||
uses: schemathesis/action@v3.0.0
|
||||
@@ -284,39 +239,6 @@ jobs:
|
||||
coverage-artifact-name: schema-coverage-readers
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Run Rules Engine API tests
|
||||
if: steps.changes.outputs.re == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
uses: schemathesis/action@v3.0.0
|
||||
with:
|
||||
schema: apidocs/openapi/rules.yaml
|
||||
base-url: ${{ env.RE_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
coverage-artifact-name: schema-coverage-rules
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Run Alarms API tests
|
||||
if: steps.changes.outputs.alarms == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
uses: schemathesis/action@v3.0.0
|
||||
with:
|
||||
schema: apidocs/openapi/alarms.yaml
|
||||
base-url: ${{ env.ALARMS_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
coverage-artifact-name: schema-coverage-alarms
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Run Reports API tests
|
||||
if: steps.changes.outputs.reports == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
uses: schemathesis/action@v3.0.0
|
||||
with:
|
||||
schema: apidocs/openapi/reports.yaml
|
||||
base-url: ${{ env.REPORTS_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
coverage-artifact-name: schema-coverage-reports
|
||||
coverage-pr-comment: false
|
||||
|
||||
- name: Stop containers
|
||||
if: always()
|
||||
run: make run_latest_ci down args="-v" && make run_addons down args="-v"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
@@ -12,15 +12,15 @@ jobs:
|
||||
check-generated-files:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PROTOC_VERSION: "33.0"
|
||||
PROTOC_VERSION: "35.1"
|
||||
PROTOC_GEN_GO_VERSION: "v1.36.11"
|
||||
PROTOC_GEN_GO_GRPC_VERSION: "v1.6.0"
|
||||
PROTOC_GEN_GO_GRPC_VERSION: "v1.6.2"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -68,7 +68,6 @@ jobs:
|
||||
- "pkg/groups/groups.go"
|
||||
- "users/emailer.go"
|
||||
- "users/hasher.go"
|
||||
- "journal/journal.go"
|
||||
- "consumers/notifier.go"
|
||||
|
||||
- name: Install Protoc
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -105,14 +105,22 @@ jobs:
|
||||
internal:
|
||||
- "internal/**"
|
||||
|
||||
journal:
|
||||
- "journal/**"
|
||||
- "cmd/journal/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
logger:
|
||||
- "logger/**"
|
||||
|
||||
pkg-atom:
|
||||
- "pkg/atom/**"
|
||||
|
||||
pkg-email:
|
||||
- "pkg/email/**"
|
||||
- "pkg/emailer/**"
|
||||
|
||||
pkg-readersclient:
|
||||
- "pkg/readersclient/**"
|
||||
|
||||
pkg-testsutil:
|
||||
- "pkg/testsutil/**"
|
||||
|
||||
pkg-errors:
|
||||
- "pkg/errors/**"
|
||||
|
||||
@@ -136,11 +144,7 @@ jobs:
|
||||
- "channels/**"
|
||||
- "domains/**"
|
||||
- "groups/**"
|
||||
- "journal/**"
|
||||
- "api/http/**"
|
||||
- "re/**"
|
||||
- "alarms/**"
|
||||
- "reports/**"
|
||||
|
||||
pkg-transformers:
|
||||
- "pkg/transformers/**"
|
||||
@@ -157,12 +161,6 @@ jobs:
|
||||
- "pkg/uuid/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
notifications:
|
||||
- "notifications/**"
|
||||
- "cmd/notifications/**"
|
||||
- "consumers/notifier.go"
|
||||
- "pkg/events/**"
|
||||
|
||||
api:
|
||||
- "api/**"
|
||||
|
||||
@@ -178,27 +176,14 @@ jobs:
|
||||
- "cmd/postgres-reader/**"
|
||||
- "cmd/timescale-reader/**"
|
||||
|
||||
re:
|
||||
- "re/**"
|
||||
- "cmd/re/**"
|
||||
- "re/api/**"
|
||||
|
||||
alarms:
|
||||
- "alarms/**"
|
||||
- "cmd/alarms/**"
|
||||
|
||||
reports:
|
||||
- "reports/**"
|
||||
- "cmd/reports/**"
|
||||
|
||||
- name: Set matrix for changed modules
|
||||
id: set-matrix
|
||||
run: |
|
||||
modules=()
|
||||
|
||||
if [[ "${{ steps.changes.outputs.workflow }}" == "true" || "${{ steps.changes.outputs.pkg-errors }}" == "true" ]]; then
|
||||
# If workflow or pkg/errors changed, test everything
|
||||
modules=("auth" "channels" "cli" "clients" "domains" "groups" "internal" "journal" "logger" "pkg-errors" "pkg-events" "pkg-grpcclient" "pkg-messaging" "pkg-sdk" "pkg-transformers" "pkg-ulid" "pkg-uuid" "users" "notifications" "api" "consumers" "readers" "re" "alarms" "reports")
|
||||
if [[ "${{ steps.changes.outputs.workflow }}" == "true" || "${{ steps.changes.outputs.pkg-errors }}" == "true" || "${{ steps.changes.outputs.pkg-testsutil }}" == "true" ]]; then
|
||||
# If workflow, shared errors, or test utilities changed, test everything
|
||||
modules=("auth" "channels" "cli" "clients" "domains" "groups" "internal" "logger" "pkg-atom" "pkg-email" "pkg-errors" "pkg-events" "pkg-grpcclient" "pkg-messaging" "pkg-readersclient" "pkg-sdk" "pkg-testsutil" "pkg-transformers" "pkg-ulid" "pkg-uuid" "users" "api" "consumers" "readers")
|
||||
else
|
||||
# Add only changed modules
|
||||
[[ "${{ steps.changes.outputs.auth }}" == "true" ]] && modules+=("auth")
|
||||
@@ -208,24 +193,22 @@ jobs:
|
||||
[[ "${{ steps.changes.outputs.domains }}" == "true" ]] && modules+=("domains")
|
||||
[[ "${{ steps.changes.outputs.groups }}" == "true" ]] && modules+=("groups")
|
||||
[[ "${{ steps.changes.outputs.internal }}" == "true" ]] && modules+=("internal")
|
||||
[[ "${{ steps.changes.outputs.journal }}" == "true" ]] && modules+=("journal")
|
||||
[[ "${{ steps.changes.outputs.logger }}" == "true" ]] && modules+=("logger")
|
||||
[[ "${{ steps.changes.outputs.pkg-atom }}" == "true" ]] && modules+=("pkg-atom")
|
||||
[[ "${{ steps.changes.outputs.pkg-email }}" == "true" ]] && modules+=("pkg-email")
|
||||
[[ "${{ steps.changes.outputs.pkg-errors }}" == "true" ]] && modules+=("pkg-errors")
|
||||
[[ "${{ steps.changes.outputs.pkg-events }}" == "true" ]] && modules+=("pkg-events")
|
||||
[[ "${{ steps.changes.outputs.pkg-grpcclient }}" == "true" ]] && modules+=("pkg-grpcclient")
|
||||
[[ "${{ steps.changes.outputs.pkg-messaging }}" == "true" ]] && modules+=("pkg-messaging")
|
||||
[[ "${{ steps.changes.outputs.pkg-readersclient }}" == "true" ]] && modules+=("pkg-readersclient")
|
||||
[[ "${{ steps.changes.outputs.pkg-sdk }}" == "true" ]] && modules+=("pkg-sdk")
|
||||
[[ "${{ steps.changes.outputs.pkg-transformers }}" == "true" ]] && modules+=("pkg-transformers")
|
||||
[[ "${{ steps.changes.outputs.pkg-ulid }}" == "true" ]] && modules+=("pkg-ulid")
|
||||
[[ "${{ steps.changes.outputs.pkg-uuid }}" == "true" ]] && modules+=("pkg-uuid")
|
||||
[[ "${{ steps.changes.outputs.users }}" == "true" ]] && modules+=("users")
|
||||
[[ "${{ steps.changes.outputs.notifications }}" == "true" ]] && modules+=("notifications")
|
||||
[[ "${{ steps.changes.outputs.api }}" == "true" ]] && modules+=("api")
|
||||
[[ "${{ steps.changes.outputs.consumers }}" == "true" ]] && modules+=("consumers")
|
||||
[[ "${{ steps.changes.outputs.readers }}" == "true" ]] && modules+=("readers")
|
||||
[[ "${{ steps.changes.outputs.re }}" == "true" ]] && modules+=("re")
|
||||
[[ "${{ steps.changes.outputs.alarms }}" == "true" ]] && modules+=("alarms")
|
||||
[[ "${{ steps.changes.outputs.reports }}" == "true" ]] && modules+=("reports")
|
||||
fi
|
||||
|
||||
# Convert to JSON array
|
||||
@@ -249,7 +232,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: "go.sum"
|
||||
@@ -263,11 +246,15 @@ jobs:
|
||||
run: |
|
||||
# Map module names to directories
|
||||
case "${{ matrix.module }}" in
|
||||
pkg-atom) dir="pkg/atom" ;;
|
||||
pkg-email) dir="pkg/email" ;;
|
||||
pkg-errors) dir="pkg/errors" ;;
|
||||
pkg-events) dir="pkg/events" ;;
|
||||
pkg-grpcclient) dir="pkg/grpcclient" ;;
|
||||
pkg-messaging) dir="pkg/messaging" ;;
|
||||
pkg-readersclient) dir="pkg/readersclient" ;;
|
||||
pkg-sdk) dir="pkg/sdk" ;;
|
||||
pkg-testsutil) dir="pkg/testsutil" ;;
|
||||
pkg-transformers) dir="pkg/transformers" ;;
|
||||
pkg-ulid) dir="pkg/ulid" ;;
|
||||
pkg-uuid) dir="pkg/uuid" ;;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
override MG_DOCKER_IMAGE_NAME_PREFIX := ghcr.io/absmach/magistrala
|
||||
MG_DOCKER_VOLUME_NAME_PREFIX ?= magistrala
|
||||
BUILD_DIR ?= build
|
||||
SERVICES = atom-bootstrap notifications certs re postgres-writer postgres-reader timescale-writer timescale-reader alarms reports journal fluxmq
|
||||
TEST_API_SERVICES = journal certs clients users channels groups domains
|
||||
SERVICES = atom-bootstrap certs postgres-writer postgres-reader timescale-writer timescale-reader fluxmq
|
||||
TEST_API_SERVICES = certs clients users channels groups domains
|
||||
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
@@ -24,7 +24,7 @@ DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | sed -E 's/[^a-z
|
||||
DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config restart
|
||||
DEFAULT_DOCKER_COMPOSE_COMMAND := up
|
||||
ATOM_TOKENS_ENV ?= docker/.env.tokens
|
||||
REQUIRED_ATOM_TOKEN_ENVS := MG_ATOM_TOKEN_FLUXMQ_AUTH MG_ATOM_TOKEN_FLUXMQ_NODE1 MG_ATOM_TOKEN_FLUXMQ_NODE2 MG_ATOM_TOKEN_FLUXMQ_NODE3 MG_ATOM_TOKEN_JOURNAL MG_ATOM_TOKEN_NOTIFICATIONS MG_ATOM_TOKEN_TIMESCALE_READER MG_ATOM_TOKEN_RE MG_ATOM_TOKEN_ALARMS MG_ATOM_TOKEN_REPORTS MG_ATOM_TOKEN_POSTGRES_READER
|
||||
REQUIRED_ATOM_TOKEN_ENVS := MG_ATOM_TOKEN_FLUXMQ_AUTH MG_ATOM_TOKEN_FLUXMQ_NODE1 MG_ATOM_TOKEN_FLUXMQ_NODE2 MG_ATOM_TOKEN_FLUXMQ_NODE3 MG_ATOM_TOKEN_TIMESCALE_READER MG_ATOM_TOKEN_RE MG_ATOM_TOKEN_ALARMS MG_ATOM_TOKEN_REPORTS MG_ATOM_TOKEN_POSTGRES_READER
|
||||
PROVISION_ATOM_TOKENS ?= false
|
||||
PROVISION_ATOM_TOKEN_GOALS := provision-atom-tokens
|
||||
DOCKER_BASE_ENV_FILES := --env-file docker/.env
|
||||
@@ -258,7 +258,6 @@ test_api_domains: TEST_API_URL := http://localhost:9000
|
||||
test_api_channels: TEST_API_URL := http://localhost:9000
|
||||
test_api_groups: TEST_API_URL := http://localhost:9000
|
||||
test_api_certs: TEST_API_URL := http://localhost:9019
|
||||
test_api_journal: TEST_API_URL := http://localhost:9021
|
||||
|
||||
$(TEST_API):
|
||||
$(call test_api_service,$(@),$(TEST_API_URL))
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
# Alarms
|
||||
|
||||
The Alarms service stores, manages and exposes alarms raised by rules and device activity. It consumes alarm events from the message broker, persists them to PostgreSQL, and provides an HTTP API for listing, viewing, updating, and deleting alarms with full authn/authz, metrics, and tracing support.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the following environment variables (values shown are from [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env) as consumed by [docker/docker-compose.yaml](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml)):
|
||||
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `MG_ALARMS_LOG_LEVEL` | Log level for the service | `debug` |
|
||||
| `MG_ALARMS_HTTP_HOST` | HTTP host to bind | `alarms` |
|
||||
| `MG_ALARMS_HTTP_PORT` | HTTP port to bind | `8050` |
|
||||
| `MG_ALARMS_HTTP_SERVER_CERT` | Path to PEM-encoded HTTPS server certificate | "" |
|
||||
| `MG_ALARMS_HTTP_SERVER_KEY` | Path to PEM-encoded HTTPS server key | "" |
|
||||
| `MG_ALARMS_DB_HOST` | PostgreSQL host | `alarms-db` |
|
||||
| `MG_ALARMS_DB_PORT` | PostgreSQL port | `5432` |
|
||||
| `MG_ALARMS_DB_USER` | PostgreSQL user | `magistrala` |
|
||||
| `MG_ALARMS_DB_PASS` | PostgreSQL password | `magistrala` |
|
||||
| `MG_ALARMS_DB_NAME` | PostgreSQL database name | `alarms` |
|
||||
| `MG_ALARMS_DB_SSL_MODE` | PostgreSQL SSL mode | `disable` |
|
||||
| `MG_ALARMS_DB_SSL_CERT` | PostgreSQL SSL client cert | "" |
|
||||
| `MG_ALARMS_DB_SSL_KEY` | PostgreSQL SSL client key | "" |
|
||||
| `MG_ALARMS_DB_SSL_ROOT_CERT` | PostgreSQL SSL root cert | "" |
|
||||
| `MG_ALARMS_INSTANCE_ID` | Instance ID for tracing/health | "" |
|
||||
| `MG_MESSAGE_BROKER_URL` | Message broker URL for alarm ingestion | `nats://nats:4222` |
|
||||
| `MG_JAEGER_URL` | Jaeger collector endpoint | `http://jaeger:4318/v1/traces` |
|
||||
| `MG_JAEGER_TRACE_RATIO` | Trace sampling ratio | `1.0` |
|
||||
| `ATOM_URL` | Atom HTTP endpoint | `http://atom:8080` |
|
||||
| `ATOM_JWKS_URL` | Atom JWKS endpoint for JWT verification | `http://atom:8080/.well-known/jwks.json` |
|
||||
| `ATOM_SERVICE_TOKEN` | Atom service token for authorization checks | "" |
|
||||
| `ATOM_ADMIN_USERNAME` | Atom admin login fallback when no service token is configured | `atom-admin` |
|
||||
| `ATOM_ADMIN_SECRET` | Atom admin secret fallback when no service token is configured | `change-me` |
|
||||
| `ATOM_TIMEOUT` | Atom request timeout | `5s` |
|
||||
| `MG_ALLOW_UNVERIFIED_USER` | Allow unverified users to access | `true` |
|
||||
|
||||
## Features
|
||||
|
||||
- **Alarm ingestion**: Consumes alarms from the message broker and persists them to PostgreSQL.
|
||||
- **Stateful updates**: Updates assignee, acknowledgment, resolution, and metadata fields.
|
||||
- **Filtering and paging**: Lists alarms by domain, rule, channel, client, subtopic, status, severity, and time range.
|
||||
- **Observability**: `/metrics` Prometheus endpoint and Jaeger tracing support.
|
||||
- **Auth and authorization**: Authn/authz enforced through Atom JWT verification and PDP checks while alarm records stay in PostgreSQL.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Runtime flow
|
||||
|
||||
1. The message broker publishes alarm events under the `alarms.>` subject.
|
||||
2. The Alarms consumer decodes the event payload, enriches it with message metadata, validates it, and calls `CreateAlarm`.
|
||||
3. The repository writes to PostgreSQL while deduplicating repeated active alarms with the same severity.
|
||||
4. The HTTP API exposes list/view/update/delete operations with authn/authz, metrics, and tracing middleware.
|
||||
|
||||
### Components
|
||||
|
||||
- **HTTP API**: `alarms/api` exposes REST endpoints and health/metrics handlers.
|
||||
- **Service layer**: `alarms/service.go` validates requests and coordinates repository operations.
|
||||
- **Repository**: `alarms/postgres/alarms.go` implements persistence and filtering.
|
||||
- **Consumer**: `alarms/consumer` processes broker messages and creates alarms.
|
||||
- **Message broker**: `alarms/brokers` uses NATS JetStream with stream `alarms` and subject `alarms.>`.
|
||||
- **Migrations**: `alarms/postgres/init.go` defines the alarms schema and indexes.
|
||||
|
||||
### Alarms table
|
||||
|
||||
Defined in `alarms/postgres/init.go`:
|
||||
|
||||
| Column | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | `VARCHAR(36)` | Alarm UUID (primary key) |
|
||||
| `rule_id` | `VARCHAR(36)` | Rule ID that triggered the alarm |
|
||||
| `domain_id` | `VARCHAR(36)` | Domain ID |
|
||||
| `channel_id` | `VARCHAR(36)` | Channel ID |
|
||||
| `subtopic` | `TEXT` | Subtopic associated with the alarm |
|
||||
| `client_id` | `VARCHAR(36)` | Client ID |
|
||||
| `measurement` | `TEXT` | Measurement name |
|
||||
| `value` | `TEXT` | Measured value |
|
||||
| `unit` | `TEXT` | Measurement unit |
|
||||
| `threshold` | `TEXT` | Threshold value |
|
||||
| `cause` | `TEXT` | Cause/description |
|
||||
| `status` | `SMALLINT` | 0 = active, 1 = cleared |
|
||||
| `severity` | `SMALLINT` | Severity (0-100) |
|
||||
| `assignee_id` | `VARCHAR(36)` | Assignee ID |
|
||||
| `created_at` | `TIMESTAMPTZ` | Creation timestamp |
|
||||
| `updated_at` | `TIMESTAMPTZ` | Last update timestamp |
|
||||
| `updated_by` | `VARCHAR(36)` | User who updated |
|
||||
| `assigned_at` | `TIMESTAMPTZ` | When assigned |
|
||||
| `assigned_by` | `VARCHAR(36)` | Who assigned |
|
||||
| `acknowledged_at` | `TIMESTAMPTZ` | When acknowledged |
|
||||
| `acknowledged_by` | `VARCHAR(36)` | Who acknowledged |
|
||||
| `resolved_at` | `TIMESTAMPTZ` | When resolved |
|
||||
| `resolved_by` | `VARCHAR(36)` | Who resolved |
|
||||
| `metadata` | `JSONB` | Custom metadata |
|
||||
|
||||
Index: `idx_alarms_state (domain_id, rule_id, channel_id, subtopic, client_id, measurement, created_at DESC)`
|
||||
|
||||
## Deployment
|
||||
|
||||
### Build and run locally
|
||||
|
||||
```bash
|
||||
make alarms
|
||||
|
||||
MG_ALARMS_LOG_LEVEL=debug \
|
||||
MG_ALARMS_HTTP_PORT=8050 \
|
||||
MG_ALARMS_DB_HOST=localhost \
|
||||
MG_ALARMS_DB_PORT=5432 \
|
||||
MG_ALARMS_DB_USER=magistrala \
|
||||
MG_ALARMS_DB_PASS=magistrala \
|
||||
MG_ALARMS_DB_NAME=alarms \
|
||||
MG_MESSAGE_BROKER_URL=nats://localhost:4222 \
|
||||
MG_AUTH_GRPC_URL=localhost:7001 \
|
||||
MG_AUTH_GRPC_TIMEOUT=300s \
|
||||
MG_DOMAINS_GRPC_URL=localhost:7003 \
|
||||
MG_DOMAINS_GRPC_TIMEOUT=300s \
|
||||
./build/alarms
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
The service is available as a Docker container. Refer to [docker/docker-compose.yaml](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml) for the `alarms` and `alarms-db` services and their environment variables. For a full local stack, make sure the auth, domains, and message broker services are also running.
|
||||
|
||||
```bash
|
||||
docker compose -f docker/docker-compose.yaml up alarms alarms-db
|
||||
```
|
||||
|
||||
### Health check
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8050/health \
|
||||
-H "accept: application/health+json"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
go test ./alarms/...
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The Alarms service supports the following operations:
|
||||
|
||||
| Operation | Method & Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `listAlarms` | `GET /{domainID}/alarms` | List alarms with filters |
|
||||
| `viewAlarm` | `GET /{domainID}/alarms/{alarmID}` | Retrieve a single alarm |
|
||||
| `updateAlarm` | `PUT /{domainID}/alarms/{alarmID}` | Update alarm status/assignee/metadata |
|
||||
| `deleteAlarm` | `DELETE /{domainID}/alarms/{alarmID}` | Delete an alarm |
|
||||
| `health` | `GET /health` | Service health check |
|
||||
|
||||
Alarm creation is driven by message broker events and is not exposed as an HTTP endpoint.
|
||||
|
||||
### Example: List alarms
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8050/<domainID>/alarms?limit=10&offset=0&status=active&severity=50" \
|
||||
-H "Authorization: Bearer <your_access_token>"
|
||||
```
|
||||
|
||||
### Example: View an alarm
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8050/<domainID>/alarms/<alarmID> \
|
||||
-H "Authorization: Bearer <your_access_token>"
|
||||
```
|
||||
|
||||
### Example: Update an alarm
|
||||
|
||||
```bash
|
||||
curl -X PUT http://localhost:8050/<domainID>/alarms/<alarmID> \
|
||||
-H "Authorization: Bearer <your_access_token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"status": "cleared",
|
||||
"assignee_id": "<userID>",
|
||||
"severity": 40,
|
||||
"metadata": { "note": "cleared after inspection" }
|
||||
}'
|
||||
```
|
||||
|
||||
### Example: Delete an alarm
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8050/<domainID>/alarms/<alarmID> \
|
||||
-H "Authorization: Bearer <your_access_token>"
|
||||
```
|
||||
|
||||
### Example: Health check
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8050/health \
|
||||
-H "accept: application/health+json"
|
||||
```
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
)
|
||||
|
||||
const SeverityMax uint8 = 100
|
||||
|
||||
var ErrInvalidSeverity = errors.New("invalid severity. Must be between 0 and 100")
|
||||
|
||||
type Metadata map[string]any
|
||||
|
||||
// Alarm represents an alarm instance.
|
||||
type Alarm struct {
|
||||
ID string `json:"id"`
|
||||
RuleID string `json:"rule_id"`
|
||||
DomainID string `json:"domain_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ClientID string `json:"client_id"`
|
||||
Subtopic string `json:"subtopic"`
|
||||
Status Status `json:"status"`
|
||||
Measurement string `json:"measurement"`
|
||||
Value string `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
Threshold string `json:"threshold"`
|
||||
Cause string `json:"cause"`
|
||||
Severity uint8 `json:"severity"`
|
||||
AssigneeID string `json:"assignee_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type PageMetadata struct {
|
||||
Offset uint64 `json:"offset" db:"offset"`
|
||||
Limit uint64 `json:"limit" db:"limit"`
|
||||
DomainID string `json:"domain_id" db:"domain_id"`
|
||||
RuleID string `json:"rule_id" db:"rule_id"`
|
||||
RuleIDs []string `json:"rule_ids" db:"rule_ids"`
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
ClientID string `json:"client_id" db:"client_id"`
|
||||
Subtopic string `json:"subtopic" db:"subtopic"`
|
||||
Measurement string `json:"measurement" db:"measurement"`
|
||||
Dir string `json:"dir" db:"dir"`
|
||||
Order string `json:"order" db:"order"`
|
||||
Status Status `json:"status" db:"status"`
|
||||
CreatedFrom time.Time `json:"created_from" db:"created_from"`
|
||||
CreatedTo time.Time `json:"created_to" db:"created_to"`
|
||||
AssigneeID string `json:"assignee_id" db:"assignee_id"`
|
||||
Severity uint8 `json:"severity" db:"severity"`
|
||||
UpdatedBy string `json:"updated_by" db:"updated_by"`
|
||||
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 {
|
||||
if a.RuleID == "" {
|
||||
return errors.New("rule_id is required")
|
||||
}
|
||||
if a.DomainID == "" {
|
||||
return errors.New("domain_id is required")
|
||||
}
|
||||
if a.ChannelID == "" {
|
||||
return errors.New("channel_id is required")
|
||||
}
|
||||
if a.ClientID == "" {
|
||||
return errors.New("client_id is required")
|
||||
}
|
||||
if a.Measurement == "" {
|
||||
return errors.New("measurement is required")
|
||||
}
|
||||
if a.Value == "" {
|
||||
return errors.New("value is required")
|
||||
}
|
||||
if a.Cause == "" {
|
||||
return errors.New("cause is required")
|
||||
}
|
||||
if a.Severity > SeverityMax {
|
||||
return ErrInvalidSeverity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service specifies an API that must be fulfilled by the domain service.
|
||||
type Service interface {
|
||||
CreateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
|
||||
UpdateAlarm(ctx context.Context, session authn.Session, alarm Alarm) (Alarm, error)
|
||||
ViewAlarm(ctx context.Context, session authn.Session, id string) (Alarm, error)
|
||||
ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error)
|
||||
DeleteAlarm(ctx context.Context, session authn.Session, id string) error
|
||||
}
|
||||
|
||||
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)
|
||||
ListAllAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
|
||||
DeleteAlarm(ctx context.Context, id string) error
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidateAlarms(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "missing rule_id",
|
||||
alarm: alarms.Alarm{
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("rule_id is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing domain_id",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("domain_id is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing channel_id",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("channel_id is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing client_id",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("client_id is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing measurement",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("measurement is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing value",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("value is required"),
|
||||
},
|
||||
{
|
||||
desc: "missing cause",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Severity: 100,
|
||||
},
|
||||
err: errors.New("cause is required"),
|
||||
},
|
||||
{
|
||||
desc: "higher severity",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: testsutil.GenerateUUID(t),
|
||||
DomainID: testsutil.GenerateUUID(t),
|
||||
ChannelID: testsutil.GenerateUUID(t),
|
||||
ClientID: testsutil.GenerateUUID(t),
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: alarms.SeverityMax + 1,
|
||||
},
|
||||
err: alarms.ErrInvalidSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
err := tc.alarm.Validate()
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains API-related concerns: endpoint definitions, middlewares
|
||||
// and all resource representations.
|
||||
package api
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
func updateAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(updateAlarmReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return alarmRes{}, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
alarm, err := svc.UpdateAlarm(ctx, session, req.Alarm)
|
||||
if err != nil {
|
||||
return alarmRes{}, err
|
||||
}
|
||||
|
||||
return alarmRes{
|
||||
Alarm: alarm,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func viewAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(alarmReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return alarmRes{}, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
alarm, err := svc.ViewAlarm(ctx, session, req.ID)
|
||||
if err != nil {
|
||||
return alarmRes{}, err
|
||||
}
|
||||
|
||||
return alarmRes{
|
||||
Alarm: alarm,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func listAlarmsEndpoint(svc alarms.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(listAlarmsReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return alarmsPageRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return alarmsPageRes{}, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
alarms, err := svc.ListAlarms(ctx, session, req.PageMetadata)
|
||||
if err != nil {
|
||||
return alarmsPageRes{}, err
|
||||
}
|
||||
|
||||
return alarmsPageRes{
|
||||
AlarmsPage: alarms,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(alarmReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return alarmRes{}, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
if err := svc.DeleteAlarm(ctx, session, req.ID); err != nil {
|
||||
return alarmRes{}, err
|
||||
}
|
||||
|
||||
return alarmRes{deleted: true}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
api "github.com/absmach/magistrala/api/http"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
)
|
||||
|
||||
type alarmReq struct {
|
||||
alarms.Alarm `json:",inline"`
|
||||
}
|
||||
|
||||
func (req alarmReq) validate() error {
|
||||
if req.Alarm.ID == "" {
|
||||
return errors.New("missing alarm id")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (req listAlarmsReq) validate() error {
|
||||
if req.Limit > api.MaxLimitSize || req.Limit < 1 {
|
||||
return apiutil.ErrLimitSize
|
||||
}
|
||||
|
||||
if req.Order != "" && req.Order != api.UpdatedAtOrder && req.Order != api.CreatedAtOrder {
|
||||
return apiutil.ErrInvalidOrder
|
||||
}
|
||||
|
||||
if req.Dir != api.AscDir && req.Dir != api.DescDir {
|
||||
return apiutil.ErrInvalidDirection
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
)
|
||||
|
||||
var (
|
||||
_ magistrala.Response = (*alarmRes)(nil)
|
||||
_ magistrala.Response = (*alarmsPageRes)(nil)
|
||||
)
|
||||
|
||||
type alarmRes struct {
|
||||
alarms.Alarm `json:",inline"`
|
||||
created bool
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func (res alarmRes) Headers() map[string]string {
|
||||
switch {
|
||||
case res.created:
|
||||
return map[string]string{
|
||||
"Location": fmt.Sprintf("/%s/alarms/%s", res.DomainID, res.ID),
|
||||
}
|
||||
default:
|
||||
return map[string]string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (res alarmRes) Code() int {
|
||||
switch {
|
||||
case res.created:
|
||||
return http.StatusCreated
|
||||
case res.deleted:
|
||||
return http.StatusNoContent
|
||||
default:
|
||||
return http.StatusOK
|
||||
}
|
||||
}
|
||||
|
||||
func (res alarmRes) Empty() bool {
|
||||
switch {
|
||||
case res.deleted:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type alarmsPageRes struct {
|
||||
alarms.AlarmsPage `json:",inline"`
|
||||
}
|
||||
|
||||
func (res alarmsPageRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res alarmsPageRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res alarmsPageRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
api "github.com/absmach/magistrala/api/http"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
func MakeHandler(svc alarms.Service, logger *slog.Logger, idp magistrala.IDProvider, instanceID string, authn smqauthn.AuthNMiddleware) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
mux.Route("/{domainID}/alarms", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
|
||||
r.Use(api.RequestIDMiddleware(idp))
|
||||
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
listAlarmsEndpoint(svc),
|
||||
decodeListAlarmsReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list_alarms").ServeHTTP)
|
||||
r.Route("/{alarmID}", func(r chi.Router) {
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewAlarmEndpoint(svc),
|
||||
decodeAlarmReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "get_alarm").ServeHTTP)
|
||||
r.Put("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateAlarmEndpoint(svc),
|
||||
decodeUpdateAlarmReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_alarm").ServeHTTP)
|
||||
r.Delete("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteAlarmEndpoint(svc),
|
||||
decodeAlarmReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_alarm").ServeHTTP)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
mux.Get("/health", magistrala.Health("alarms", instanceID))
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeListAlarmsReq(_ context.Context, r *http.Request) (any, error) {
|
||||
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
domainID, err := apiutil.ReadStringQuery(r, "domain_id", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
channelID, err := apiutil.ReadStringQuery(r, "channel_id", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
clientID, err := apiutil.ReadStringQuery(r, "client_id", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
subtopic, err := apiutil.ReadStringQuery(r, "subtopic", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
ruleID, err := apiutil.ReadStringQuery(r, "rule_id", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
s, err := apiutil.ReadStringQuery(r, api.StatusKey, alarms.All)
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
status, err := alarms.ToStatus(s)
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
assigneeID, err := apiutil.ReadStringQuery(r, "assignee_id", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
serverity, err := apiutil.ReadNumQuery(r, "severity", uint64(math.MaxUint8))
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
updatedBy, err := apiutil.ReadStringQuery(r, "updated_by", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
assignedBy, err := apiutil.ReadStringQuery(r, "assigned_by", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
acknowledgedBy, err := apiutil.ReadStringQuery(r, "acknowledged_by", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
resolvedBy, err := apiutil.ReadStringQuery(r, "resolved_by", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
cfrom, err := apiutil.ReadStringQuery(r, "created_from", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
cto, err := apiutil.ReadStringQuery(r, "created_to", "")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder)
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
dir, err := apiutil.ReadStringQuery(r, api.DirKey, "desc")
|
||||
if err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
var createdFrom, createdTo time.Time
|
||||
if cfrom != "" {
|
||||
if createdFrom, err = time.Parse(time.RFC3339, cfrom); err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
}
|
||||
if cto != "" {
|
||||
if createdTo, err = time.Parse(time.RFC3339, cto); err != nil {
|
||||
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
}
|
||||
|
||||
return listAlarmsReq{
|
||||
PageMetadata: alarms.PageMetadata{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
DomainID: domainID,
|
||||
ChannelID: channelID,
|
||||
ClientID: clientID,
|
||||
Subtopic: subtopic,
|
||||
RuleID: ruleID,
|
||||
Status: status,
|
||||
AssigneeID: assigneeID,
|
||||
ResolvedBy: resolvedBy,
|
||||
Severity: uint8(serverity),
|
||||
UpdatedBy: updatedBy,
|
||||
AcknowledgedBy: acknowledgedBy,
|
||||
AssignedBy: assignedBy,
|
||||
CreatedFrom: createdFrom,
|
||||
CreatedTo: createdTo,
|
||||
Dir: dir,
|
||||
Order: order,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeAlarmReq(_ context.Context, r *http.Request) (any, error) {
|
||||
return alarmReq{
|
||||
Alarm: alarms.Alarm{
|
||||
ID: chi.URLParam(r, "alarmID"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return updateAlarmReq{}, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
|
||||
req := updateAlarmReq{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req.Alarm); err != nil {
|
||||
return updateAlarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||
}
|
||||
|
||||
req.Alarm.ID = chi.URLParam(r, "alarmID")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build msg_fluxmq
|
||||
// +build msg_fluxmq
|
||||
|
||||
package brokers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
broker "github.com/absmach/magistrala/pkg/messaging/fluxmq"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
|
||||
const (
|
||||
AllTopic = "alarms/#"
|
||||
|
||||
prefix = "alarms"
|
||||
)
|
||||
|
||||
var cfg = jetstream.StreamConfig{
|
||||
Name: "alarms",
|
||||
Description: "Magistrala stream alarms",
|
||||
Subjects: []string{"alarms/#"},
|
||||
Retention: jetstream.LimitsPolicy,
|
||||
MaxMsgsPerSubject: 1e6,
|
||||
MaxAge: time.Hour * 24,
|
||||
MaxMsgSize: 1024 * 1024,
|
||||
Discard: jetstream.DiscardOld,
|
||||
Storage: jetstream.FileStorage,
|
||||
}
|
||||
|
||||
func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
|
||||
pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg), broker.ConnectionName("alarms-msg-pubsub"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) {
|
||||
pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg), broker.ConnectionName("alarms-msg-pub"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !msg_fluxmq && !msg_rabbitmq && !rabbitmq
|
||||
// +build !msg_fluxmq,!msg_rabbitmq,!rabbitmq
|
||||
|
||||
package brokers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
broker "github.com/absmach/magistrala/pkg/messaging/nats"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
|
||||
const (
|
||||
AllTopic = "alarms/#"
|
||||
|
||||
prefix = "alarms"
|
||||
)
|
||||
|
||||
var cfg = jetstream.StreamConfig{
|
||||
Name: "alarms",
|
||||
Description: "Magistrala stream alarms",
|
||||
Subjects: []string{"alarms.>"},
|
||||
Retention: jetstream.LimitsPolicy,
|
||||
MaxMsgsPerSubject: 1e6,
|
||||
MaxAge: time.Hour * 24,
|
||||
MaxMsgSize: 1024 * 1024,
|
||||
Discard: jetstream.DiscardOld,
|
||||
Storage: jetstream.FileStorage,
|
||||
}
|
||||
|
||||
func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
|
||||
pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) {
|
||||
pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
)
|
||||
|
||||
var errFailedToDecode = errors.New("failed to decode alarm")
|
||||
|
||||
type handler struct {
|
||||
svc alarms.Service
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewHandler(svc alarms.Service, logger *slog.Logger) messaging.MessageHandler {
|
||||
return &handler{svc: svc, logger: logger}
|
||||
}
|
||||
|
||||
func (h handler) Handle(msg *messaging.Message) (err error) {
|
||||
if msg == nil {
|
||||
return errors.New("message is empty")
|
||||
}
|
||||
if msg.GetPayload() == nil {
|
||||
return errors.New("message payload is empty")
|
||||
}
|
||||
|
||||
var alarm alarms.Alarm
|
||||
if err := gob.NewDecoder(bytes.NewReader(msg.GetPayload())).Decode(&alarm); err != nil {
|
||||
return messaging.NewError(errors.Wrap(errFailedToDecode, err), messaging.Term)
|
||||
}
|
||||
alarm.DomainID = msg.GetDomain()
|
||||
alarm.ChannelID = msg.GetChannel()
|
||||
alarm.ClientID = msg.ClientIdentity()
|
||||
alarm.Subtopic = msg.GetSubtopic()
|
||||
alarm.CreatedAt = time.Unix(0, int64(msg.GetCreated()))
|
||||
|
||||
if err := alarm.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = h.svc.CreateAlarm(context.Background(), alarm)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h handler) Cancel() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package alarms contains domain concept definitions needed to support
|
||||
// Alarms service feature, i.e. create, read, update, and delete alarms.
|
||||
package alarms
|
||||
@@ -1,322 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/operations"
|
||||
"github.com/absmach/magistrala/auth"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
smqauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
)
|
||||
|
||||
var (
|
||||
errDomainUpdateAlarms = errors.New("not authorized to update alarms in domain")
|
||||
errDomainDeleteAlarms = errors.New("not authorized to delete alarms in domain")
|
||||
errDomainViewAlarms = errors.New("not authorized to view alarms in domain")
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc alarms.Service
|
||||
authz smqauthz.Authorization
|
||||
atomAuthz atom.Authorizer
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
}
|
||||
|
||||
var _ alarms.Service = (*authorizationMiddleware)(nil)
|
||||
|
||||
const (
|
||||
atomObjectKindResource = "resource"
|
||||
atomObjectTypeResourceRule = "resource:" + atom.KindRule
|
||||
atomAuthorizedRulePageLimit = 100
|
||||
)
|
||||
|
||||
type atomAuthorizedObjectLister interface {
|
||||
AuthorizedObjectIDs(ctx context.Context, q atom.AuthorizedObjectIDsQuery) (atom.AuthorizedObjectIDs, error)
|
||||
}
|
||||
|
||||
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 NewAtomAuthorizationMiddleware(svc alarms.Service, authz atom.Authorizer, entitiesOps permissions.EntitiesOperations[permissions.Operation]) (alarms.Service, error) {
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
atomAuthz: authz,
|
||||
entitiesOps: entitiesOps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
return am.svc.CreateAlarm(ctx, alarm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
current, err := am.svc.ViewAlarm(ctx, session, alarm.ID)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
|
||||
if len(alarm.Metadata) > 0 {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpUpdateAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.AssigneeID != "" {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpAssignAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
if am.atomAuthz == nil {
|
||||
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,
|
||||
Permission: policies.MembershipPermission,
|
||||
ObjectType: policies.DomainType,
|
||||
Object: session.DomainID,
|
||||
}, nil); err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.AcknowledgedBy != "" {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpAcknowledgeAlarm, session, current); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
|
||||
}
|
||||
}
|
||||
|
||||
if alarm.ResolvedBy != "" {
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpResolveAlarm, session, current); 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 {
|
||||
alarm, err := am.svc.ViewAlarm(ctx, session, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := am.authorizeAlarmOrRule(ctx, operations.OpDeleteAlarm, session, alarm); err != nil {
|
||||
return errors.Wrap(errDomainDeleteAlarms, err)
|
||||
}
|
||||
|
||||
return am.svc.DeleteAlarm(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
if pm.DomainID == "" {
|
||||
pm.DomainID = session.DomainID
|
||||
}
|
||||
|
||||
switch err := am.checkSuperAdmin(ctx, session); {
|
||||
case err == nil:
|
||||
session.SuperAdmin = true
|
||||
case errors.Contains(err, svcerr.ErrSuperAdminAction):
|
||||
if err := am.authorizeTenantAlarm(ctx, operations.OpViewAlarm, session); err != nil {
|
||||
if pm.RuleID != "" {
|
||||
if ruleErr := am.authorizeRuleAlarmRead(ctx, session, pm.RuleID); ruleErr != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
ruleIDs, ruleErr := am.authorizedReadableRuleIDs(ctx, session)
|
||||
if ruleErr != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
if len(ruleIDs) == 0 {
|
||||
return alarms.AlarmsPage{
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Alarms: []alarms.Alarm{},
|
||||
}, nil
|
||||
}
|
||||
pm.RuleIDs = ruleIDs
|
||||
}
|
||||
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) {
|
||||
alarm, err := am.svc.ViewAlarm(ctx, session, id)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, err
|
||||
}
|
||||
if err := am.authorizeViewAlarm(ctx, session, alarm); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(errDomainViewAlarms, err)
|
||||
}
|
||||
|
||||
return alarm, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeAlarmOrRule(ctx context.Context, op permissions.Operation, session authn.Session, alarm alarms.Alarm) error {
|
||||
tenantErr := am.authorizeTenantAlarm(ctx, op, session)
|
||||
if tenantErr == nil {
|
||||
return nil
|
||||
}
|
||||
if alarm.RuleID == "" {
|
||||
return tenantErr
|
||||
}
|
||||
if err := am.authorize(ctx, op, session, policies.RulesType, alarm.RuleID, atom.KindRule); err != nil {
|
||||
return tenantErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeViewAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) error {
|
||||
tenantErr := am.authorizeTenantAlarm(ctx, operations.OpViewAlarm, session)
|
||||
if tenantErr == nil {
|
||||
return nil
|
||||
}
|
||||
if alarm.RuleID == "" {
|
||||
return tenantErr
|
||||
}
|
||||
if err := am.authorizeRuleAlarmRead(ctx, session, alarm.RuleID); err != nil {
|
||||
return tenantErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeTenantAlarm(ctx context.Context, op permissions.Operation, session authn.Session) error {
|
||||
return am.authorize(ctx, op, session, policies.DomainType, session.DomainID, atom.KindAlarm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizeRuleAlarmRead(ctx context.Context, session authn.Session, ruleID string) error {
|
||||
if am.atomAuthz != nil {
|
||||
return am.authorize(ctx, operations.OpViewAlarm, session, policies.RulesType, ruleID, atom.KindRule)
|
||||
}
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, operations.OpViewAlarm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pr := smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: ruleID,
|
||||
ObjectType: policies.RulesType,
|
||||
Permission: perm.String(),
|
||||
}
|
||||
return am.authz.Authorize(ctx, pr, nil)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorizedReadableRuleIDs(ctx context.Context, session authn.Session) ([]string, error) {
|
||||
lister, ok := am.atomAuthz.(atomAuthorizedObjectLister)
|
||||
if !ok {
|
||||
return nil, errors.ErrAuthorization
|
||||
}
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, operations.OpViewAlarm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for offset := uint64(0); ; offset += atomAuthorizedRulePageLimit {
|
||||
page, err := lister.AuthorizedObjectIDs(ctx, atom.AuthorizedObjectIDsQuery{
|
||||
SubjectID: atom.SubjectID(session),
|
||||
Action: atom.CapabilityName(perm.String()),
|
||||
ObjectKind: atomObjectKindResource,
|
||||
ObjectType: atomObjectTypeResourceRule,
|
||||
TenantID: session.DomainID,
|
||||
Limit: atomAuthorizedRulePageLimit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids = append(ids, page.IDs...)
|
||||
if uint64(len(page.IDs)) < atomAuthorizedRulePageLimit || offset+uint64(len(page.IDs)) >= page.Total {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj, resourceKind string) error {
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if am.atomAuthz != nil {
|
||||
return atom.Authorize(ctx, am.atomAuthz, session, perm.String(), objType, obj, resourceKind)
|
||||
}
|
||||
|
||||
pr := smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: obj,
|
||||
ObjectType: objType,
|
||||
Permission: perm.String(),
|
||||
}
|
||||
|
||||
var pat *smqauthz.PATReq
|
||||
if session.PatID != "" {
|
||||
opName := am.entitiesOps.OperationName(operations.EntityType, op)
|
||||
pat = &smqauthz.PATReq{
|
||||
UserID: session.UserID,
|
||||
PatID: session.PatID,
|
||||
EntityID: auth.AnyIDs,
|
||||
EntityType: auth.RulesType.String(),
|
||||
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 am.atomAuthz != nil {
|
||||
return atom.Authorize(ctx, am.atomAuthz, session, policies.AdminPermission, policies.PlatformType, policies.MagistralaObject, policies.PlatformType)
|
||||
}
|
||||
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
|
||||
SubjectType: policies.UserType,
|
||||
Subject: session.UserID,
|
||||
Permission: policies.AdminPermission,
|
||||
ObjectType: policies.PlatformType,
|
||||
Object: policies.MagistralaObject,
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/mocks"
|
||||
"github.com/absmach/magistrala/alarms/operations"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type recordingAtomAuthorizer struct {
|
||||
allowed bool
|
||||
allow func(atom.AuthzRequest) bool
|
||||
authorized atom.AuthorizedObjectIDs
|
||||
reqs []atom.AuthzRequest
|
||||
queries []atom.AuthorizedObjectIDsQuery
|
||||
}
|
||||
|
||||
func (a *recordingAtomAuthorizer) CheckAuthz(_ context.Context, req atom.AuthzRequest) (atom.AuthzResponse, error) {
|
||||
a.reqs = append(a.reqs, req)
|
||||
if a.allow != nil {
|
||||
return atom.AuthzResponse{Allowed: a.allow(req)}, nil
|
||||
}
|
||||
return atom.AuthzResponse{Allowed: a.allowed}, nil
|
||||
}
|
||||
|
||||
func (a *recordingAtomAuthorizer) AuthorizedObjectIDs(_ context.Context, q atom.AuthorizedObjectIDsQuery) (atom.AuthorizedObjectIDs, error) {
|
||||
a.queries = append(a.queries, q)
|
||||
return a.authorized, nil
|
||||
}
|
||||
|
||||
func TestListAlarmsAuthorizesTenantAlarmReader(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
session := authn.Session{UserID: "user-1", DomainID: "domain-1", DomainUserID: "domain-1_user-1"}
|
||||
authz := &recordingAtomAuthorizer{allowed: true}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ListAlarms", mock.Anything, session, expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
page, err := wrapped.ListAlarms(context.Background(), session, pm)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(10), page.Limit)
|
||||
require.Len(t, authz.reqs, 1)
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_read",
|
||||
ResourceID: "",
|
||||
ObjectKind: "tenant",
|
||||
ObjectID: "domain-1",
|
||||
Context: map[string]any{
|
||||
"domain_id": "domain-1",
|
||||
"legacy_object_type": "domain",
|
||||
},
|
||||
}, authz.reqs[0])
|
||||
}
|
||||
|
||||
func TestListAlarmsFiltersToReadableRulesWhenTenantAlarmReadDenied(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
expectedPM.RuleIDs = []string{"rule-1", "rule-2"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allowed: false,
|
||||
authorized: atom.AuthorizedObjectIDs{IDs: []string{"rule-1", "rule-2"}, Total: 2},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ListAlarms", mock.Anything, authn.Session{UserID: "user-1", DomainID: "domain-1"}, expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
_, err = wrapped.ListAlarms(context.Background(), authn.Session{UserID: "user-1", DomainID: "domain-1"}, pm)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 1)
|
||||
assert.Equal(t, "alarm_read", authz.reqs[0].Action)
|
||||
assert.Equal(t, "tenant", authz.reqs[0].ObjectKind)
|
||||
require.Len(t, authz.queries, 1)
|
||||
assert.Equal(t, atom.AuthorizedObjectIDsQuery{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_read",
|
||||
ObjectKind: "resource",
|
||||
ObjectType: "resource:rule",
|
||||
TenantID: "domain-1",
|
||||
Limit: 100,
|
||||
}, authz.queries[0])
|
||||
}
|
||||
|
||||
func TestListAlarmsWithRuleFilterAuthorizesRuleRead(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10, RuleID: "rule-1"}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
session := authn.Session{UserID: "user-1", DomainID: "domain-1"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allow: func(req atom.AuthzRequest) bool {
|
||||
return req.Action == "alarm_read" && req.ObjectKind == "resource" && req.ObjectID == "rule-1"
|
||||
},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ListAlarms", mock.Anything, session, expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
_, err = wrapped.ListAlarms(context.Background(), session, pm)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 2)
|
||||
assert.Equal(t, "alarm_read", authz.reqs[0].Action)
|
||||
assert.Equal(t, "alarm_read", authz.reqs[1].Action)
|
||||
assert.Equal(t, "resource", authz.reqs[1].ObjectKind)
|
||||
assert.Equal(t, "rules", authz.reqs[1].Context["legacy_object_type"])
|
||||
}
|
||||
|
||||
func TestListAlarmsSuperAdminSkipsListAuthorization(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
pm := alarms.PageMetadata{Limit: 10}
|
||||
expectedPM := pm
|
||||
expectedPM.DomainID = "domain-1"
|
||||
session := authn.Session{UserID: "admin-1", DomainID: "domain-1", Role: authn.SuperAdminRole}
|
||||
authz := &recordingAtomAuthorizer{allowed: true}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ListAlarms", mock.Anything, mock.MatchedBy(func(s authn.Session) bool {
|
||||
return s.SuperAdmin
|
||||
}), expectedPM).Return(alarms.AlarmsPage{Limit: 10}, nil).Once()
|
||||
_, err = wrapped.ListAlarms(context.Background(), session, pm)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 1)
|
||||
assert.Equal(t, "manage", authz.reqs[0].Action)
|
||||
}
|
||||
|
||||
func TestAcknowledgeAlarmAuthorizesRuleAlarmActionWhenTenantDenied(t *testing.T) {
|
||||
svc := mocks.NewService(t)
|
||||
session := authn.Session{UserID: "user-1", DomainID: "domain-1"}
|
||||
current := alarms.Alarm{ID: "alarm-1", RuleID: "rule-1", DomainID: "domain-1"}
|
||||
update := alarms.Alarm{ID: "alarm-1", AcknowledgedBy: "user-1"}
|
||||
authz := &recordingAtomAuthorizer{
|
||||
allow: func(req atom.AuthzRequest) bool {
|
||||
return req.Action == "alarm_acknowledge" && req.ObjectKind == "resource" && req.ObjectID == "rule-1"
|
||||
},
|
||||
}
|
||||
wrapped, err := NewAtomAuthorizationMiddleware(svc, authz, testEntitiesOps(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
svc.On("ViewAlarm", mock.Anything, session, "alarm-1").Return(current, nil).Once()
|
||||
svc.On("UpdateAlarm", mock.Anything, session, update).Return(update, nil).Once()
|
||||
_, err = wrapped.UpdateAlarm(context.Background(), session, update)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, authz.reqs, 2)
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_acknowledge",
|
||||
ResourceID: "",
|
||||
ObjectKind: "tenant",
|
||||
ObjectID: "domain-1",
|
||||
Context: map[string]any{
|
||||
"domain_id": "domain-1",
|
||||
"legacy_object_type": "domain",
|
||||
},
|
||||
}, authz.reqs[0])
|
||||
assert.Equal(t, atom.AuthzRequest{
|
||||
SubjectID: "user-1",
|
||||
Action: "alarm_acknowledge",
|
||||
ResourceID: "rule-1",
|
||||
ObjectKind: "resource",
|
||||
ObjectID: "rule-1",
|
||||
Context: map[string]any{
|
||||
"domain_id": "domain-1",
|
||||
"legacy_object_type": "rules",
|
||||
},
|
||||
}, authz.reqs[1])
|
||||
}
|
||||
|
||||
func testEntitiesOps(t *testing.T) permissions.EntitiesOperations[permissions.Operation] {
|
||||
t.Helper()
|
||||
details := operations.OperationDetails()
|
||||
perms := make(map[string]permissions.Permission, len(details))
|
||||
for op, detail := range details {
|
||||
if detail.PermissionRequired {
|
||||
perms[detail.Name] = testPermission(op, detail.Name)
|
||||
}
|
||||
}
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
permissions.EntitiesPermission{operations.EntityType: perms},
|
||||
permissions.EntitiesOperationDetails[permissions.Operation]{operations.EntityType: details},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return entitiesOps
|
||||
}
|
||||
|
||||
func testPermission(op permissions.Operation, fallback string) permissions.Permission {
|
||||
switch op {
|
||||
case operations.OpViewAlarm, operations.OpListAlarms:
|
||||
return "alarm_read_permission"
|
||||
case operations.OpUpdateAlarm:
|
||||
return "alarm_update_permission"
|
||||
case operations.OpDeleteAlarm:
|
||||
return "alarm_delete_permission"
|
||||
case operations.OpAssignAlarm:
|
||||
return "alarm_assign_permission"
|
||||
case operations.OpAcknowledgeAlarm:
|
||||
return "alarm_acknowledge_permission"
|
||||
case operations.OpResolveAlarm:
|
||||
return "alarm_resolve_permission"
|
||||
default:
|
||||
return permissions.Permission(fallback)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package middleware provides middleware for the alarms service.
|
||||
// This is logging, metrics, and tracing middleware.
|
||||
package middleware
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger *slog.Logger
|
||||
service alarms.Service
|
||||
}
|
||||
|
||||
var _ alarms.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
func NewLoggingMiddleware(logger *slog.Logger, service alarms.Service) alarms.Service {
|
||||
return &loggingMiddleware{
|
||||
logger: logger,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (created alarms.Alarm, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.Group("alarm",
|
||||
slog.String("rule_id", alarm.RuleID),
|
||||
slog.String("domain_id", alarm.DomainID),
|
||||
slog.String("channel_id", alarm.ChannelID),
|
||||
slog.String("client_id", alarm.ClientID),
|
||||
slog.String("subtopic", alarm.Subtopic),
|
||||
slog.String("measurement", alarm.Measurement),
|
||||
slog.String("value", alarm.Value),
|
||||
slog.String("unit", alarm.Unit),
|
||||
slog.Uint64("status", uint64(alarm.Status)),
|
||||
slog.Uint64("severity", uint64(alarm.Severity)),
|
||||
slog.String("threshold", alarm.Threshold),
|
||||
slog.String("cause", alarm.Cause),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("Create alarm failed", args...)
|
||||
return
|
||||
}
|
||||
if created.ID != "" {
|
||||
lm.logger.Info("Create alarm completed successfully", args...)
|
||||
}
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.CreateAlarm(ctx, alarm)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (dba alarms.Alarm, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.Group("alarm",
|
||||
slog.String("id", dba.ID),
|
||||
slog.String("rule_id", dba.RuleID),
|
||||
slog.String("domain_id", dba.DomainID),
|
||||
slog.String("channel_id", dba.ChannelID),
|
||||
slog.String("client_id", dba.ClientID),
|
||||
slog.String("subtopic", dba.Subtopic),
|
||||
slog.String("measurement", dba.Measurement),
|
||||
slog.String("value", dba.Value),
|
||||
slog.String("unit", dba.Unit),
|
||||
slog.String("status", dba.Status.String()),
|
||||
slog.Uint64("severity", uint64(dba.Severity)),
|
||||
slog.String("threshold", dba.Threshold),
|
||||
slog.String("cause", dba.Cause),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("Update alarm failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Update alarm completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.UpdateAlarm(ctx, session, alarm)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (dba alarms.Alarm, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("id", id),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("View alarm failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("View alarm completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.ViewAlarm(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ListAlarms(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (dbp alarms.AlarmsPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.Int("offset", int(pm.Offset)),
|
||||
slog.Int("limit", int(pm.Limit)),
|
||||
slog.String("rule_id", pm.RuleID),
|
||||
slog.String("domain_id", pm.DomainID),
|
||||
slog.String("channel_id", pm.ChannelID),
|
||||
slog.String("client_id", pm.ClientID),
|
||||
slog.String("subtopic", pm.Subtopic),
|
||||
slog.String("status", pm.Status.String()),
|
||||
slog.Uint64("severity", uint64(pm.Severity)),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("List alarms failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("List alarms completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.ListAlarms(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("id", id),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.Any("error", err))
|
||||
lm.logger.Warn("Delete alarm failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Delete alarm completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.DeleteAlarm(ctx, session, id)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
service alarms.Service
|
||||
}
|
||||
|
||||
var _ alarms.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
func NewMetricsMiddleware(counter metrics.Counter, latency metrics.Histogram, service alarms.Service) alarms.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "create_alarm").Add(1)
|
||||
mm.latency.With("method", "create_alarm").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.CreateAlarm(ctx, alarm)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_alarm").Add(1)
|
||||
mm.latency.With("method", "update_alarm").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateAlarm(ctx, session, alarm)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "get_alarm").Add(1)
|
||||
mm.latency.With("method", "get_alarm").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ViewAlarm(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ListAlarms(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "list_alarms").Add(1)
|
||||
mm.latency.With("method", "list_alarms").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ListAlarms(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "delete_alarm").Add(1)
|
||||
mm.latency.With("method", "delete_alarm").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.DeleteAlarm(ctx, session, id)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
smqTracing "github.com/absmach/magistrala/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type tracingMiddleware struct {
|
||||
tracer trace.Tracer
|
||||
svc alarms.Service
|
||||
}
|
||||
|
||||
var _ alarms.Service = (*tracingMiddleware)(nil)
|
||||
|
||||
func NewTracingMiddleware(tracer trace.Tracer, svc alarms.Service) alarms.Service {
|
||||
return &tracingMiddleware{
|
||||
tracer: tracer,
|
||||
svc: svc,
|
||||
}
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "create_alarm", trace.WithAttributes(
|
||||
attribute.String("rule_id", alarm.RuleID),
|
||||
attribute.String("measurement", alarm.Measurement),
|
||||
attribute.String("value", alarm.Value),
|
||||
attribute.String("unit", alarm.Unit),
|
||||
attribute.String("cause", alarm.Cause),
|
||||
attribute.String("status", alarm.Status.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.CreateAlarm(ctx, alarm)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_alarm", trace.WithAttributes(
|
||||
attribute.String("rule_id", alarm.RuleID),
|
||||
attribute.String("measurement", alarm.Measurement),
|
||||
attribute.String("value", alarm.Value),
|
||||
attribute.String("unit", alarm.Unit),
|
||||
attribute.String("cause", alarm.Cause),
|
||||
attribute.String("status", alarm.Status.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateAlarm(ctx, session, alarm)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "get_alarm", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ViewAlarm(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ListAlarms(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "list_alarms", trace.WithAttributes(
|
||||
attribute.Int("offset", int(pm.Offset)),
|
||||
attribute.Int("limit", int(pm.Limit)),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ListAlarms(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "delete_alarm", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.DeleteAlarm(ctx, session, id)
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Repository {
|
||||
mock := &Repository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Repository is an autogenerated mock type for the Repository type
|
||||
type Repository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Repository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Repository) EXPECT() *Repository_Expecter {
|
||||
return &Repository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CreateAlarm provides a mock function for the type Repository
|
||||
func (_mock *Repository) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, alarm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, alarms.Alarm) error); ok {
|
||||
r1 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_CreateAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAlarm'
|
||||
type Repository_CreateAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - alarm alarms.Alarm
|
||||
func (_e *Repository_Expecter) CreateAlarm(ctx interface{}, alarm interface{}) *Repository_CreateAlarm_Call {
|
||||
return &Repository_CreateAlarm_Call{Call: _e.mock.On("CreateAlarm", ctx, alarm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_CreateAlarm_Call) Run(run func(ctx context.Context, alarm alarms.Alarm)) *Repository_CreateAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 alarms.Alarm
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(alarms.Alarm)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_CreateAlarm_Call) Return(alarm1 alarms.Alarm, err error) *Repository_CreateAlarm_Call {
|
||||
_c.Call.Return(alarm1, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_CreateAlarm_Call) RunAndReturn(run func(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error)) *Repository_CreateAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteAlarm provides a mock function for the type Repository
|
||||
func (_mock *Repository) DeleteAlarm(ctx context.Context, id string) error {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteAlarm")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_DeleteAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAlarm'
|
||||
type Repository_DeleteAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - id string
|
||||
func (_e *Repository_Expecter) DeleteAlarm(ctx interface{}, id interface{}) *Repository_DeleteAlarm_Call {
|
||||
return &Repository_DeleteAlarm_Call{Call: _e.mock.On("DeleteAlarm", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Repository_DeleteAlarm_Call) Run(run func(ctx context.Context, id string)) *Repository_DeleteAlarm_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,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_DeleteAlarm_Call) Return(err error) *Repository_DeleteAlarm_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_DeleteAlarm_Call) RunAndReturn(run func(ctx context.Context, id string) error) *Repository_DeleteAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// 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 ListAllAlarms")
|
||||
}
|
||||
|
||||
var r0 alarms.AlarmsPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.PageMetadata) (alarms.AlarmsPage, error)); ok {
|
||||
return returnFunc(ctx, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.PageMetadata) alarms.AlarmsPage); ok {
|
||||
r0 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.AlarmsPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, alarms.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ListAllAlarms is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - pm alarms.PageMetadata
|
||||
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_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 {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 alarms.PageMetadata
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(alarms.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListAllAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAllAlarms_Call {
|
||||
_c.Call.Return(alarmsPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// UpdateAlarm provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, alarm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, alarms.Alarm) error); ok {
|
||||
r1 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAlarm'
|
||||
type Repository_UpdateAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - alarm alarms.Alarm
|
||||
func (_e *Repository_Expecter) UpdateAlarm(ctx interface{}, alarm interface{}) *Repository_UpdateAlarm_Call {
|
||||
return &Repository_UpdateAlarm_Call{Call: _e.mock.On("UpdateAlarm", ctx, alarm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateAlarm_Call) Run(run func(ctx context.Context, alarm alarms.Alarm)) *Repository_UpdateAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 alarms.Alarm
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(alarms.Alarm)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateAlarm_Call) Return(alarm1 alarms.Alarm, err error) *Repository_UpdateAlarm_Call {
|
||||
_c.Call.Return(alarm1, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateAlarm_Call) RunAndReturn(run func(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error)) *Repository_UpdateAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewAlarm provides a mock function for the type Repository
|
||||
func (_mock *Repository) ViewAlarm(ctx context.Context, alarmID string, domainID string) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, alarmID, domainID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, alarmID, domainID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, alarmID, domainID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = returnFunc(ctx, alarmID, domainID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ViewAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewAlarm'
|
||||
type Repository_ViewAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - alarmID string
|
||||
// - domainID string
|
||||
func (_e *Repository_Expecter) ViewAlarm(ctx interface{}, alarmID interface{}, domainID interface{}) *Repository_ViewAlarm_Call {
|
||||
return &Repository_ViewAlarm_Call{Call: _e.mock.On("ViewAlarm", ctx, alarmID, domainID)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewAlarm_Call) Run(run func(ctx context.Context, alarmID string, domainID string)) *Repository_ViewAlarm_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 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewAlarm_Call) Return(alarm alarms.Alarm, err error) *Repository_ViewAlarm_Call {
|
||||
_c.Call.Return(alarm, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewAlarm_Call) RunAndReturn(run func(ctx context.Context, alarmID string, domainID string) (alarms.Alarm, error)) *Repository_ViewAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewService(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Service {
|
||||
mock := &Service{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Service is an autogenerated mock type for the Service type
|
||||
type Service struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Service_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Service) EXPECT() *Service_Expecter {
|
||||
return &Service_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CreateAlarm provides a mock function for the type Service
|
||||
func (_mock *Service) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, alarm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, alarms.Alarm) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, alarms.Alarm) error); ok {
|
||||
r1 = returnFunc(ctx, alarm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_CreateAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAlarm'
|
||||
type Service_CreateAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - alarm alarms.Alarm
|
||||
func (_e *Service_Expecter) CreateAlarm(ctx interface{}, alarm interface{}) *Service_CreateAlarm_Call {
|
||||
return &Service_CreateAlarm_Call{Call: _e.mock.On("CreateAlarm", ctx, alarm)}
|
||||
}
|
||||
|
||||
func (_c *Service_CreateAlarm_Call) Run(run func(ctx context.Context, alarm alarms.Alarm)) *Service_CreateAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 alarms.Alarm
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(alarms.Alarm)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_CreateAlarm_Call) Return(alarm1 alarms.Alarm, err error) *Service_CreateAlarm_Call {
|
||||
_c.Call.Return(alarm1, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_CreateAlarm_Call) RunAndReturn(run func(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error)) *Service_CreateAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteAlarm provides a mock function for the type Service
|
||||
func (_mock *Service) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteAlarm")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_DeleteAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAlarm'
|
||||
type Service_DeleteAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - session authn.Session
|
||||
// - id string
|
||||
func (_e *Service_Expecter) DeleteAlarm(ctx interface{}, session interface{}, id interface{}) *Service_DeleteAlarm_Call {
|
||||
return &Service_DeleteAlarm_Call{Call: _e.mock.On("DeleteAlarm", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_DeleteAlarm_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_DeleteAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 authn.Session
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(authn.Session)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DeleteAlarm_Call) Return(err error) *Service_DeleteAlarm_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DeleteAlarm_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) error) *Service_DeleteAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListAlarms provides a mock function for the type Service
|
||||
func (_mock *Service) ListAlarms(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
ret := _mock.Called(ctx, session, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListAlarms")
|
||||
}
|
||||
|
||||
var r0 alarms.AlarmsPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, alarms.PageMetadata) (alarms.AlarmsPage, error)); ok {
|
||||
return returnFunc(ctx, session, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, alarms.PageMetadata) alarms.AlarmsPage); ok {
|
||||
r0 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.AlarmsPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, alarms.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ListAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAlarms'
|
||||
type Service_ListAlarms_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListAlarms is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - session authn.Session
|
||||
// - pm alarms.PageMetadata
|
||||
func (_e *Service_Expecter) ListAlarms(ctx interface{}, session interface{}, pm interface{}) *Service_ListAlarms_Call {
|
||||
return &Service_ListAlarms_Call{Call: _e.mock.On("ListAlarms", ctx, session, pm)}
|
||||
}
|
||||
|
||||
func (_c *Service_ListAlarms_Call) Run(run func(ctx context.Context, session authn.Session, pm alarms.PageMetadata)) *Service_ListAlarms_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 authn.Session
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(authn.Session)
|
||||
}
|
||||
var arg2 alarms.PageMetadata
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(alarms.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Service_ListAlarms_Call {
|
||||
_c.Call.Return(alarmsPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListAlarms_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Service_ListAlarms_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateAlarm provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, session, alarm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, alarms.Alarm) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, session, alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, alarms.Alarm) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, session, alarm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, alarms.Alarm) error); ok {
|
||||
r1 = returnFunc(ctx, session, alarm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_UpdateAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAlarm'
|
||||
type Service_UpdateAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - session authn.Session
|
||||
// - alarm alarms.Alarm
|
||||
func (_e *Service_Expecter) UpdateAlarm(ctx interface{}, session interface{}, alarm interface{}) *Service_UpdateAlarm_Call {
|
||||
return &Service_UpdateAlarm_Call{Call: _e.mock.On("UpdateAlarm", ctx, session, alarm)}
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateAlarm_Call) Run(run func(ctx context.Context, session authn.Session, alarm alarms.Alarm)) *Service_UpdateAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 authn.Session
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(authn.Session)
|
||||
}
|
||||
var arg2 alarms.Alarm
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(alarms.Alarm)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateAlarm_Call) Return(alarm1 alarms.Alarm, err error) *Service_UpdateAlarm_Call {
|
||||
_c.Call.Return(alarm1, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateAlarm_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error)) *Service_UpdateAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewAlarm provides a mock function for the type Service
|
||||
func (_mock *Service) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewAlarm")
|
||||
}
|
||||
|
||||
var r0 alarms.Alarm
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (alarms.Alarm, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) alarms.Alarm); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(alarms.Alarm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ViewAlarm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewAlarm'
|
||||
type Service_ViewAlarm_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewAlarm is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - session authn.Session
|
||||
// - id string
|
||||
func (_e *Service_Expecter) ViewAlarm(ctx interface{}, session interface{}, id interface{}) *Service_ViewAlarm_Call {
|
||||
return &Service_ViewAlarm_Call{Call: _e.mock.On("ViewAlarm", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_ViewAlarm_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_ViewAlarm_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 authn.Session
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(authn.Session)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewAlarm_Call) Return(alarm alarms.Alarm, err error) *Service_ViewAlarm_Call {
|
||||
_c.Call.Return(alarm, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewAlarm_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error)) *Service_ViewAlarm_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package operations
|
||||
|
||||
import "github.com/absmach/magistrala/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: "alarm_assign",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpAcknowledgeAlarm: {
|
||||
Name: "alarm_acknowledge",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpResolveAlarm: {
|
||||
Name: "alarm_resolve",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateAlarm: {
|
||||
Name: "update",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
api "github.com/absmach/magistrala/api/http"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
"github.com/absmach/magistrala/pkg/postgres"
|
||||
"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
|
||||
}
|
||||
|
||||
var _ alarms.Repository = (*repository)(nil)
|
||||
|
||||
func NewAlarmsRepo(db *sqlx.DB) alarms.Repository {
|
||||
return &repository{db: db}
|
||||
}
|
||||
|
||||
func (r *repository) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
query := `
|
||||
WITH existing AS (
|
||||
SELECT status, severity
|
||||
FROM alarms
|
||||
WHERE domain_id = :domain_id
|
||||
AND rule_id = :rule_id
|
||||
AND channel_id = :channel_id
|
||||
AND client_id = :client_id
|
||||
AND subtopic = :subtopic
|
||||
AND measurement = :measurement
|
||||
AND created_at <= :created_at
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
)
|
||||
INSERT INTO alarms (
|
||||
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
|
||||
)
|
||||
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
|
||||
WHERE (
|
||||
EXISTS (
|
||||
SELECT 1 FROM existing
|
||||
WHERE existing.status IS DISTINCT FROM :status
|
||||
OR (:status = 0 AND existing.status = 0 AND existing.severity IS DISTINCT FROM :severity)
|
||||
)
|
||||
OR (
|
||||
NOT EXISTS (SELECT 1 FROM existing) AND :status = 0
|
||||
)
|
||||
)
|
||||
RETURNING
|
||||
id, rule_id, domain_id, channel_id, client_id, subtopic, measurement,
|
||||
value, unit, threshold, cause, status, severity, created_at,
|
||||
assignee_id, updated_at, updated_by, assigned_at, assigned_by,
|
||||
acknowledged_at, acknowledged_by, resolved_at, resolved_by, metadata
|
||||
;
|
||||
`
|
||||
dba, err := toDBAlarm(alarm)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
row, err := r.db.NamedQueryContext(ctx, query, dba)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, postgres.HandleError(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
if !row.Next() {
|
||||
return alarms.Alarm{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
dba = dbAlarm{}
|
||||
if err := row.StructScan(&dba); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
return toAlarm(dba)
|
||||
}
|
||||
|
||||
func (r *repository) UpdateAlarm(ctx context.Context, alarm alarms.Alarm) (alarms.Alarm, error) {
|
||||
var query []string
|
||||
var upq string
|
||||
if alarm.Status != 0 {
|
||||
query = append(query, "status = :status,")
|
||||
}
|
||||
if alarm.AssigneeID != "" {
|
||||
query = append(query, "assignee_id = :assignee_id,")
|
||||
}
|
||||
if !alarm.AssignedAt.IsZero() {
|
||||
query = append(query, "assigned_at = :assigned_at,")
|
||||
}
|
||||
if alarm.AssignedBy != "" {
|
||||
query = append(query, "assigned_by = :assigned_by,")
|
||||
}
|
||||
if alarm.AcknowledgedBy != "" {
|
||||
query = append(query, "acknowledged_by = :acknowledged_by,")
|
||||
}
|
||||
if !alarm.AcknowledgedAt.IsZero() {
|
||||
query = append(query, "acknowledged_at = :acknowledged_at,")
|
||||
}
|
||||
if alarm.ResolvedBy != "" {
|
||||
query = append(query, "resolved_by = :resolved_by,")
|
||||
}
|
||||
if !alarm.ResolvedAt.IsZero() {
|
||||
query = append(query, "resolved_at = :resolved_at,")
|
||||
}
|
||||
if alarm.Metadata != nil {
|
||||
query = append(query, "metadata = :metadata,")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
upq = strings.Join(query, " ")
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`UPDATE alarms SET %s updated_by = :updated_by, updated_at = :updated_at WHERE id = :id
|
||||
RETURNING id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit, threshold,
|
||||
cause, status, severity, assignee_id, assigned_at, assigned_by, acknowledged_at, acknowledged_by,
|
||||
resolved_by, resolved_at, metadata, created_at, updated_by, updated_at;`, upq)
|
||||
|
||||
dba, err := toDBAlarm(alarm)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
row, err := r.db.NamedQueryContext(ctx, q, dba)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
if !row.Next() {
|
||||
return alarms.Alarm{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
dba = dbAlarm{}
|
||||
if err := row.StructScan(&dba); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return toAlarm(dba)
|
||||
}
|
||||
|
||||
func (r *repository) ViewAlarm(ctx context.Context, alarmID, domainID string) (alarms.Alarm, error) {
|
||||
query := `SELECT * FROM alarms WHERE id = :id AND domain_id = :domain_id;`
|
||||
row, err := r.db.NamedQueryContext(ctx, query, map[string]any{
|
||||
"id": alarmID, "domain_id": domainID,
|
||||
})
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, postgres.HandleError(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
if !row.Next() {
|
||||
return alarms.Alarm{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
dba := dbAlarm{}
|
||||
if err := row.StructScan(&dba); err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
alarm, err := toAlarm(dba)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return alarm, nil
|
||||
}
|
||||
|
||||
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) alarmsPage(ctx context.Context, comQuery string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
dir := api.DescDir
|
||||
if pm.Dir == api.AscDir {
|
||||
dir = api.AscDir
|
||||
}
|
||||
|
||||
var orderClause string
|
||||
switch pm.Order {
|
||||
case api.CreatedAtOrder:
|
||||
orderClause = fmt.Sprintf("ORDER BY 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 * 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 {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []alarms.Alarm
|
||||
for rows.Next() {
|
||||
dba := dbAlarm{}
|
||||
if err := rows.StructScan(&dba); err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
a, err := toAlarm(dba)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, a)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, r.db, cq, pm)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return alarms.AlarmsPage{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Alarms: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *repository) DeleteAlarm(ctx context.Context, id string) error {
|
||||
query := `DELETE FROM alarms WHERE id = :id;`
|
||||
result, err := r.db.NamedExecContext(ctx, query, map[string]any{"id": id})
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dbAlarm struct {
|
||||
ID string `db:"id"`
|
||||
RuleID string `db:"rule_id"`
|
||||
DomainID string `db:"domain_id"`
|
||||
ChannelID string `db:"channel_id"`
|
||||
ClientID string `db:"client_id"`
|
||||
Subtopic string `db:"subtopic"`
|
||||
Measurement string `db:"measurement"`
|
||||
Value string `db:"value"`
|
||||
Unit string `db:"unit"`
|
||||
Cause string `db:"cause"`
|
||||
Threshold string `db:"threshold"`
|
||||
Status alarms.Status `db:"status"`
|
||||
Severity uint8 `db:"severity"`
|
||||
AssigneeID string `db:"assignee_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
||||
UpdatedBy *string `db:"updated_by,omitempty"`
|
||||
AssignedAt sql.NullTime `db:"assigned_at,omitempty"`
|
||||
AssignedBy *string `db:"assigned_by,omitempty"`
|
||||
AcknowledgedAt sql.NullTime `db:"acknowledged_at,omitempty"`
|
||||
AcknowledgedBy *string `db:"acknowledged_by,omitempty"`
|
||||
ResolvedAt sql.NullTime `db:"resolved_at,omitempty"`
|
||||
ResolvedBy *string `db:"resolved_by,omitempty"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func toDBAlarm(a alarms.Alarm) (dbAlarm, error) {
|
||||
if a.CreatedAt.IsZero() {
|
||||
a.CreatedAt = time.Now()
|
||||
}
|
||||
var updatedBy *string
|
||||
if a.UpdatedBy != "" {
|
||||
updatedBy = &a.UpdatedBy
|
||||
}
|
||||
var updatedAt sql.NullTime
|
||||
if a.UpdatedAt != (time.Time{}) {
|
||||
updatedAt = sql.NullTime{Time: a.UpdatedAt, Valid: true}
|
||||
}
|
||||
|
||||
var acknowledgedBy *string
|
||||
if a.AcknowledgedBy != "" {
|
||||
acknowledgedBy = &a.AcknowledgedBy
|
||||
}
|
||||
var acknowledgedAt sql.NullTime
|
||||
if a.AcknowledgedAt != (time.Time{}) {
|
||||
acknowledgedAt = sql.NullTime{Time: a.AcknowledgedAt, Valid: true}
|
||||
}
|
||||
|
||||
var resolvedBy *string
|
||||
if a.ResolvedBy != "" {
|
||||
resolvedBy = &a.ResolvedBy
|
||||
}
|
||||
var resolvedAt sql.NullTime
|
||||
if a.ResolvedAt != (time.Time{}) {
|
||||
resolvedAt = sql.NullTime{Time: a.ResolvedAt, Valid: true}
|
||||
}
|
||||
|
||||
var assignedBy *string
|
||||
if a.AssignedBy != "" {
|
||||
assignedBy = &a.AssignedBy
|
||||
}
|
||||
var assignedAt sql.NullTime
|
||||
if a.AssignedAt != (time.Time{}) {
|
||||
assignedAt = sql.NullTime{Time: a.AssignedAt, Valid: true}
|
||||
}
|
||||
|
||||
metadata := []byte("{}")
|
||||
if len(a.Metadata) > 0 {
|
||||
b, err := json.Marshal(a.Metadata)
|
||||
if err != nil {
|
||||
return dbAlarm{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
|
||||
}
|
||||
metadata = b
|
||||
}
|
||||
|
||||
return dbAlarm{
|
||||
ID: a.ID,
|
||||
RuleID: a.RuleID,
|
||||
DomainID: a.DomainID,
|
||||
ChannelID: a.ChannelID,
|
||||
ClientID: a.ClientID,
|
||||
Subtopic: a.Subtopic,
|
||||
Measurement: a.Measurement,
|
||||
Value: a.Value,
|
||||
Unit: a.Unit,
|
||||
Cause: a.Cause,
|
||||
Threshold: a.Threshold,
|
||||
Status: a.Status,
|
||||
Severity: a.Severity,
|
||||
AssigneeID: a.AssigneeID,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
AssignedAt: assignedAt,
|
||||
AssignedBy: assignedBy,
|
||||
AcknowledgedAt: acknowledgedAt,
|
||||
AcknowledgedBy: acknowledgedBy,
|
||||
ResolvedAt: resolvedAt,
|
||||
ResolvedBy: resolvedBy,
|
||||
Metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
|
||||
var updatedBy string
|
||||
if dbr.UpdatedBy != nil {
|
||||
updatedBy = *dbr.UpdatedBy
|
||||
}
|
||||
var updatedAt time.Time
|
||||
if dbr.UpdatedAt.Valid {
|
||||
updatedAt = dbr.UpdatedAt.Time
|
||||
}
|
||||
|
||||
var assignedBy string
|
||||
if dbr.AssignedBy != nil {
|
||||
assignedBy = *dbr.AssignedBy
|
||||
}
|
||||
var assignedAt time.Time
|
||||
if dbr.AssignedAt.Valid {
|
||||
assignedAt = dbr.AssignedAt.Time
|
||||
}
|
||||
|
||||
var acknowledgedBy string
|
||||
if dbr.AcknowledgedBy != nil {
|
||||
acknowledgedBy = *dbr.AcknowledgedBy
|
||||
}
|
||||
var acknowledgedAt time.Time
|
||||
if dbr.AcknowledgedAt.Valid {
|
||||
acknowledgedAt = dbr.AcknowledgedAt.Time
|
||||
}
|
||||
|
||||
var resolvedBy string
|
||||
if dbr.ResolvedBy != nil {
|
||||
resolvedBy = *dbr.ResolvedBy
|
||||
}
|
||||
var resolvedAt time.Time
|
||||
if dbr.ResolvedAt.Valid {
|
||||
resolvedAt = dbr.ResolvedAt.Time
|
||||
}
|
||||
|
||||
var metadata map[string]any
|
||||
if len(dbr.Metadata) > 0 {
|
||||
err := json.Unmarshal(dbr.Metadata, &metadata)
|
||||
if err != nil {
|
||||
return alarms.Alarm{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
return alarms.Alarm{
|
||||
ID: dbr.ID,
|
||||
RuleID: dbr.RuleID,
|
||||
DomainID: dbr.DomainID,
|
||||
ChannelID: dbr.ChannelID,
|
||||
ClientID: dbr.ClientID,
|
||||
Subtopic: dbr.Subtopic,
|
||||
Measurement: dbr.Measurement,
|
||||
Value: dbr.Value,
|
||||
Unit: dbr.Unit,
|
||||
Threshold: dbr.Threshold,
|
||||
Cause: dbr.Cause,
|
||||
Status: dbr.Status,
|
||||
Severity: dbr.Severity,
|
||||
AssigneeID: dbr.AssigneeID,
|
||||
CreatedAt: dbr.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
AssignedAt: assignedAt,
|
||||
AssignedBy: assignedBy,
|
||||
AcknowledgedAt: acknowledgedAt,
|
||||
AcknowledgedBy: acknowledgedBy,
|
||||
ResolvedAt: resolvedAt,
|
||||
ResolvedBy: resolvedBy,
|
||||
Metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pageQuery(pm alarms.PageMetadata) (string, error) {
|
||||
query := pageQueryConditions(pm)
|
||||
|
||||
var emq string
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return emq, nil
|
||||
}
|
||||
|
||||
func pageQueryConditions(pm alarms.PageMetadata) []string {
|
||||
var query []string
|
||||
if pm.DomainID != "" {
|
||||
query = append(query, "alarms.domain_id = :domain_id")
|
||||
}
|
||||
if pm.RuleID != "" {
|
||||
query = append(query, "alarms.rule_id = :rule_id")
|
||||
}
|
||||
if len(pm.RuleIDs) > 0 {
|
||||
query = append(query, "alarms.rule_id = ANY(:rule_ids)")
|
||||
}
|
||||
if pm.ChannelID != "" {
|
||||
query = append(query, "alarms.channel_id = :channel_id")
|
||||
}
|
||||
if pm.Subtopic != "" {
|
||||
query = append(query, "alarms.subtopic = :subtopic")
|
||||
}
|
||||
if pm.ClientID != "" {
|
||||
query = append(query, "alarms.client_id = :client_id")
|
||||
}
|
||||
if pm.Measurement != "" {
|
||||
query = append(query, "alarms.measurement = :measurement")
|
||||
}
|
||||
if pm.Status != alarms.AllStatus {
|
||||
query = append(query, "alarms.status = :status")
|
||||
}
|
||||
if pm.Severity != math.MaxUint8 {
|
||||
query = append(query, "alarms.severity = :severity")
|
||||
}
|
||||
if pm.AssigneeID != "" {
|
||||
query = append(query, "alarms.assignee_id = :assignee_id")
|
||||
}
|
||||
if pm.UpdatedBy != "" {
|
||||
query = append(query, "alarms.updated_by = :updated_by")
|
||||
}
|
||||
if pm.ResolvedBy != "" {
|
||||
query = append(query, "alarms.resolved_by = :resolved_by")
|
||||
}
|
||||
if pm.AcknowledgedBy != "" {
|
||||
query = append(query, "alarms.acknowledged_by = :acknowledged_by")
|
||||
}
|
||||
if pm.AssignedBy != "" {
|
||||
query = append(query, "alarms.assigned_by = :assigned_by")
|
||||
}
|
||||
if !pm.CreatedFrom.IsZero() {
|
||||
query = append(query, "alarms.created_at >= :created_from")
|
||||
}
|
||||
if !pm.CreatedTo.IsZero() {
|
||||
query = append(query, "alarms.created_at <= :created_to")
|
||||
}
|
||||
return query
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/postgres"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
namegen = namegenerator.NewGenerator()
|
||||
idProvider = uuid.New()
|
||||
)
|
||||
|
||||
func TestCreateAlarm(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
alarm := alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
ChannelID: generateUUID(t),
|
||||
ClientID: generateUUID(t),
|
||||
Subtopic: namegen.Generate(),
|
||||
Measurement: namegen.Generate(),
|
||||
Value: namegen.Generate(),
|
||||
Unit: namegen.Generate(),
|
||||
Threshold: namegen.Generate(),
|
||||
Cause: namegen.Generate(),
|
||||
Status: 0,
|
||||
AssigneeID: generateUUID(t),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
alarm: alarm,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "duplicate alarm",
|
||||
alarm: alarm,
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "missing rule id",
|
||||
alarm: alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
ChannelID: generateUUID(t),
|
||||
ClientID: generateUUID(t),
|
||||
Subtopic: namegen.Generate(),
|
||||
Measurement: namegen.Generate(),
|
||||
Value: namegen.Generate(),
|
||||
Unit: namegen.Generate(),
|
||||
Threshold: namegen.Generate(),
|
||||
Cause: namegen.Generate(),
|
||||
Status: 0,
|
||||
AssigneeID: generateUUID(t),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
},
|
||||
{
|
||||
desc: "invalid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
ChannelID: generateUUID(t),
|
||||
ClientID: generateUUID(t),
|
||||
Subtopic: namegen.Generate(),
|
||||
Measurement: namegen.Generate(),
|
||||
Value: namegen.Generate(),
|
||||
Unit: namegen.Generate(),
|
||||
Threshold: namegen.Generate(),
|
||||
Cause: namegen.Generate(),
|
||||
Status: 0,
|
||||
AssigneeID: generateUUID(t),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
|
||||
Metadata: map[string]any{
|
||||
"key": make(chan int),
|
||||
},
|
||||
},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
},
|
||||
{
|
||||
desc: "empty alarm",
|
||||
alarm: alarms.Alarm{},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
alarm, err := repo.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.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
assert.NotEmpty(t, alarm.ID)
|
||||
assert.Equal(t, tc.alarm.RuleID, alarm.RuleID)
|
||||
assert.Equal(t, tc.alarm.Measurement, alarm.Measurement)
|
||||
assert.Equal(t, tc.alarm.Value, alarm.Value)
|
||||
assert.Equal(t, tc.alarm.Unit, alarm.Unit)
|
||||
assert.Equal(t, tc.alarm.Cause, alarm.Cause)
|
||||
assert.Equal(t, tc.alarm.Status, alarm.Status)
|
||||
assert.Equal(t, tc.alarm.DomainID, alarm.DomainID)
|
||||
assert.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
|
||||
assert.Equal(t, tc.alarm.Metadata, alarm.Metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAlarm(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
alarm := alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
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(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
alarm, err := repo.CreateAlarm(context.Background(), alarm)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
ID: alarm.ID,
|
||||
Status: alarms.ClearedStatus,
|
||||
DomainID: alarm.DomainID,
|
||||
AssigneeID: generateUUID(t),
|
||||
AssignedBy: generateUUID(t),
|
||||
AssignedAt: time.Now().UTC(),
|
||||
AcknowledgedBy: generateUUID(t),
|
||||
AcknowledgedAt: time.Now().UTC(),
|
||||
CreatedAt: alarm.CreatedAt,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
UpdatedBy: generateUUID(t),
|
||||
ResolvedAt: time.Now().UTC(),
|
||||
ResolvedBy: generateUUID(t),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm",
|
||||
alarm: alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
},
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "invalid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
ID: alarm.ID,
|
||||
RuleID: generateUUID(t),
|
||||
Status: 0,
|
||||
DomainID: generateUUID(t),
|
||||
AssigneeID: strings.Repeat("a", 40),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
err: repoerr.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "empty alarm",
|
||||
alarm: alarms.Alarm{},
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
alarm, err := repo.UpdateAlarm(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.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
assert.NotEmpty(t, alarm.ID)
|
||||
assert.Equal(t, tc.alarm.Status, alarm.Status)
|
||||
assert.Equal(t, tc.alarm.DomainID, alarm.DomainID)
|
||||
assert.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
|
||||
assert.Equal(t, tc.alarm.UpdatedBy, alarm.UpdatedBy)
|
||||
assert.Equal(t, tc.alarm.ResolvedBy, alarm.ResolvedBy)
|
||||
assert.Equal(t, tc.alarm.AcknowledgedBy, alarm.AcknowledgedBy)
|
||||
assert.Equal(t, tc.alarm.Metadata, alarm.Metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewAlarm(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
alarm := alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
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(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
alarm, err := repo.CreateAlarm(context.Background(), alarm)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
domainID string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
id: alarm.ID,
|
||||
domainID: alarm.DomainID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm id",
|
||||
id: generateUUID(t),
|
||||
domainID: alarm.DomainID,
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "non existing domain id",
|
||||
id: alarm.ID,
|
||||
domainID: generateUUID(t),
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
alarm, err := repo.ViewAlarm(context.Background(), tc.id, tc.domainID)
|
||||
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.NotEmpty(t, alarm.ID)
|
||||
assert.Equal(t, tc.id, alarm.ID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAlarms(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
})
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
items := make([]alarms.Alarm, 1000)
|
||||
for i := range 1000 {
|
||||
items[i] = alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
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(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
alarm, err := repo.CreateAlarm(context.Background(), items[i])
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
items[i].ID = alarm.ID
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
pm alarms.PageMetadata
|
||||
response []alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid page",
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
response: items[:10],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "offset and limit",
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 10,
|
||||
Limit: 50,
|
||||
},
|
||||
response: items[10:60],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "empty page",
|
||||
pm: alarms.PageMetadata{},
|
||||
response: []alarms.Alarm{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid page",
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 1000,
|
||||
Limit: 10,
|
||||
},
|
||||
response: []alarms.Alarm{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "invalid assignee id",
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
AssigneeID: generateUUID(t),
|
||||
},
|
||||
response: []alarms.Alarm{},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
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))
|
||||
|
||||
return
|
||||
}
|
||||
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
assert.Equal(t, len(tc.response), len(alarms.Alarms))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAlarm(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
alarm := alarms.Alarm{
|
||||
ID: generateUUID(t),
|
||||
RuleID: generateUUID(t),
|
||||
DomainID: generateUUID(t),
|
||||
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(),
|
||||
Metadata: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
alarm, err := repo.CreateAlarm(context.Background(), alarm)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
id: alarm.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm",
|
||||
id: generateUUID(t),
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
err := repo.DeleteAlarm(context.Background(), tc.id)
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateUUID(t *testing.T) string {
|
||||
ulid, err := idProvider.ID()
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
return ulid
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
// Migration of Alarms service.
|
||||
func Migration() (*migrate.MemoryMigrationSource, error) {
|
||||
alarmsMigration := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "alarms_01",
|
||||
// VARCHAR(36) for columns with IDs as UUIDS have a maximum of 36 characters
|
||||
Up: []string{
|
||||
`CREATE TABLE IF NOT EXISTS alarms (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
rule_id VARCHAR(36) NOT NULL CHECK (length(rule_id) > 0),
|
||||
domain_id VARCHAR(36) NOT NULL,
|
||||
channel_id VARCHAR(36) NOT NULL,
|
||||
subtopic TEXT NOT NULL,
|
||||
client_id VARCHAR(36) NOT NULL,
|
||||
measurement TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
threshold TEXT NOT NULL,
|
||||
cause TEXT NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
|
||||
severity SMALLINT NOT NULL DEFAULT 0 CHECK (severity >= 0),
|
||||
assignee_id VARCHAR(36),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NULL,
|
||||
updated_by VARCHAR(36) NULL,
|
||||
assigned_at TIMESTAMPTZ NULL,
|
||||
assigned_by VARCHAR(36) NULL,
|
||||
acknowledged_at TIMESTAMPTZ NULL,
|
||||
acknowledged_by VARCHAR(36) NULL,
|
||||
resolved_at TIMESTAMPTZ NULL,
|
||||
resolved_by VARCHAR(36) NULL,
|
||||
metadata JSONB
|
||||
);`,
|
||||
"CREATE INDEX IF NOT EXISTS idx_alarms_state ON alarms (domain_id, rule_id, channel_id, subtopic, client_id, measurement, created_at DESC);",
|
||||
},
|
||||
Down: []string{
|
||||
`DROP TABLE IF EXISTS alarms`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return alarmsMigration, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apostgres "github.com/absmach/magistrala/alarms/postgres"
|
||||
"github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/jmoiron/sqlx"
|
||||
dockertest "github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var (
|
||||
db *sqlx.DB
|
||||
database postgres.Database
|
||||
tracer = otel.Tracer("repo_tests")
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
container, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "postgres",
|
||||
Tag: "16.2-alpine",
|
||||
Env: []string{
|
||||
"POSTGRES_USER=test",
|
||||
"POSTGRES_PASSWORD=test",
|
||||
"POSTGRES_DB=test",
|
||||
"listen_addresses = '*'",
|
||||
},
|
||||
}, func(config *docker.HostConfig) {
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start container: %s", err)
|
||||
}
|
||||
|
||||
port := container.GetPort("5432/tcp")
|
||||
|
||||
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
|
||||
pool.MaxWait = 120 * time.Second
|
||||
if err := pool.Retry(func() error {
|
||||
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
|
||||
db, err := sql.Open("pgx", url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
dbConfig := postgres.Config{
|
||||
Host: "localhost",
|
||||
Port: port,
|
||||
User: "test",
|
||||
Pass: "test",
|
||||
Name: "test",
|
||||
SSLMode: "disable",
|
||||
SSLCert: "",
|
||||
SSLKey: "",
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
database = postgres.NewDatabase(db, dbConfig, tracer)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
// Defers will not be run when using os.Exit
|
||||
db.Close()
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Fatalf("Could not purge container: %s", err)
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
idp magistrala.IDProvider
|
||||
repo Repository
|
||||
}
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
func NewService(idp magistrala.IDProvider, repo Repository) Service {
|
||||
return &service{
|
||||
idp: idp,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) (Alarm, error) {
|
||||
id, err := s.idp.ID()
|
||||
if err != nil {
|
||||
return Alarm{}, err
|
||||
}
|
||||
alarm.ID = id
|
||||
if alarm.CreatedAt.IsZero() {
|
||||
alarm.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
if err := alarm.Validate(); err != nil {
|
||||
return Alarm{}, err
|
||||
}
|
||||
|
||||
created, err := s.repo.CreateAlarm(ctx, alarm)
|
||||
if err != nil && err != repoerr.ErrNotFound {
|
||||
return Alarm{}, err
|
||||
}
|
||||
if err == repoerr.ErrNotFound {
|
||||
return Alarm{}, nil
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID string) (Alarm, error) {
|
||||
return s.repo.ViewAlarm(ctx, alarmID, session.DomainID)
|
||||
}
|
||||
|
||||
func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) {
|
||||
pm.DomainID = session.DomainID
|
||||
return s.repo.ListAllAlarms(ctx, pm)
|
||||
}
|
||||
|
||||
func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error {
|
||||
return s.repo.DeleteAlarm(ctx, alarmID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateAlarm(ctx context.Context, session authn.Session, alarm Alarm) (Alarm, error) {
|
||||
alarm.UpdatedAt = time.Now()
|
||||
alarm.UpdatedBy = session.UserID
|
||||
|
||||
return s.repo.UpdateAlarm(ctx, alarm)
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
"github.com/absmach/magistrala/alarms/mocks"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
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 := newService(t, repo)
|
||||
ts := time.Now()
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: "rule-id",
|
||||
DomainID: "domain-id",
|
||||
ChannelID: "channel-id",
|
||||
ClientID: "client-id",
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
CreatedAt: ts,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "missing rule_id",
|
||||
alarm: alarms.Alarm{
|
||||
DomainID: "domain-id",
|
||||
ChannelID: "channel-id",
|
||||
ClientID: "client-id",
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
CreatedAt: ts,
|
||||
},
|
||||
err: errors.New("rule_id is required"),
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
_, err := svc.CreateAlarm(context.Background(), tc.alarm)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
domainID string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
id: "alarm-id",
|
||||
domainID: "domain-id",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm id",
|
||||
id: "alarm-id",
|
||||
domainID: "domain-id",
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
s := authn.Session{DomainID: tc.domainID}
|
||||
repoCall := repo.On("ViewAlarm", context.Background(), tc.id, tc.domainID).Return(alarms.Alarm{}, tc.err)
|
||||
_, err := svc.ViewAlarm(context.Background(), s, tc.id)
|
||||
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
|
||||
}
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
alarm alarms.Alarm
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: "rule-id",
|
||||
DomainID: "domain-id",
|
||||
ChannelID: "channel-id",
|
||||
ClientID: "client-id",
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm",
|
||||
alarm: alarms.Alarm{
|
||||
RuleID: "rule-id",
|
||||
DomainID: "domain-id",
|
||||
ChannelID: "channel-id",
|
||||
ClientID: "client-id",
|
||||
Subtopic: "subtopic",
|
||||
Measurement: "measurement",
|
||||
Value: "value",
|
||||
Unit: "unit",
|
||||
Cause: "cause",
|
||||
Severity: 100,
|
||||
},
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
s := authn.Session{DomainID: tc.alarm.DomainID}
|
||||
repoCall := repo.On("UpdateAlarm", context.Background(), mock.Anything).Return(tc.alarm, tc.err)
|
||||
_, err := svc.UpdateAlarm(context.Background(), s, 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
|
||||
}
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAlarms(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
pm alarms.PageMetadata
|
||||
page alarms.AlarmsPage
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid page",
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
page: alarms.AlarmsPage{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Total: 10,
|
||||
Alarms: []alarms.Alarm{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
s := authn.Session{DomainID: tc.pm.DomainID}
|
||||
repoCall := repo.On("ListAllAlarms", context.Background(), 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))
|
||||
|
||||
return
|
||||
}
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAlarm(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
svc := newService(t, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid alarm",
|
||||
id: "alarm-id",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "non existing alarm",
|
||||
id: "alarm-id",
|
||||
err: repoerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
s := authn.Session{DomainID: tc.id}
|
||||
repoCall := repo.On("DeleteAlarm", context.Background(), tc.id).Return(tc.err)
|
||||
err := svc.DeleteAlarm(context.Background(), s, tc.id)
|
||||
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
|
||||
}
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package alarms
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
)
|
||||
|
||||
type Status uint8
|
||||
|
||||
const (
|
||||
ActiveStatus Status = iota
|
||||
ClearedStatus
|
||||
|
||||
// AllStatus is used for querying purposes to list alarms irrespective
|
||||
// of their status. It is never stored in the database as the actual
|
||||
// Alarm status and should always be the largest value in this enumeration.
|
||||
AllStatus
|
||||
)
|
||||
|
||||
const (
|
||||
Active = "active"
|
||||
Cleared = "cleared"
|
||||
Unknown = "unknown"
|
||||
All = "all"
|
||||
)
|
||||
|
||||
// String converts alarm status to string literal.
|
||||
func (s Status) String() string {
|
||||
switch s {
|
||||
case ActiveStatus:
|
||||
return Active
|
||||
case ClearedStatus:
|
||||
return Cleared
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// ToStatus converts string value to a valid Alarm status.
|
||||
func ToStatus(status string) (Status, error) {
|
||||
switch strings.ToLower(status) {
|
||||
case Active:
|
||||
return ActiveStatus, nil
|
||||
case Cleared:
|
||||
return ClearedStatus, nil
|
||||
case All:
|
||||
return AllStatus, nil
|
||||
default:
|
||||
return Status(0), svcerr.ErrInvalidStatus
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Marshaller for Alarm.
|
||||
func (s Status) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// Custom Unmarshaler for Alarm.
|
||||
func (s *Status) UnmarshalJSON(data []byte) error {
|
||||
str := strings.Trim(string(data), "\"")
|
||||
val, err := ToStatus(str)
|
||||
*s = val
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: auth/v1/auth.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: auth/v1/auth.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: certs/v1/certs.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: certs/v1/certs.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: channels/v1/channels.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: channels/v1/channels.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: clients/v1/clients.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: clients/v1/clients.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: common/v1/common.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: domains/v1/domains.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: domains/v1/domains.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: groups/v1/groups.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: groups/v1/groups.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: readers/v1/readers.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: token/v1/token.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: token/v1/token.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.0
|
||||
// protoc v7.35.1
|
||||
// source: users/v1/users.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.0
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: users/v1/users.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/absmach/magistrala"
|
||||
api "github.com/absmach/magistrala/api/http"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/testsutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Magistrala Journal Log Service
|
||||
description: |
|
||||
This is the Journal Log Server based on the OpenAPI 3.0 specification. It is the HTTP API for viewing journal log history. You can now help us improve the API whether it's by making changes to the definition itself or to the code.
|
||||
Some useful links:
|
||||
- [The Magistrala repository](https://github.com/absmach/magistrala)
|
||||
contact:
|
||||
email: info@mainflux.com
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9021
|
||||
- url: https://localhost:9021
|
||||
|
||||
tags:
|
||||
- name: journal-log
|
||||
description: Everything about your Journal Log
|
||||
externalDocs:
|
||||
description: Find out more about Journal Log
|
||||
url: http://docs.mainflux.io/
|
||||
|
||||
paths:
|
||||
/journal/user/{userID}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List user journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/user_id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
- $ref: "#/components/parameters/limit"
|
||||
- $ref: "#/components/parameters/operation"
|
||||
- $ref: "#/components/parameters/with_attributes"
|
||||
- $ref: "#/components/parameters/with_metadata"
|
||||
- $ref: "#/components/parameters/from"
|
||||
- $ref: "#/components/parameters/to"
|
||||
- $ref: "#/components/parameters/dir"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/JournalsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/{domainID}/journal/client/{clientID}/telemetry:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: View client telemetry
|
||||
description: |
|
||||
Retrieves telemetry data for a specific client within a domain.
|
||||
This includes connection status, messages sent/received, and other metrics.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/domain_id"
|
||||
- $ref: "#/components/parameters/client_id"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Client telemetry data retrieved successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Telemetry"
|
||||
"400":
|
||||
description: Failed due to malformed request parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: Client not found.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/{domainID}/journal/{entityType}/{id}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List entity journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/domain_id"
|
||||
- $ref: "#/components/parameters/entity_type"
|
||||
- $ref: "#/components/parameters/id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
- $ref: "#/components/parameters/limit"
|
||||
- $ref: "#/components/parameters/operation"
|
||||
- $ref: "#/components/parameters/with_attributes"
|
||||
- $ref: "#/components/parameters/with_metadata"
|
||||
- $ref: "#/components/parameters/from"
|
||||
- $ref: "#/components/parameters/to"
|
||||
- $ref: "#/components/parameters/dir"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/JournalsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/health:
|
||||
get:
|
||||
summary: Retrieves service health check info.
|
||||
tags:
|
||||
- health
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/HealthRes"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Telemetry:
|
||||
type: object
|
||||
properties:
|
||||
client_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier of the client
|
||||
example: "bb7edb32-2eac-4aad-aebe-ed96fe073879"
|
||||
domain_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier of the domain
|
||||
example: "29d425c8-542b-4614-8a4d-a5951945d720"
|
||||
subscriptions:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of active subscriptions for the client
|
||||
example: 5
|
||||
inbound_messages:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of messages received by the client
|
||||
example: 1234567
|
||||
outbound_messages:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of messages sent by the client
|
||||
example: 987654
|
||||
first_seen:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the client was first seen
|
||||
example: "2024-01-11T10:00:00.000Z"
|
||||
last_seen:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the client was last seen
|
||||
example: "2024-01-11T12:05:07.449053Z"
|
||||
|
||||
Journal:
|
||||
type: object
|
||||
properties:
|
||||
operation:
|
||||
type: string
|
||||
example: user.create
|
||||
description: Journal operation.
|
||||
occurred_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-11T12:05:07.449053Z"
|
||||
description: Time when the journal occurred.
|
||||
attributes:
|
||||
type: object
|
||||
description: Journal attributes.
|
||||
example:
|
||||
{
|
||||
"created_at": "2024-06-12T11:34:32.991591Z",
|
||||
"id": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"identity": "Gawne-Havlicek@email.com",
|
||||
"name": "Newgard-Frisina",
|
||||
"status": "enabled",
|
||||
"updated_at": "2024-06-12T11:34:33.116795Z",
|
||||
"updated_by": "ad228f20-4741-47c5-bef7-d871b541c019",
|
||||
}
|
||||
metadata:
|
||||
type: object
|
||||
description: Journal payload.
|
||||
example: { "Update": "Calvo-Felkins" }
|
||||
xml:
|
||||
name: journal
|
||||
|
||||
JournalPage:
|
||||
type: object
|
||||
properties:
|
||||
journals:
|
||||
type: array
|
||||
minItems: 0
|
||||
uniqueItems: true
|
||||
items:
|
||||
$ref: "#/components/schemas/Journal"
|
||||
total:
|
||||
type: integer
|
||||
example: 1
|
||||
description: Total number of items.
|
||||
offset:
|
||||
type: integer
|
||||
description: Number of items to skip during retrieval.
|
||||
limit:
|
||||
type: integer
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- journals
|
||||
- total
|
||||
- offset
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Error message
|
||||
example: { "error": "malformed entity specification" }
|
||||
|
||||
parameters:
|
||||
domain_id:
|
||||
name: domainID
|
||||
description: Unique identifier for a domain.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
client_id:
|
||||
name: clientID
|
||||
description: Unique identifier for a client
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
entity_type:
|
||||
name: entityType
|
||||
description: Type of entity, e.g. group, client, channel.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- group
|
||||
- client
|
||||
- channel
|
||||
required: true
|
||||
example: group
|
||||
|
||||
user_id:
|
||||
name: userID
|
||||
description: Unique identifier for a user.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
id:
|
||||
name: id
|
||||
description: Unique identifier for an entity, e.g. group, channel or client. Used together with entity_type.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
offset:
|
||||
name: offset
|
||||
description: Number of items to skip during retrieval.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
minimum: 0
|
||||
required: false
|
||||
example: 0
|
||||
|
||||
limit:
|
||||
name: limit
|
||||
description: Size of the subset to retrieve.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
required: false
|
||||
example: 10
|
||||
|
||||
operation:
|
||||
name: operation
|
||||
description: Journal operation.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: user.create
|
||||
|
||||
with_attributes:
|
||||
name: with_attributes
|
||||
description: Include journal attributes.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: true
|
||||
|
||||
with_metadata:
|
||||
name: with_metadata
|
||||
description: Include journal metadata.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: true
|
||||
|
||||
from:
|
||||
name: from
|
||||
description: Start date in unix time.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
example: 1966777289
|
||||
|
||||
to:
|
||||
name: to
|
||||
description: End date in unix time.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
example: 1966777289
|
||||
|
||||
dir:
|
||||
name: dir
|
||||
description: Sort direction.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
responses:
|
||||
JournalsPageRes:
|
||||
description: Data retrieved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/JournalPage"
|
||||
|
||||
HealthRes:
|
||||
description: Service Health Check.
|
||||
content:
|
||||
application/health+json:
|
||||
schema:
|
||||
$ref: "./schemas/health_info.yaml"
|
||||
|
||||
ServiceError:
|
||||
description: Unexpected server-side error occurred.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
* User access: "Authorization: Bearer <user_access_token>"
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
Vendored
+1
-1
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/auth"
|
||||
"github.com/absmach/magistrala/auth/cache"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/testsutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/auth"
|
||||
"github.com/absmach/magistrala/auth/mocks"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
policymocks "github.com/absmach/magistrala/pkg/policies/mocks"
|
||||
"github.com/absmach/magistrala/pkg/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
|
||||
@@ -25,7 +25,6 @@ const (
|
||||
defChannelsURL string = defURL + ":9005"
|
||||
defGroupsURL string = defURL + ":9004"
|
||||
defHTTPURL string = defURL + ":8008"
|
||||
defJournalURL string = defURL + ":9021"
|
||||
defTLSVerification bool = false
|
||||
defOffset string = "0"
|
||||
defLimit string = "10"
|
||||
@@ -41,7 +40,6 @@ type remotes struct {
|
||||
GroupsURL string `toml:"groups_url"`
|
||||
HTTPAdapterURL string `toml:"http_adapter_url"`
|
||||
CertsURL string `toml:"certs_url"`
|
||||
JournalURL string `toml:"journal_url"`
|
||||
HostURL string `toml:"host_url"`
|
||||
TLSVerification bool `toml:"tls_verification"`
|
||||
}
|
||||
@@ -110,7 +108,6 @@ func ParseConfig(sdkConf smqsdk.Config) (smqsdk.Config, error) {
|
||||
ChannelsURL: defChannelsURL,
|
||||
GroupsURL: defGroupsURL,
|
||||
HTTPAdapterURL: defHTTPURL,
|
||||
JournalURL: defJournalURL,
|
||||
HostURL: defURL,
|
||||
TLSVerification: defTLSVerification,
|
||||
},
|
||||
@@ -194,10 +191,6 @@ func ParseConfig(sdkConf smqsdk.Config) (smqsdk.Config, error) {
|
||||
sdkConf.CertsURL = config.Remotes.CertsURL
|
||||
}
|
||||
|
||||
if sdkConf.JournalURL == "" && config.Remotes.JournalURL != "" {
|
||||
sdkConf.JournalURL = config.Remotes.JournalURL
|
||||
}
|
||||
|
||||
if sdkConf.HostURL == "" && config.Remotes.HostURL != "" {
|
||||
sdkConf.HostURL = config.Remotes.HostURL
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
smqsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdJournal = cobra.Command{
|
||||
Use: "get <entity_type> <entity_id> <domain_id> <user_auth_token>",
|
||||
Short: "Get journal",
|
||||
Long: "Get journal\n" +
|
||||
"Usage:\n" +
|
||||
"\tmagistrala-cli journal get user <user_id> <user_auth_token> - lists user journal logs\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <domain_id> <user_auth_token> - lists entity journal logs\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <domain_id> <user_auth_token> --offset <offset> --limit <limit> - lists user journal logs with provided offset and limit\n",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 3 || len(args) > 4 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
pageMetadata := smqsdk.PageMetadata{
|
||||
Offset: Offset,
|
||||
Limit: Limit,
|
||||
}
|
||||
|
||||
entityType, entityID, token := args[0], args[1], args[2]
|
||||
domainID := ""
|
||||
if len(args) == 4 {
|
||||
entityType, entityID, domainID, token = args[0], args[1], args[2], args[3]
|
||||
}
|
||||
|
||||
journal, err := sdk.Journal(cmd.Context(), entityType, entityID, domainID, pageMetadata, token)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, journal)
|
||||
},
|
||||
}
|
||||
|
||||
// NewJournalCmd returns journal log command.
|
||||
func NewJournalCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "journal get",
|
||||
Short: "journal log",
|
||||
Long: `journal to read journal log`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(&cmdJournal)
|
||||
|
||||
return &cmd
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/alarms"
|
||||
httpAPI "github.com/absmach/magistrala/alarms/api"
|
||||
"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/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
"github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
"github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "alarms"
|
||||
envPrefixDB = "MG_ALARMS_DB_"
|
||||
envPrefixHTTP = "MG_ALARMS_HTTP_"
|
||||
defDB = "alarms"
|
||||
defSvcHTTPPort = "8050"
|
||||
alarmEntity = "alarm"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
|
||||
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
PermissionsFile string `env:"MG_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err.Error())
|
||||
}
|
||||
|
||||
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err.Error())
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer mglog.ExitWithError(&exitCode)
|
||||
|
||||
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("error shutting down tracer provider: %v", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
dbConfig := postgres.Config{Name: defDB}
|
||||
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
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
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
repo := alarmsRepo.NewAlarmsRepo(db)
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
if atomCfg.URL == "" {
|
||||
logger.Error("ATOM_URL is required")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
logger.Info("AuthN configured to use Atom bearer tokens")
|
||||
logger.Info("AuthZ configured to use Atom PDP")
|
||||
am := smqauthn.NewAuthNMiddleware(atomauthn.NewAuthentication())
|
||||
|
||||
idp := uuid.New()
|
||||
|
||||
svc := alarms.NewService(idp, repo)
|
||||
|
||||
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.NewAtomAuthorizationMiddleware(svc, atom.NewClient(atomCfg), 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)
|
||||
svc = middleware.NewTracingMiddleware(tracer, svc)
|
||||
|
||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpAPI.MakeHandler(svc, logger, idp, cfg.InstanceID, am), logger)
|
||||
|
||||
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer pubSub.Close()
|
||||
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
|
||||
|
||||
consumer := consumer.NewHandler(svc, logger)
|
||||
|
||||
subCfg := messaging.SubscriberConfig{
|
||||
ID: svcName,
|
||||
Topic: brokers.AllTopic,
|
||||
DeliveryPolicy: messaging.DeliverAllPolicy,
|
||||
Handler: consumer,
|
||||
}
|
||||
if err := pubSub.Subscribe(ctx, subCfg); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to subscribe to message broker: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return hs.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("billing service terminated: %s", err))
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+1
-1
@@ -26,9 +26,9 @@ import (
|
||||
apostgres "github.com/absmach/magistrala/auth/postgres"
|
||||
"github.com/absmach/magistrala/auth/tokenizer/asymmetric"
|
||||
"github.com/absmach/magistrala/auth/tokenizer/symmetric"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
redisclient "github.com/absmach/magistrala/internal/clients/redis"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
"github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@ import (
|
||||
"github.com/absmach/magistrala/certs/middleware"
|
||||
"github.com/absmach/magistrala/certs/pki"
|
||||
"github.com/absmach/magistrala/certs/postgres"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
smqauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
|
||||
+1
-1
@@ -22,8 +22,8 @@ import (
|
||||
"github.com/absmach/fluxmq/pkg/proto/auth/v1/authv1connect"
|
||||
fluxmqgrpc "github.com/absmach/magistrala/fluxmq/api/grpc"
|
||||
fluxmqhttp "github.com/absmach/magistrala/fluxmq/api/http"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains journal main function to start the journal service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
httpapi "github.com/absmach/magistrala/journal/api"
|
||||
"github.com/absmach/magistrala/journal/events"
|
||||
"github.com/absmach/magistrala/journal/middleware"
|
||||
journalpg "github.com/absmach/magistrala/journal/postgres"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
smqauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
"github.com/absmach/magistrala/pkg/events/store"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/postgres"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
"github.com/absmach/magistrala/pkg/server/http"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "journal"
|
||||
envPrefixDB = "MG_JOURNAL_DB_"
|
||||
envPrefixHTTP = "MG_JOURNAL_HTTP_"
|
||||
defDB = "journal"
|
||||
defSvcHTTPPort = "9021"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_JOURNAL_LOG_LEVEL" envDefault:"info"`
|
||||
ESURL string `env:"MG_ES_URL" envDefault:"amqp://guest:guest@localhost:5682/"`
|
||||
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
|
||||
InstanceID string `env:"MG_JOURNAL_INSTANCE_ID" envDefault:""`
|
||||
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err)
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer mglog.ExitWithError(&exitCode)
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate instanceID: %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())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
db, err := pgclient.Setup(dbConfig, *journalpg.Migration())
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
if atomCfg.URL == "" {
|
||||
logger.Error("ATOM_URL is required")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
atomClient := atom.NewClient(atomCfg)
|
||||
authn := atomauthn.NewAuthentication()
|
||||
authnMiddleware := smqauthn.NewAuthNMiddleware(authn)
|
||||
authz := atom.NewAuthorizationCompat(atomClient)
|
||||
logger.Info("AuthN/AuthZ configured to use Atom")
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("error shutting down tracer provider: %s", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
svc := newService(db, dbConfig, authz, logger, tracer)
|
||||
|
||||
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, "journal-es-sub", logger)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create subscriber: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Subscribed to Event Store")
|
||||
|
||||
if err := events.Start(ctx, svcName, subscriber, svc); err != nil {
|
||||
logger.Error("failed to start %s service: %s", svcName, err)
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error()))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
hs := http.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authnMiddleware, logger, svcName, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
|
||||
go chc.CallHome(ctx)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return hs.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db *sqlx.DB, dbConfig pgclient.Config, authz smqauthz.Authorization, logger *slog.Logger, tracer trace.Tracer) journal.Service {
|
||||
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
repo := journalpg.NewRepository(database)
|
||||
idp := uuid.New()
|
||||
|
||||
svc := journal.NewService(idp, repo)
|
||||
svc = middleware.NewAuthorization(svc, authz)
|
||||
svc = middleware.NewLogging(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics("journal", "journal_writer")
|
||||
svc = middleware.NewMetrics(svc, counter, latency)
|
||||
svc = middleware.NewTracing(svc, tracer)
|
||||
|
||||
return svc
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains notifications main function to start the notifications service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/notifications/emailer"
|
||||
"github.com/absmach/magistrala/notifications/events"
|
||||
"github.com/absmach/magistrala/notifications/middleware"
|
||||
"github.com/absmach/magistrala/pkg/events/store"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "notifications"
|
||||
defEmailPort = "25"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_NOTIFICATIONS_LOG_LEVEL" envDefault:"info"`
|
||||
ESURL string `env:"MG_ES_URL" envDefault:"amqp://guest:guest@localhost:5682/"`
|
||||
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
|
||||
InstanceID string `env:"MG_NOTIFICATIONS_INSTANCE_ID" envDefault:""`
|
||||
DomainAltName string `env:"MG_NOTIFICATIONS_DOMAIN_ALT_NAME" envDefault:"domain"`
|
||||
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
EmailHost string `env:"MG_EMAIL_HOST" envDefault:"localhost"`
|
||||
EmailPort string `env:"MG_EMAIL_PORT" envDefault:"25"`
|
||||
EmailUsername string `env:"MG_EMAIL_USERNAME" envDefault:""`
|
||||
EmailPassword string `env:"MG_EMAIL_PASSWORD" envDefault:""`
|
||||
EmailFromAddress string `env:"MG_EMAIL_FROM_ADDRESS" envDefault:"noreply@magistrala.com"`
|
||||
EmailFromName string `env:"MG_EMAIL_FROM_NAME" envDefault:"Magistrala Notifications"`
|
||||
InvitationTemplate string `env:"MG_EMAIL_INVITATION_TEMPLATE" envDefault:"docker/templates/invitation-sent-email.tmpl"`
|
||||
AcceptanceTemplate string `env:"MG_EMAIL_ACCEPTANCE_TEMPLATE" envDefault:"docker/templates/invitation-accepted-email.tmpl"`
|
||||
RejectionTemplate string `env:"MG_EMAIL_REJECTION_TEMPLATE" envDefault:"docker/templates/invitation-rejected-email.tmpl"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err)
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer mglog.ExitWithError(&exitCode)
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("error shutting down tracer provider: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
if atomCfg.URL == "" {
|
||||
logger.Error("ATOM_URL is required")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
usersResolver := emailer.NewAtomUserResolver(atom.NewClient(atomCfg))
|
||||
logger.Info("Notifications user lookup configured to use Atom")
|
||||
|
||||
emailerCfg := emailer.Config{
|
||||
FromAddress: cfg.EmailFromAddress,
|
||||
FromName: cfg.EmailFromName,
|
||||
DomainAltName: cfg.DomainAltName,
|
||||
InvitationTemplate: cfg.InvitationTemplate,
|
||||
AcceptanceTemplate: cfg.AcceptanceTemplate,
|
||||
RejectionTemplate: cfg.RejectionTemplate,
|
||||
EmailHost: cfg.EmailHost,
|
||||
EmailPort: cfg.EmailPort,
|
||||
EmailUsername: cfg.EmailUsername,
|
||||
EmailPassword: cfg.EmailPassword,
|
||||
}
|
||||
|
||||
notifier, err := emailer.New(usersResolver, emailerCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create emailer: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap notifier with middleware
|
||||
notifier = middleware.NewLogging(notifier, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "notifier")
|
||||
notifier = middleware.NewMetrics(notifier, counter, latency)
|
||||
notifier = middleware.NewTracing(notifier, tp.Tracer(svcName))
|
||||
|
||||
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, "notifications-es-sub", logger)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create subscriber: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Subscribed to Event Store")
|
||||
|
||||
if err := events.Start(ctx, svcName, subscriber, notifier); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to start %s service: %s", svcName, err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
|
||||
go chc.CallHome(ctx)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
|
||||
@@ -17,11 +17,11 @@ import (
|
||||
"github.com/absmach/magistrala/consumers"
|
||||
consumertracing "github.com/absmach/magistrala/consumers/tracing"
|
||||
httpapi "github.com/absmach/magistrala/consumers/writers/api"
|
||||
"github.com/absmach/magistrala/consumers/writers/brokers"
|
||||
writerpg "github.com/absmach/magistrala/consumers/writers/postgres"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
|
||||
brokers "github.com/absmach/magistrala/pkg/messaging/writers"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains rule engine main function to start the service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
abrokers "github.com/absmach/magistrala/alarms/brokers"
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/consumers/writers/brokers"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
"github.com/absmach/magistrala/pkg/callout"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
"github.com/absmach/magistrala/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
smqbrokers "github.com/absmach/magistrala/pkg/messaging/brokers"
|
||||
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"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/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "rules_engine"
|
||||
envPrefixDB = "MG_RE_DB_"
|
||||
envPrefixHTTP = "MG_RE_HTTP_"
|
||||
envPrefixCallout = "MG_RE_CALLOUT_"
|
||||
defDB = "r"
|
||||
defSvcHTTPPort = "9008"
|
||||
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
|
||||
)
|
||||
|
||||
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
|
||||
// A larger buffer size would also work, but we’d likely need another instance of RE in that case.
|
||||
// A smaller size would probably work too, but there's no need to be that frugal with resources.
|
||||
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:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"MG_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:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
PermissionsFile string `env:"MG_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
var logger *slog.Logger
|
||||
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err.Error())
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer mglog.ExitWithError(&exitCode)
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ec := email.Config{}
|
||||
if err := env.Parse(&ec); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load email configuration : %s", err))
|
||||
exitCode = 1
|
||||
|
||||
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())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
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
|
||||
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
callout, err := callout.New(callCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
msgSub, err := smqbrokers.NewPubSub(ctx, cfg.BrokerURL, logger, smqbrokers.ConnectionName("re-msg-pubsub"))
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to connect to message broker for mg pubSub: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer msgSub.Close()
|
||||
msgSub = brokerstracing.NewPubSub(httpServerConfig, tracer, msgSub)
|
||||
|
||||
writersPub, err := brokers.NewPublisher(ctx, cfg.BrokerURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to connect to message broker for writers publisher: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer writersPub.Close()
|
||||
writersPub = brokerstracing.NewPublisher(httpServerConfig, tracer, writersPub)
|
||||
|
||||
alarmsPub, err := abrokers.NewPublisher(ctx, cfg.BrokerURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to connect to message broker for alarms publisher: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer alarmsPub.Close()
|
||||
alarmsPub = brokerstracing.NewPublisher(httpServerConfig, tracer, alarmsPub)
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
if atomCfg.URL == "" {
|
||||
logger.Error("ATOM_URL is required")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
authnSvc := atomauthn.NewAuthentication()
|
||||
logger.Info("AuthN configured to use Atom bearer tokens")
|
||||
am := smqauthn.NewAuthNMiddleware(authnSvc)
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
logger.Info("AuthZ configured to use Atom PDP")
|
||||
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
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))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
client, err := grpcclient.NewHandler(regrpcCfg)
|
||||
if err != nil {
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
|
||||
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
|
||||
|
||||
svc, err := newService(ctx, cfg, database, runInfo, msgSub, writersPub, alarmsPub, ec, logger, readersClient, callout, tracer)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
subCfg := messaging.SubscriberConfig{
|
||||
ID: svcName,
|
||||
Topic: smqbrokers.SubjectAllMessages,
|
||||
DeliveryPolicy: messaging.DeliverAllPolicy,
|
||||
Handler: svc,
|
||||
}
|
||||
if err := msgSub.Subscribe(ctx, subCfg); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to subscribe to internal message broker: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for info := range runInfo {
|
||||
logger.LogAttrs(context.Background(), info.Level, info.Message, info.Details...)
|
||||
}
|
||||
}()
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, mux, logger, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
|
||||
go chc.CallHome(ctx)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return svc.StartScheduler(ctx)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return httpSvc.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvc)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, 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()
|
||||
|
||||
emailerClient, err := emailer.New(&ec)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
|
||||
var csvc re.Service
|
||||
csvc, err = re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create RE service: %w", err)
|
||||
}
|
||||
csvc = re.WithAtom(csvc, atom.NewClient(atomCfg))
|
||||
|
||||
csvc, err = events.NewEventStoreMiddleware(ctx, csvc, cfg.ESURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init re event store middleware: %w", err)
|
||||
}
|
||||
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
|
||||
}
|
||||
|
||||
ruleOps, _, 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)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AtomAuthorizationMiddleware(csvc, atom.NewClient(atomCfg), entitiesOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps)
|
||||
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
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains reports main function to start the service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
"github.com/absmach/magistrala/pkg/callout"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
"github.com/absmach/magistrala/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/permissions"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
httpapi "github.com/absmach/magistrala/reports/api"
|
||||
reportsevents "github.com/absmach/magistrala/reports/events"
|
||||
"github.com/absmach/magistrala/reports/middleware"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
repg "github.com/absmach/magistrala/reports/postgres"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "reports"
|
||||
envPrefixDB = "MG_REPORTS_DB_"
|
||||
envPrefixHTTP = "MG_REPORTS_HTTP_"
|
||||
envPrefixCallout = "MG_REPORTS_CALLOUT_"
|
||||
defDB = "repo"
|
||||
defSvcHTTPPort = "9017"
|
||||
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
|
||||
templatePath = "template/reports_default_template.html"
|
||||
reportEntity = "report"
|
||||
)
|
||||
|
||||
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
|
||||
const channBuffer = 256
|
||||
|
||||
//go:embed template/reports_default_template.html
|
||||
var templateFS embed.FS
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_REPORTS_LOG_LEVEL" envDefault:"info"`
|
||||
InstanceID string `env:"MG_REPORTS_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
ESConsumerName string `env:"MG_REPORTS_EVENT_CONSUMER" envDefault:"reports"`
|
||||
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"MG_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"`
|
||||
PermissionsFile string `env:"MG_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
var logger *slog.Logger
|
||||
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err.Error())
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer mglog.ExitWithError(&exitCode)
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var templateData []byte
|
||||
|
||||
switch cfg.DefaultTemplatePath {
|
||||
case "":
|
||||
templateData, err = templateFS.ReadFile(templatePath)
|
||||
default:
|
||||
templateData, err = os.ReadFile(templatePath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to read report template: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
template := reports.ReportTemplate(string(templateData))
|
||||
|
||||
if err := template.Validate(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to validate report template: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
logger.Info("Report template validated successfully")
|
||||
|
||||
ec := email.Config{}
|
||||
if err := env.Parse(&ec); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load email configuration : %s", err))
|
||||
exitCode = 1
|
||||
|
||||
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())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
callout, err := callout.New(callCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
if atomCfg.URL == "" {
|
||||
logger.Error("ATOM_URL is required")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
authnSvc := atomauthn.NewAuthentication()
|
||||
logger.Info("AuthN configured to use Atom bearer tokens")
|
||||
am := smqauthn.NewAuthNMiddleware(authnSvc)
|
||||
|
||||
logger.Info("AuthZ configured to use Atom PDP")
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
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))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
client, err := grpcclient.NewHandler(regrpcCfg)
|
||||
if err != nil {
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
|
||||
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
|
||||
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
svc, err := newService(ctx, cfg, database, runInfo, ec, logger, readersClient, template, callout, tracer)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for info := range runInfo {
|
||||
logger.LogAttrs(context.Background(), info.Level, info.Message, info.Details...)
|
||||
}
|
||||
}()
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, mux, logger, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
|
||||
go chc.CallHome(ctx)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return svc.StartScheduler(ctx)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return httpSvc.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvc)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, 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()
|
||||
|
||||
emailClient, err := emailer.New(&ec)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
atomCfg := atom.LoadConfig()
|
||||
|
||||
var csvc reports.Service
|
||||
csvc, err = reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailClient, readersClient, template, cfg.ConverterURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create reports service: %w", err)
|
||||
}
|
||||
csvc = reports.WithAtom(csvc, atom.NewClient(atomCfg))
|
||||
|
||||
csvc, err = reportsevents.NewEventStoreMiddleware(ctx, csvc, cfg.ESURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init reports event store middleware: %w", err)
|
||||
}
|
||||
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
|
||||
}
|
||||
|
||||
reportOps, _, 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)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AtomAuthorizationMiddleware(csvc, atom.NewClient(atomCfg), entitiesOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps)
|
||||
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
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
<!-- Copyright (c) Abstract Machines -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Title}}</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(41, 128, 185);
|
||||
--secondary-color: rgb(26, 82, 118);
|
||||
--subtle-color: rgb(189, 195, 199);
|
||||
--table-header-bg: rgb(236, 240, 241);
|
||||
--alternate-row: rgb(245, 247, 249);
|
||||
--text-primary: rgb(44, 62, 80);
|
||||
--text-secondary: rgb(127, 140, 141);
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: var(--white);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
padding: 15mm 10mm;
|
||||
margin: 0 auto;
|
||||
background: var(--white);
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
page-break-after: always;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page:last-child {
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 8mm;
|
||||
}
|
||||
|
||||
.header-top-bar {
|
||||
height: 8px;
|
||||
background-color: var(--primary-color);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header-date {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
text-align: right;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.header-separator {
|
||||
height: 2px;
|
||||
background-color: var(--subtle-color);
|
||||
margin: 5px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-separator::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: var(--subtle-color);
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.metrics-section {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.metrics-section.continuation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.metrics-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: var(--secondary-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metrics-info {
|
||||
background-color: var(--alternate-row);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: flex;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.metric-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-weight: bold;
|
||||
color: var(--text-primary);
|
||||
width: 120px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-style: italic;
|
||||
color: var(--text-primary);
|
||||
font-size: 11px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.record-count {
|
||||
text-align: right;
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table-header-bar {
|
||||
height: 4px;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--secondary-color);
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid var(--subtle-color);
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 6px 8px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.data-table tr:nth-child(even) {
|
||||
background-color: var(--alternate-row);
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background-color: rgba(41, 128, 185, 0.05);
|
||||
}
|
||||
|
||||
.col-time {
|
||||
width: 25%;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.col-value {
|
||||
width: 17%;
|
||||
color: var(--text-primary);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.col-unit {
|
||||
width: 17%;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.col-protocol {
|
||||
width: 17%;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.col-subtopic {
|
||||
width: 24%;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
border-top: 2px solid var(--subtle-color);
|
||||
padding-top: 6px;
|
||||
margin-top: 8mm;
|
||||
}
|
||||
|
||||
.footer-separator {
|
||||
height: 1px;
|
||||
background-color: var(--subtle-color);
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer-separator::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: var(--subtle-color);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-generated {
|
||||
font-size: 8px;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.footer-page {
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{if gt (len .Reports) 0}}
|
||||
{{$firstPageRows := 24}}
|
||||
{{$continuationPageRows := 32}}
|
||||
{{$totalPages := 0}}
|
||||
|
||||
{{/* Calculate total pages across all reports */}}
|
||||
{{range $report := .Reports}}
|
||||
{{$totalMessages := len .Messages}}
|
||||
{{$reportPages := 1}}
|
||||
{{if gt $totalMessages $firstPageRows}}
|
||||
{{$remaining := sub $totalMessages $firstPageRows}}
|
||||
{{$additionalPages := div $remaining $continuationPageRows}}
|
||||
{{if gt (mod $remaining $continuationPageRows) 0}}
|
||||
{{$additionalPages = add $additionalPages 1}}
|
||||
{{end}}
|
||||
{{$reportPages = add 1 $additionalPages}}
|
||||
{{end}}
|
||||
{{$totalPages = add $totalPages $reportPages}}
|
||||
{{end}}
|
||||
|
||||
{{$globalPage := 0}}
|
||||
|
||||
{{range $reportIndex, $report := .Reports}}
|
||||
{{$totalMessages := len .Messages}}
|
||||
{{$pageCount := 1}}
|
||||
{{if gt $totalMessages $firstPageRows}}
|
||||
{{$remaining := sub $totalMessages $firstPageRows}}
|
||||
{{$additionalPages := div $remaining $continuationPageRows}}
|
||||
{{if gt (mod $remaining $continuationPageRows) 0}}
|
||||
{{$additionalPages = add $additionalPages 1}}
|
||||
{{end}}
|
||||
{{$pageCount = add 1 $additionalPages}}
|
||||
{{end}}
|
||||
|
||||
{{range $pageNum := iterate $pageCount}}
|
||||
{{$globalPage = add $globalPage 1}}
|
||||
{{$isFirstPage := eq $pageNum 0}}
|
||||
{{$startRow := getStartRow $pageNum $firstPageRows $continuationPageRows}}
|
||||
{{$endRow := getEndRow $pageNum $firstPageRows $continuationPageRows $totalMessages}}
|
||||
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<div class="header-top-bar"></div>
|
||||
<div class="header-content">
|
||||
<div style="width: 100px;"></div>
|
||||
<div class="header-title">{{$.Title}}</div>
|
||||
<div class="header-date">{{$.GeneratedDate}}{{if $.Timezone}} ({{$.Timezone}}){{end}}</div>
|
||||
</div>
|
||||
<div class="header-separator"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
{{if $isFirstPage}}
|
||||
<div class="metrics-section">
|
||||
<div class="metrics-title">Metrics</div>
|
||||
<div class="metrics-info">
|
||||
<div class="metric-row">
|
||||
<div class="metric-label">Name:</div>
|
||||
<div class="metric-value">{{$report.Metric.Name}}</div>
|
||||
</div>
|
||||
{{if $report.Metric.ClientID}}
|
||||
<div class="metric-row">
|
||||
<div class="metric-label">Device ID:</div>
|
||||
<div class="metric-value">{{$report.Metric.ClientID}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="metric-row">
|
||||
<div class="metric-label">Channel ID:</div>
|
||||
<div class="metric-value">{{$report.Metric.ChannelID}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="record-count">
|
||||
Total Records: {{$totalMessages}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="metrics-section continuation">
|
||||
<div class="metrics-title">Metrics (continued)</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-header-bar"></div>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-time">Time</th>
|
||||
<th class="col-value">Value</th>
|
||||
<th class="col-unit">Unit</th>
|
||||
<th class="col-protocol">Protocol</th>
|
||||
<th class="col-subtopic">Subtopic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $msgIndex, $msg := $report.Messages}}
|
||||
{{if and (ge $msgIndex $startRow) (lt $msgIndex $endRow)}}
|
||||
<tr>
|
||||
<td class="col-time">{{formatTime $msg.Time}}</td>
|
||||
<td class="col-value">{{formatValue $msg}}</td>
|
||||
<td class="col-unit">{{$msg.Unit}}</td>
|
||||
<td class="col-protocol">{{$msg.Protocol}}</td>
|
||||
<td class="col-subtopic">{{$msg.Subtopic}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-separator"></div>
|
||||
<div class="footer-content">
|
||||
<div class="footer-generated">Generated: {{$.GeneratedTime}}{{if $.Timezone}} ({{$.Timezone}}){{end}}</div>
|
||||
<div class="footer-page">Page {{$globalPage}} of {{$totalPages}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<div class="header-top-bar"></div>
|
||||
<div class="header-content">
|
||||
<div style="width: 100px;"></div>
|
||||
<div class="header-title">{{.Title}}</div>
|
||||
<div class="header-date">{{.GeneratedDate}}{{if .Timezone}} ({{.Timezone}}){{end}}</div>
|
||||
</div>
|
||||
<div class="header-separator"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<div class="metrics-section">
|
||||
<div class="metrics-title">Metrics</div>
|
||||
<div class="metrics-info">
|
||||
<div class="metric-row">
|
||||
<div class="metric-label">Name:</div>
|
||||
<div class="metric-value">No Report</div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-label">Channel ID:</div>
|
||||
<div class="metric-value">N/A</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="record-count">
|
||||
Total Records: 0
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-header-bar"></div>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-time">Time</th>
|
||||
<th class="col-value">Value</th>
|
||||
<th class="col-unit">Unit</th>
|
||||
<th class="col-protocol">Protocol</th>
|
||||
<th class="col-subtopic">Subtopic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; font-style: italic; color: #888;">No data available</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-separator"></div>
|
||||
<div class="footer-content">
|
||||
<div class="footer-generated">Generated: {{.GeneratedTime}}{{if .Timezone}} ({{.Timezone}}){{end}}</div>
|
||||
<div class="footer-page">Page 1 of 1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
"github.com/absmach/magistrala"
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
atomauthn "github.com/absmach/magistrala/pkg/authn/atom"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
|
||||
@@ -17,11 +17,11 @@ import (
|
||||
"github.com/absmach/magistrala/consumers"
|
||||
consumertracing "github.com/absmach/magistrala/consumers/tracing"
|
||||
httpapi "github.com/absmach/magistrala/consumers/writers/api"
|
||||
"github.com/absmach/magistrala/consumers/writers/brokers"
|
||||
"github.com/absmach/magistrala/consumers/writers/timescale"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
|
||||
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
|
||||
brokers "github.com/absmach/magistrala/pkg/messaging/writers"
|
||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/server"
|
||||
|
||||
@@ -17,6 +17,5 @@ user_token = ""
|
||||
groups_url = "http://localhost:9004"
|
||||
host_url = "http://localhost"
|
||||
http_adapter_url = "http://localhost:8008"
|
||||
journal_url = "http://localhost:9021"
|
||||
tls_verification = false
|
||||
users_url = "http://localhost:9002"
|
||||
|
||||
@@ -8,7 +8,7 @@ The service is configured using environment variables. Values shown are from [do
|
||||
|
||||
### SMTP notifier (email)
|
||||
|
||||
Used by `consumers/notifiers/smtp` via `internal/email`.
|
||||
Used by `consumers/notifiers/smtp` via `pkg/email`.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------- | ---------------------------------------------- | ------------------ |
|
||||
@@ -93,7 +93,7 @@ Defined in `consumers/notifiers/smpp/README.md`.
|
||||
- **Service layer**: `consumers/notifiers/service.go` handles authn, ID creation, and notification dispatch.
|
||||
- **Repository**: `consumers/notifiers/postgres` persists subscriptions and supports filtering.
|
||||
- **Notifier implementations**: `consumers/notifiers/smtp` (email) and `consumers/notifiers/smpp` (SMS).
|
||||
- **Email agent**: `internal/email` manages SMTP connectivity and template rendering.
|
||||
- **Email agent**: `pkg/email` manages SMTP connectivity and template rendering.
|
||||
|
||||
### Subscriptions table
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
"github.com/absmach/magistrala/consumers/notifiers"
|
||||
"github.com/absmach/magistrala/consumers/notifiers/api"
|
||||
"github.com/absmach/magistrala/consumers/notifiers/mocks"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
smqmocks "github.com/absmach/magistrala/consumers/mocks"
|
||||
"github.com/absmach/magistrala/consumers/notifiers"
|
||||
"github.com/absmach/magistrala/consumers/notifiers/mocks"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
"github.com/absmach/magistrala/pkg/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/absmach/magistrala/consumers"
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/email"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
)
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ time_fields = [
|
||||
]
|
||||
```
|
||||
|
||||
The topic filter uses slash-delimited MQTT-style syntax (`+`, `#`) in the config file for both backends. Writers do not expose broker mode, delivery policy, or consumer-group settings in this file. They always consume through the stream-backed broker adapter in `consumers/writers/brokers`:
|
||||
The topic filter uses slash-delimited MQTT-style syntax (`+`, `#`) in the config file for both backends. Writers do not expose broker mode, delivery policy, or consumer-group settings in this file. They always consume through the stream-backed broker adapter in `pkg/messaging/writers`:
|
||||
|
||||
- NATS builds use JetStream streams with durable consumers.
|
||||
- FluxMQ builds publish to and consume from the `writers` stream queue while preserving the same `writers/#` config syntax.
|
||||
@@ -129,7 +129,7 @@ The topic filter uses slash-delimited MQTT-style syntax (`+`, `#`) in the config
|
||||
|
||||
### Components
|
||||
|
||||
- **Message broker adapter**: `consumers/writers/brokers` (NATS JetStream or FluxMQ stream queues).
|
||||
- **Message broker adapter**: `pkg/messaging/writers` (NATS JetStream or FluxMQ stream queues).
|
||||
- **Writer services**: `consumers/writers/postgres` and `consumers/writers/timescale`.
|
||||
- **HTTP API**: `consumers/writers/api` exposes `/health` and `/metrics`.
|
||||
- **Migrations**: `consumers/writers/*/init.go` defines the schema and indexes.
|
||||
|
||||
-27
@@ -280,13 +280,6 @@ MG_EMAIL_PASSWORD=password
|
||||
MG_EMAIL_FROM_ADDRESS=from@example.com
|
||||
MG_EMAIL_FROM_NAME=Example
|
||||
MG_EMAIL_TEMPLATE=
|
||||
MG_EMAIL_INVITATION_TEMPLATE=invitation-sent-email.tmpl
|
||||
MG_EMAIL_ACCEPTANCE_TEMPLATE=invitation-accepted-email.tmpl
|
||||
MG_EMAIL_REJECTION_TEMPLATE=invitation-rejected-email.tmpl
|
||||
|
||||
### Notifications
|
||||
MG_NOTIFICATIONS_LOG_LEVEL=debug
|
||||
MG_NOTIFICATIONS_INSTANCE_ID=
|
||||
|
||||
### Google OAuth2
|
||||
MG_GOOGLE_CLIENT_ID=
|
||||
@@ -438,23 +431,6 @@ MG_TIMESCALE_SSL_CERT=
|
||||
MG_TIMESCALE_SSL_KEY=
|
||||
MG_TIMESCALE_SSL_ROOT_CERT=
|
||||
|
||||
### Journal
|
||||
MG_JOURNAL_LOG_LEVEL=info
|
||||
MG_JOURNAL_HTTP_HOST=journal
|
||||
MG_JOURNAL_HTTP_PORT=9021
|
||||
MG_JOURNAL_HTTP_SERVER_CERT=
|
||||
MG_JOURNAL_HTTP_SERVER_KEY=
|
||||
MG_JOURNAL_DB_HOST=journal-db
|
||||
MG_JOURNAL_DB_PORT=5432
|
||||
MG_JOURNAL_DB_USER=magistrala
|
||||
MG_JOURNAL_DB_PASS=magistrala
|
||||
MG_JOURNAL_DB_NAME=journal
|
||||
MG_JOURNAL_DB_SSL_MODE=disable
|
||||
MG_JOURNAL_DB_SSL_CERT=
|
||||
MG_JOURNAL_DB_SSL_KEY=
|
||||
MG_JOURNAL_DB_SSL_ROOT_CERT=
|
||||
MG_JOURNAL_INSTANCE_ID=
|
||||
|
||||
### GRAFANA and PROMETHEUS
|
||||
MG_PROMETHEUS_PORT=9090
|
||||
MG_GRAFANA_PORT=3001
|
||||
@@ -671,8 +647,6 @@ MG_UI_BACKEND_INSTANCE_ID=
|
||||
MG_UI_BACKEND_URL=http://ui-backend:9097
|
||||
MG_UI_VERIFICATION_TLS=false
|
||||
MG_UI_CONTENT_TYPE=application/senml+json
|
||||
# Set to yes to accept the EULA for the UI services. To view the EULA visit: https://github.com/absmach/eula
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes
|
||||
OTEL_SERVICE_NAME=ui-mg
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
|
||||
|
||||
@@ -711,7 +685,6 @@ MG_BOOTSTRAP_URL=http://bootstrap:9013
|
||||
MG_HTTP_ADAPTER_URL=http://nginx:80/http
|
||||
MG_PUBLISH_PROXY_URL=http://nginx:80
|
||||
MG_READER_URL=http://timescale-reader:9011
|
||||
MG_JOURNAL_URL=http://journal:9021
|
||||
|
||||
### UI Configuration
|
||||
MG_UI_TYPE=mg
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ For a trusted production certificate, set `staging=false`:
|
||||
make run_tls host=example.com email=admin@example.com staging=false
|
||||
```
|
||||
|
||||
The target updates `docker/.env`, starts the Compose stack with the fallback certificate, runs certbot, switches Nginx to the issued certificate, and recreates Nginx. It also sets `MG_UI_DOCKER_ACCEPT_EULA=yes` for the UI container and configures public UI URLs to `https://${MG_PUBLIC_HOST}`.
|
||||
The target updates `docker/.env`, starts the Compose stack with the fallback certificate, runs certbot, switches Nginx to the issued certificate, and recreates Nginx. It also configures public UI URLs to `https://${MG_PUBLIC_HOST}`.
|
||||
|
||||
To configure the same instance without Let's Encrypt, use:
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ volumes:
|
||||
|
||||
services:
|
||||
bootstrap-db:
|
||||
image: postgres:16.2-alpine
|
||||
image: postgres:16.14-alpine3.24
|
||||
container_name: magistrala-bootstrap-db
|
||||
restart: on-failure
|
||||
environment:
|
||||
|
||||
@@ -17,7 +17,7 @@ volumes:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16.2-alpine
|
||||
image: postgres:16.14-alpine3.24
|
||||
container_name: magistrala-postgres
|
||||
restart: on-failure
|
||||
environment:
|
||||
|
||||
@@ -18,7 +18,7 @@ volumes:
|
||||
|
||||
services:
|
||||
promethues:
|
||||
image: prom/prometheus:v2.49.1
|
||||
image: prom/prometheus:v3.13.1
|
||||
container_name: magistrala-prometheus
|
||||
restart: on-failure
|
||||
ports:
|
||||
@@ -32,7 +32,7 @@ services:
|
||||
- magistrala-prometheus-volume:/prometheus
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:10.2.3
|
||||
image: grafana/grafana:13.1.1
|
||||
container_name: magistrala-grafana
|
||||
depends_on:
|
||||
- promethues
|
||||
|
||||
+13
-108
@@ -14,7 +14,6 @@ networks:
|
||||
volumes:
|
||||
magistrala-pat-db-volume:
|
||||
magistrala-ui-backend-db-volume:
|
||||
magistrala-journal-volume:
|
||||
magistrala-re-db-volume:
|
||||
magistrala-alarms-db-volume:
|
||||
magistrala-reports-db-volume:
|
||||
@@ -128,65 +127,8 @@ services:
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
|
||||
journal-db:
|
||||
image: postgres:16.2-alpine
|
||||
container_name: magistrala-journal-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
POSTGRES_USER: ${MG_JOURNAL_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_JOURNAL_DB_PASS}
|
||||
POSTGRES_DB: ${MG_JOURNAL_DB_NAME}
|
||||
MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS}
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- magistrala-journal-volume:/var/lib/postgresql/data
|
||||
|
||||
journal:
|
||||
image: ghcr.io/absmach/magistrala/journal:${MG_RELEASE_TAG}
|
||||
container_name: magistrala-journal
|
||||
depends_on:
|
||||
journal-db:
|
||||
condition: service_started
|
||||
atom-bootstrap:
|
||||
condition: service_completed_successfully
|
||||
nginx:
|
||||
condition: service_started
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_JOURNAL_LOG_LEVEL: ${MG_JOURNAL_LOG_LEVEL}
|
||||
MG_JOURNAL_HTTP_HOST: ${MG_JOURNAL_HTTP_HOST}
|
||||
MG_JOURNAL_HTTP_PORT: ${MG_JOURNAL_HTTP_PORT}
|
||||
MG_JOURNAL_HTTP_SERVER_CERT: ${MG_JOURNAL_HTTP_SERVER_CERT}
|
||||
MG_JOURNAL_HTTP_SERVER_KEY: ${MG_JOURNAL_HTTP_SERVER_KEY}
|
||||
MG_JOURNAL_DB_HOST: ${MG_JOURNAL_DB_HOST}
|
||||
MG_JOURNAL_DB_PORT: ${MG_JOURNAL_DB_PORT}
|
||||
MG_JOURNAL_DB_USER: ${MG_JOURNAL_DB_USER}
|
||||
MG_JOURNAL_DB_PASS: ${MG_JOURNAL_DB_PASS}
|
||||
MG_JOURNAL_DB_NAME: ${MG_JOURNAL_DB_NAME}
|
||||
MG_JOURNAL_DB_SSL_MODE: ${MG_JOURNAL_DB_SSL_MODE}
|
||||
MG_JOURNAL_DB_SSL_CERT: ${MG_JOURNAL_DB_SSL_CERT}
|
||||
MG_JOURNAL_DB_SSL_KEY: ${MG_JOURNAL_DB_SSL_KEY}
|
||||
MG_JOURNAL_DB_SSL_ROOT_CERT: ${MG_JOURNAL_DB_SSL_ROOT_CERT}
|
||||
ATOM_URL: ${ATOM_URL}
|
||||
ATOM_SERVICE_TOKEN: ${MG_ATOM_TOKEN_JOURNAL}
|
||||
ATOM_JWKS_URL: ${ATOM_JWKS_URL}
|
||||
ATOM_JWT_ISSUER: ${ATOM_JWT_ISSUER}
|
||||
ATOM_JWT_AUDIENCE: ${ATOM_JWT_AUDIENCE}
|
||||
ATOM_TIMEOUT: ${ATOM_TIMEOUT}
|
||||
MG_ES_URL: ${MG_ES_URL}
|
||||
MG_JAEGER_URL: ${MG_JAEGER_URL}
|
||||
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
|
||||
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
|
||||
MG_JOURNAL_INSTANCE_ID: ${MG_JOURNAL_INSTANCE_ID}
|
||||
MG_ALLOW_UNVERIFIED_USER: ${MG_ALLOW_UNVERIFIED_USER}
|
||||
ports:
|
||||
- ${MG_JOURNAL_HTTP_PORT}:${MG_JOURNAL_HTTP_PORT}
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
nginx:
|
||||
image: docker.io/nginx:1.29.2-alpine3.22
|
||||
image: docker.io/nginx:1.31.3-alpine3.24
|
||||
container_name: magistrala-nginx
|
||||
restart: on-failure
|
||||
volumes:
|
||||
@@ -228,7 +170,7 @@ services:
|
||||
hard: 65536
|
||||
|
||||
certbot:
|
||||
image: docker.io/certbot/certbot:v2.11.0
|
||||
image: docker.io/certbot/certbot:v5.7.0
|
||||
container_name: magistrala-certbot
|
||||
profiles:
|
||||
- letsencrypt
|
||||
@@ -284,46 +226,8 @@ services:
|
||||
sleep 12h & wait $$!
|
||||
done
|
||||
|
||||
notifications:
|
||||
image: ghcr.io/absmach/magistrala/notifications:${MG_RELEASE_TAG}
|
||||
container_name: magistrala-notifications
|
||||
depends_on:
|
||||
atom-bootstrap:
|
||||
condition: service_completed_successfully
|
||||
nginx:
|
||||
condition: service_started
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_NOTIFICATIONS_LOG_LEVEL: ${MG_NOTIFICATIONS_LOG_LEVEL}
|
||||
MG_NOTIFICATIONS_INSTANCE_ID: ${MG_NOTIFICATIONS_INSTANCE_ID}
|
||||
MG_ES_URL: ${MG_ES_URL}
|
||||
MG_JAEGER_URL: ${MG_JAEGER_URL}
|
||||
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
|
||||
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
|
||||
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
|
||||
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
|
||||
MG_EMAIL_USERNAME: ${MG_EMAIL_USERNAME}
|
||||
MG_EMAIL_PASSWORD: ${MG_EMAIL_PASSWORD}
|
||||
MG_EMAIL_FROM_ADDRESS: ${MG_EMAIL_FROM_ADDRESS}
|
||||
MG_EMAIL_FROM_NAME: ${MG_EMAIL_FROM_NAME}
|
||||
MG_EMAIL_INVITATION_TEMPLATE: ${MG_EMAIL_INVITATION_TEMPLATE}
|
||||
MG_EMAIL_ACCEPTANCE_TEMPLATE: ${MG_EMAIL_ACCEPTANCE_TEMPLATE}
|
||||
MG_EMAIL_REJECTION_TEMPLATE: ${MG_EMAIL_REJECTION_TEMPLATE}
|
||||
ATOM_URL: ${ATOM_URL}
|
||||
ATOM_SERVICE_TOKEN: ${MG_ATOM_TOKEN_NOTIFICATIONS}
|
||||
ATOM_JWKS_URL: ${ATOM_JWKS_URL}
|
||||
ATOM_JWT_ISSUER: ${ATOM_JWT_ISSUER}
|
||||
ATOM_JWT_AUDIENCE: ${ATOM_JWT_AUDIENCE}
|
||||
ATOM_TIMEOUT: ${ATOM_TIMEOUT}
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ./templates/${MG_EMAIL_INVITATION_TEMPLATE}:/${MG_EMAIL_INVITATION_TEMPLATE}
|
||||
- ./templates/${MG_EMAIL_ACCEPTANCE_TEMPLATE}:/${MG_EMAIL_ACCEPTANCE_TEMPLATE}
|
||||
- ./templates/${MG_EMAIL_REJECTION_TEMPLATE}:/${MG_EMAIL_REJECTION_TEMPLATE}
|
||||
|
||||
jaeger:
|
||||
image: docker.io/jaegertracing/all-in-one:1.74.0
|
||||
image: docker.io/jaegertracing/all-in-one:1.76.0
|
||||
container_name: magistrala-jaeger
|
||||
environment:
|
||||
COLLECTOR_OTLP_ENABLED: ${MG_JAEGER_COLLECTOR_OTLP_ENABLED}
|
||||
@@ -449,7 +353,6 @@ services:
|
||||
MG_PUBLISH_PROXY_URL: ${MG_PUBLISH_PROXY_URL}
|
||||
MG_READER_URL: ${MG_READER_URL}
|
||||
MG_BACKEND_URL: ${MG_UI_BACKEND_URL}
|
||||
MG_JOURNAL_URL: ${MG_JOURNAL_URL}
|
||||
MG_ALARMS_URL: ${MG_ALARMS_URL}
|
||||
MG_RE_URL: ${MG_RE_URL}
|
||||
MG_REPORTS_URL: ${MG_REPORTS_URL}
|
||||
@@ -467,7 +370,6 @@ services:
|
||||
NEXT_LOG_LEVEL: "debug"
|
||||
MG_HOST_URL: ${MG_HOST_URL}
|
||||
MG_UI_IMAGE_URL: ${MG_UI_IMAGE_URL}
|
||||
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}
|
||||
@@ -521,7 +423,6 @@ services:
|
||||
MG_UI_VERIFICATION_TLS: ${MG_UI_VERIFICATION_TLS}
|
||||
MG_UI_CONTENT_TYPE: ${MG_UI_CONTENT_TYPE}
|
||||
MG_READER_URL: ${MG_READER_URL}
|
||||
MG_UI_DOCKER_ACCEPT_EULA: ${MG_UI_DOCKER_ACCEPT_EULA}
|
||||
MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL}
|
||||
MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT}
|
||||
MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt}
|
||||
@@ -601,11 +502,12 @@ services:
|
||||
create_host_path: true
|
||||
|
||||
ui-backend-db:
|
||||
image: docker.io/postgres:18.0-alpine3.22
|
||||
image: docker.io/postgres:18.4-alpine3.24
|
||||
container_name: magistrala-ui-backend-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
POSTGRES_USER: ${MG_UI_BACKEND_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_UI_BACKEND_DB_PASS}
|
||||
POSTGRES_DB: ${MG_UI_BACKEND_DB_NAME}
|
||||
@@ -623,7 +525,7 @@ services:
|
||||
retries: 60
|
||||
|
||||
seaweedfs-s3:
|
||||
image: chrislusf/seaweedfs:4.16
|
||||
image: chrislusf/seaweedfs:4.40
|
||||
container_name: magistrala-seaweedfs-s3
|
||||
command: server -s3 -s3.config=/etc/seaweedfs/s3.json -dir=/data
|
||||
ports:
|
||||
@@ -668,7 +570,7 @@ services:
|
||||
AWS_EC2_METADATA_DISABLED: "true"
|
||||
|
||||
timescale:
|
||||
image: timescale/timescaledb:2.19.3-pg16-oss
|
||||
image: timescale/timescaledb:2.29.0-pg16-oss
|
||||
container_name: magistrala-timescale
|
||||
restart: on-failure
|
||||
environment:
|
||||
@@ -796,11 +698,12 @@ services:
|
||||
volumes:
|
||||
- ./addons/timescale-writer/config.toml:${MG_TIMESCALE_WRITER_CONFIG_PATH}
|
||||
re-db:
|
||||
image: docker.io/postgres:18.0-alpine3.22
|
||||
image: docker.io/postgres:18.4-alpine3.24
|
||||
container_name: magistrala-re-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
POSTGRES_USER: ${MG_RE_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_RE_DB_PASS}
|
||||
POSTGRES_DB: ${MG_RE_DB_NAME}
|
||||
@@ -880,11 +783,12 @@ services:
|
||||
- ./templates/${MG_RE_EMAIL_TEMPLATE}:/email.tmpl
|
||||
|
||||
alarms-db:
|
||||
image: docker.io/postgres:18.0-alpine3.22
|
||||
image: docker.io/postgres:18.4-alpine3.24
|
||||
container_name: magistrala-alarms-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
POSTGRES_USER: ${MG_ALARMS_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_ALARMS_DB_PASS}
|
||||
POSTGRES_DB: ${MG_ALARMS_DB_NAME}
|
||||
@@ -943,11 +847,12 @@ services:
|
||||
- ./permission.yaml:${MG_PERMISSIONS_FILE}
|
||||
|
||||
reports-db:
|
||||
image: docker.io/postgres:18.0-alpine3.22
|
||||
image: docker.io/postgres:18.4-alpine3.24
|
||||
container_name: magistrala-reports-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
POSTGRES_USER: ${MG_REPORTS_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_REPORTS_DB_PASS}
|
||||
POSTGRES_DB: ${MG_REPORTS_DB_NAME}
|
||||
|
||||
@@ -127,15 +127,16 @@ queues:
|
||||
max_length_bytes: 1073741824
|
||||
|
||||
auth:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
external:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
|
||||
hooks:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
|
||||
@@ -124,15 +124,16 @@ queues:
|
||||
max_length_bytes: 1073741824
|
||||
|
||||
auth:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
external:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
|
||||
hooks:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
|
||||
@@ -124,15 +124,16 @@ queues:
|
||||
max_length_bytes: 1073741824
|
||||
|
||||
auth:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
external:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
transport: "grpc"
|
||||
timeout: 15s
|
||||
protocols:
|
||||
mqtt: true
|
||||
http: true
|
||||
coap: true
|
||||
amqp: true
|
||||
amqp091: false
|
||||
|
||||
hooks:
|
||||
url: "http://fluxmq-auth:7016"
|
||||
|
||||
+5
-6
@@ -213,7 +213,6 @@ set_env MG_LETSENCRYPT_FORCE_RENEWAL "$FORCE_RENEWAL"
|
||||
set_env MG_NGINX_SERVER_NAME "$HOST"
|
||||
comment_env_any MG_NGINX_SERVER_CERT
|
||||
comment_env_any MG_NGINX_SERVER_KEY
|
||||
set_env MG_UI_DOCKER_ACCEPT_EULA yes
|
||||
|
||||
set_env MG_OAUTH_UI_REDIRECT_URL "https://$HOST/api/auth/token"
|
||||
set_env MG_OAUTH_UI_ERROR_URL "https://$HOST/login"
|
||||
@@ -234,19 +233,19 @@ write_ui_proxy
|
||||
|
||||
if [ "$LETSENCRYPT_ENABLED" = "false" ]; then
|
||||
echo "Starting Magistrala with the fallback Nginx certificate"
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes compose up -d
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes COMPOSE_PROFILES=letsencrypt compose stop certbot >/dev/null 2>&1 || true
|
||||
compose up -d
|
||||
COMPOSE_PROFILES=letsencrypt compose stop certbot >/dev/null 2>&1 || true
|
||||
echo "Let's Encrypt disabled. Nginx cert/key paths are commented in docker/.env."
|
||||
echo "Fallback TLS setup complete: https://$HOST/"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Starting Magistrala with the fallback Nginx certificate"
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes compose up -d
|
||||
compose up -d
|
||||
wait_for_nginx_http
|
||||
|
||||
echo "Requesting Let's Encrypt certificate for $HOST"
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes COMPOSE_PROFILES=letsencrypt compose up -d --force-recreate certbot
|
||||
COMPOSE_PROFILES=letsencrypt compose up -d --force-recreate certbot
|
||||
|
||||
cert_ready() {
|
||||
compose logs certbot 2>&1 | \
|
||||
@@ -271,6 +270,6 @@ set_env MG_NGINX_SERVER_CERT "$cert_path"
|
||||
set_env MG_NGINX_SERVER_KEY "$key_path"
|
||||
set_env MG_LETSENCRYPT_FORCE_RENEWAL false
|
||||
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes compose up -d --force-recreate nginx
|
||||
compose up -d --force-recreate nginx
|
||||
|
||||
echo "TLS setup complete: https://$HOST/"
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{.Subject}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f7fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 30px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
|
||||
padding: 30px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
.content p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.success-details {
|
||||
background-color: #eafaf1;
|
||||
border-left: 4px solid #27ae60;
|
||||
padding: 20px;
|
||||
margin: 25px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.success-details p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.icon-success {
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
font-size: 48px;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fb;
|
||||
padding: 25px 40px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.footer-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #e0e3e8;
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Invitation Accepted</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="icon-success">✓</div>
|
||||
|
||||
<p>Dear <strong>{{.User}}</strong>,</p>
|
||||
|
||||
<div class="success-details">
|
||||
<p>{{.Content}}</p>
|
||||
</div>
|
||||
|
||||
<p>You can now collaborate with them in the domain.</p>
|
||||
|
||||
<hr class="footer-divider">
|
||||
|
||||
<p>Best regards,<br><strong>{{.Footer}}</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>This is an automated email. Please do not reply directly to this message.</p>
|
||||
<p style="margin-top: 10px; color: #999;">Powered by Magistrala</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,93 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{.Subject}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f7fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 30px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
||||
padding: 30px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
.content p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.rejection-details {
|
||||
background-color: #fadbd8;
|
||||
border-left: 4px solid #e74c3c;
|
||||
padding: 20px;
|
||||
margin: 25px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.rejection-details p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fb;
|
||||
padding: 25px 40px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.footer-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #e0e3e8;
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Invitation Declined</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Dear <strong>{{.User}}</strong>,</p>
|
||||
|
||||
<div class="rejection-details">
|
||||
<p>{{.Content}}</p>
|
||||
</div>
|
||||
|
||||
<p>If you would like to extend another invitation or have any questions, please don't hesitate to contact us.</p>
|
||||
|
||||
<hr class="footer-divider">
|
||||
|
||||
<p>Best regards,<br><strong>{{.Footer}}</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>This is an automated email. Please do not reply directly to this message.</p>
|
||||
<p style="margin-top: 10px; color: #999;">Powered by Magistrala</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,106 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{.Subject}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f7fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 30px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #083662 0%, #0a4d8f 100%);
|
||||
padding: 30px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
.content p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.invitation-details {
|
||||
background-color: #f8f9fb;
|
||||
border-left: 4px solid #083662;
|
||||
padding: 20px;
|
||||
margin: 25px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.invitation-details p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
background-color: #083662;
|
||||
color: #ffffff;
|
||||
padding: 14px 32px;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fb;
|
||||
padding: 25px 40px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.footer-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #e0e3e8;
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Domain Invitation</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Dear <strong>{{.User}}</strong>,</p>
|
||||
|
||||
<div class="invitation-details">
|
||||
<p>{{.Content}}</p>
|
||||
</div>
|
||||
|
||||
<p>Please log in to your account to view and respond to this invitation.</p>
|
||||
|
||||
<p>If you have any questions, please don't hesitate to contact us.</p>
|
||||
|
||||
<hr class="footer-divider">
|
||||
|
||||
<p>Best regards,<br><strong>{{.Footer}}</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>This is an automated email. Please do not reply directly to this message.</p>
|
||||
<p style="margin-top: 10px; color: #999;">Powered by Magistrala</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
grpcChannelsV1 "github.com/absmach/magistrala/api/grpc/channels/v1"
|
||||
grpcClientsV1 "github.com/absmach/magistrala/api/grpc/clients/v1"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/connections"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
@@ -56,6 +56,13 @@ func (s *connectServer) Authenticate(ctx context.Context, req *connect.Request[a
|
||||
username := req.Msg.GetUsername()
|
||||
password := req.Msg.GetPassword()
|
||||
|
||||
// Reject empty-username clients early to avoid hitting Atom's rate limiter
|
||||
// for an empty identifier, which would trigger the FluxMQ circuit breaker
|
||||
// and block all subsequent MQTT connections.
|
||||
if username == "" || password == "" {
|
||||
return connect.NewResponse(&authv1.AuthnRes{Authenticated: false}), nil
|
||||
}
|
||||
|
||||
token := authn.AuthPack(authn.BasicAuth, username, password)
|
||||
res, err := s.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{Token: token})
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"connectrpc.com/connect"
|
||||
authv1 "github.com/absmach/fluxmq/pkg/proto/auth/v1"
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/internal/atom"
|
||||
"github.com/absmach/magistrala/pkg/atom"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/messaging"
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
@@ -24,29 +24,25 @@ require (
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f
|
||||
github.com/ivanpirog/coloredcobra v1.0.1
|
||||
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
|
||||
github.com/jackc/pgtype v1.14.4
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.7
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nats-io/nats.go v1.52.0
|
||||
github.com/oklog/ulid/v2 v2.1.1
|
||||
github.com/oklog/ulid/v2 v2.1.2
|
||||
github.com/openbao/openbao/api/v2 v2.6.0
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/plgd-dev/go-coap/v3 v3.5.4
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_golang v1.24.1
|
||||
github.com/rabbitmq/amqp091-go v1.13.0
|
||||
github.com/redis/go-redis/v9 v9.21.0
|
||||
github.com/rubenv/sql-migrate v1.8.1
|
||||
github.com/slack-go/slack v0.27.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/sqids/sqids-go v0.4.1
|
||||
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.2
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
@@ -65,15 +61,8 @@ require (
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/smarty/assertions v1.16.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // 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
|
||||
@@ -115,11 +104,10 @@ require (
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 // 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/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
@@ -145,13 +133,14 @@ require (
|
||||
github.com/pion/transport/v4 v4.0.2 // 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.68.1 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.12.0
|
||||
github.com/prometheus/common v0.70.1 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/smarty/assertions v1.16.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
@@ -172,6 +161,6 @@ require (
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
|
||||
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
||||
connectrpc.com/otelconnect v0.9.0 h1:NggB3pzRC3pukQWaYbRHJulxuXvmCKCKkQ9hbrHAWoA=
|
||||
@@ -14,12 +12,10 @@ github.com/0x6flab/namegenerator v1.4.0/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gB
|
||||
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/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
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=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
|
||||
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=
|
||||
@@ -28,16 +24,6 @@ github.com/absmach/fluxmq v0.40.0 h1:J7s6PHXliWRfwpQpt/umRNnrUegj9xYsVyK9BV8Azhk
|
||||
github.com/absmach/fluxmq v0.40.0/go.mod h1:oVbq3VlkD0vPKc45gkcxXpD2tbweyF+CDw1x72AS+PA=
|
||||
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/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/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=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -48,32 +34,21 @@ github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdz
|
||||
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||
github.com/caarlos0/env/v11 v11.4.1 h1:fYwH0sWEsBSMPG7t4e/PEfTFzrWrpjyygXyUnWiSwEw=
|
||||
github.com/caarlos0/env/v11 v11.4.1/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/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=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
|
||||
github.com/containerd/continuity v0.5.0/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/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.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=
|
||||
@@ -96,12 +71,10 @@ 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/dsnet/golib/memfile v1.0.0 h1:J9pUspY2bDCbF9o+YGwcf3uG6MdyITfh/Fk3/CaEiFs=
|
||||
github.com/dsnet/golib/memfile v1.0.0/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
@@ -121,16 +94,10 @@ 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-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -138,42 +105,21 @@ 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-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.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
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.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -214,91 +160,26 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4=
|
||||
github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
|
||||
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
|
||||
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
|
||||
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
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=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
@@ -315,33 +196,19 @@ github.com/lestrrat-go/jwx/v2 v2.1.7 h1:bnYeET+S8IOyAw6W4LTc6SEeK7Xs58SKKZkR7scb
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.7/go.mod h1:exQ9ZBuN1cMLYmxwhTlHUru08ykONG0z+HbLEeDG9qo=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
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/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/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.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
|
||||
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
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=
|
||||
@@ -354,23 +221,16 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
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=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
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.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc=
|
||||
github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
|
||||
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
|
||||
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
|
||||
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/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=
|
||||
github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/openbao/openbao/api/v2 v2.6.0 h1:KvfspAaL9bab9hI8jFYkV2cgtSrwWtaG+k9AUTHWU4M=
|
||||
github.com/openbao/openbao/api/v2 v2.6.0/go.mod h1:H4IWiH+2rgF/TbrsUbsfrMyGoqojkLqxPCRLENSMnSo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -392,9 +252,6 @@ 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.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
||||
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/plgd-dev/go-coap/v3 v3.5.4 h1:KtSx2upgpeMzjdYXsEZDgUYui9l08HfoO6DldCI7Msc=
|
||||
github.com/plgd-dev/go-coap/v3 v3.5.4/go.mod h1:kgdxil4mi3Bi9s5av/NbQeVwRJ+8N6zGHFEPy7qTRWI=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -402,38 +259,20 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
||||
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
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.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY=
|
||||
github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
|
||||
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=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/rabbitmq/amqp091-go v1.12.0 h1:V0v14Iqfs+MwHWihJt/nGS5Ulu0vw572b2Co3mwunkI=
|
||||
github.com/rabbitmq/amqp091-go v1.12.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
|
||||
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/rabbitmq/amqp091-go v1.13.0 h1:L8NA1WtF76C6KA3LAoufjfLgbist/If1UQYcsOjtxXA=
|
||||
github.com/rabbitmq/amqp091-go v1.13.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
|
||||
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
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/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
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=
|
||||
@@ -442,20 +281,11 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
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/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=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/slack-go/slack v0.27.0 h1:VWOpUzOK6UAPCCQlFxl79jhv8a/b+GOSJMnWziDJ8B8=
|
||||
github.com/slack-go/slack v0.27.0/go.mod h1:UEe+jmo9WLlwHB04qsOrTDvqM7Aa4rQL3O5wF3n0hx4=
|
||||
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=
|
||||
@@ -476,32 +306,17 @@ github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjb
|
||||
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/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=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vadv/gopher-lua-libs v0.8.0 h1:u2GVTj32Wnmu8RpSxeAdlTf9mYZrrm9ALKGYmvnvvZQ=
|
||||
github.com/vadv/gopher-lua-libs v0.8.0/go.mod h1:iNYvPoNV6ur7xJj4Uj3hEVebv8Z0/MoeM1igsXQbv8g=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -513,15 +328,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 h1:noHsffKZsNfU38DwcXWEPldrTjIZ8FPNKx8mYMGnqjs=
|
||||
github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
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.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
|
||||
@@ -544,186 +352,59 @@ go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.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=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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/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=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad h1:3iLyITS/sySRwbUKoC7ogfj2Yr1Cjs0pfaRKj5U5HEw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
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=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM=
|
||||
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
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=
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
# Journal
|
||||
|
||||
The Journal service listens to the platform event stream, persists each event to PostgreSQL for auditability and exposes HTTP endpoints to query journals or view per-client telemetry (first/last seen, subscriptions, in/out message counters).
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured with the following environment variables (unset values fall back to defaults).
|
||||
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `MG_JOURNAL_LOG_LEVEL` | Log level for Journal (debug, info, warn, error) | info |
|
||||
| `MG_JOURNAL_HTTP_HOST` | Journal HTTP host | localhost |
|
||||
| `MG_JOURNAL_HTTP_PORT` | Journal HTTP port | 9021 |
|
||||
| `MG_JOURNAL_HTTP_SERVER_CERT` | Path to PEM-encoded HTTP server certificate | "" |
|
||||
| `MG_JOURNAL_HTTP_SERVER_KEY` | Path to PEM-encoded HTTP server key | "" |
|
||||
| `MG_JOURNAL_HTTP_SERVER_CA_CERTS` | Path to trusted CA bundle for the HTTP server | "" |
|
||||
| `MG_JOURNAL_HTTP_CLIENT_CA_CERTS` | Path to client CA bundle to require HTTP mTLS | "" |
|
||||
| `MG_JOURNAL_DB_HOST` | Database host address | localhost |
|
||||
| `MG_JOURNAL_DB_PORT` | Database host port | 5432 |
|
||||
| `MG_JOURNAL_DB_USER` | Database user | magistrala |
|
||||
| `MG_JOURNAL_DB_PASS` | Database password | magistrala |
|
||||
| `MG_JOURNAL_DB_NAME` | Name of the database used by the service | journal |
|
||||
| `MG_JOURNAL_DB_SSL_MODE` | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
|
||||
| `MG_JOURNAL_DB_SSL_CERT` | Path to the PEM-encoded certificate file | "" |
|
||||
| `MG_JOURNAL_DB_SSL_KEY` | Path to the PEM-encoded key file | "" |
|
||||
| `MG_JOURNAL_DB_SSL_ROOT_CERT` | Path to the PEM-encoded root certificate file | "" |
|
||||
| `MG_ES_URL` | Event store URL (NATS) consumed for journal entries | nats://localhost:4222 |
|
||||
| `MG_JAEGER_URL` | Jaeger tracing endpoint | <http://localhost:4318/v1/traces> |
|
||||
| `MG_JAEGER_TRACE_RATIO` | Trace sampling ratio | 1.0 |
|
||||
| `MG_SEND_TELEMETRY` | Send telemetry to the Magistrala call-home server | true |
|
||||
| `MG_AUTH_GRPC_URL` | Auth service gRPC URL | "" |
|
||||
| `MG_AUTH_GRPC_TIMEOUT` | Auth service gRPC timeout | 1s |
|
||||
| `MG_AUTH_GRPC_CLIENT_CERT` | Path to PEM-encoded Auth gRPC client certificate | "" |
|
||||
| `MG_AUTH_GRPC_CLIENT_KEY` | Path to PEM-encoded Auth gRPC client key | "" |
|
||||
| `MG_AUTH_GRPC_SERVER_CA_CERTS` | Path to PEM-encoded Auth gRPC trusted CA bundle | "" |
|
||||
| `MG_DOMAINS_GRPC_URL` | Domains service gRPC URL | "" |
|
||||
| `MG_DOMAINS_GRPC_TIMEOUT` | Domains service gRPC timeout | 1s |
|
||||
| `MG_DOMAINS_GRPC_CLIENT_CERT` | Path to PEM-encoded Domains gRPC client certificate | "" |
|
||||
| `MG_DOMAINS_GRPC_CLIENT_KEY` | Path to PEM-encoded Domains gRPC client key | "" |
|
||||
| `MG_DOMAINS_GRPC_SERVER_CA_CERTS` | Path to PEM-encoded Domains gRPC trusted CA bundle | "" |
|
||||
| `MG_JOURNAL_INSTANCE_ID` | Journal instance ID (auto-generated when empty) | "" |
|
||||
| `MG_ALLOW_UNVERIFIED_USER` | Allow unverified users to authenticate (useful in dev) | false |
|
||||
|
||||
## Deployment
|
||||
|
||||
The service is distributed as a Docker container. Check [`docker/docker-compose.yaml`](https://github.com/absmach/magistrala/tree/main/docker/docker-compose.yaml) for the `journal` and `journal-db` services and how they are wired into the base stack.
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/absmach/magistrala
|
||||
cd magistrala
|
||||
|
||||
# build and install the binary
|
||||
make journal
|
||||
make install
|
||||
|
||||
# run with the essentials; requires Postgres, Auth gRPC, Domains gRPC, and NATS running
|
||||
MG_JOURNAL_HTTP_HOST=localhost \
|
||||
MG_JOURNAL_HTTP_PORT=9021 \
|
||||
MG_JOURNAL_DB_HOST=localhost \
|
||||
MG_JOURNAL_DB_PORT=5432 \
|
||||
MG_JOURNAL_DB_USER=magistrala \MG_JOURNAL_DB_PASS=magistrala \MG_JOURNAL_DB_NAME=journal \
|
||||
MG_AUTH_GRPC_URL=localhost:7001 \
|
||||
MG_DOMAINS_GRPC_URL=localhost:7003 \
|
||||
MG_ES_URL=nats://localhost:4222 \
|
||||
$GOBIN/magistrala-journal
|
||||
```
|
||||
|
||||
## HTTP API
|
||||
|
||||
Base URL defaults to `http://localhost:9021`. All journal and telemetry endpoints require `Authorization: Bearer <token>` (health is public).
|
||||
|
||||
### Usage
|
||||
|
||||
| Operation | Description |
|
||||
| --- | --- |
|
||||
| List user journals | Page through journals for a user across domains. |
|
||||
| List entity journals | Page through journals for a group, client, channel, or user within a domain. |
|
||||
| View client telemetry | Aggregate telemetry counters for a client in a domain. |
|
||||
| Health check | Liveness and build info. |
|
||||
|
||||
### API examples
|
||||
|
||||
#### List user journals
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:9021/journal/user/${USER_ID}?limit=5&with_attributes=true&dir=desc" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"journals": [
|
||||
{
|
||||
"operation": "user.create",
|
||||
"occurred_at": "2024-01-11T12:05:07.449053Z",
|
||||
"attributes": {
|
||||
"created_at": "2024-06-12T11:34:32.991591Z",
|
||||
"id": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"identity": "Gawne-Havlicek@email.com",
|
||||
"name": "Newgard-Frisina",
|
||||
"status": "enabled",
|
||||
"updated_at": "2024-06-12T11:34:33.116795Z",
|
||||
"updated_by": "ad228f20-4741-47c5-bef7-d871b541c019"
|
||||
},
|
||||
"metadata": {
|
||||
"Update": "Calvo-Felkins"
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"offset": 0,
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
#### List entity journals in a domain
|
||||
|
||||
Retrieves telemetry data for a specific client within a domain. This includes connection status, messages sent/received, and other metrics.
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:9021/${DOMAIN_ID}/journal/client/${CLIENT_ID}?operation=client.create&with_metadata=true&dir=desc&limit=10" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 2,
|
||||
"offset": 0,
|
||||
"limit": 10,
|
||||
"journals": [
|
||||
{
|
||||
"operation": "client.create",
|
||||
"occurred_at": "2024-06-12T11:34:33Z",
|
||||
"domain": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"attributes": {
|
||||
"id": "bb7edb32-2eac-4aad-aebe-ed96fe073879",
|
||||
"domain": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"name": "clientName",
|
||||
"status": "enabled"
|
||||
},
|
||||
"metadata": {
|
||||
"trace_id": "6efb4c24b1b4a684"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### View client telemetry
|
||||
|
||||
Retrieves telemetry data for a specific client within a domain. This includes connection status, messages sent/received, and other metrics.
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:9021/${DOMAIN_ID}/journal/client/${CLIENT_ID}/telemetry" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "bb7edb32-2eac-4aad-aebe-ed96fe073879",
|
||||
"domain_id": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"subscriptions": 5,
|
||||
"inbound_messages": 1234567,
|
||||
"outbound_messages": 987654,
|
||||
"first_seen": "2024-01-11T10:00:00Z",
|
||||
"last_seen": "2024-01-11T12:05:07.449053Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Health check
|
||||
|
||||
```bash
|
||||
curl "http://localhost:9021/health"
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "pass",
|
||||
"version": "0.18.0",
|
||||
"commit": "ffffffff",
|
||||
"description": "journal service",
|
||||
"build_time": "1970-01-01_00:00:00",
|
||||
"instance_id": "b4f1d5d2-4f24-4c2a-9a40-123456789abc"
|
||||
}
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains API-related concerns: endpoint definitions, middlewares
|
||||
// and all resource representations.
|
||||
package api
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
func retrieveJournalsEndpoint(svc journal.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(retrieveJournalsReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
page, err := svc.RetrieveAll(ctx, session, req.page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pageRes{
|
||||
JournalsPage: page,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveClientTelemetryEndpoint(svc journal.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(retrieveClientTelemetryReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
telemetry, err := svc.RetrieveClientTelemetry(ctx, session, req.clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientTelemetryRes{
|
||||
ClientTelemetry: telemetry,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,573 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
api "github.com/absmach/magistrala/api/http"
|
||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
httpapi "github.com/absmach/magistrala/journal/api"
|
||||
"github.com/absmach/magistrala/journal/mocks"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
validToken = "valid"
|
||||
validID = testsutil.GenerateUUID(&testing.T{})
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
client *http.Client
|
||||
method string
|
||||
url string
|
||||
token string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (tr testRequest) make() (*http.Response, error) {
|
||||
req, err := http.NewRequest(tr.method, tr.url, tr.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tr.token != "" {
|
||||
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
|
||||
}
|
||||
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newjournalServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
|
||||
logger := mglog.NewMock()
|
||||
authn := new(authnmocks.Authentication)
|
||||
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
|
||||
mux := httpapi.MakeHandler(svc, am, logger, "journal-log", "test")
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func TestListUserJournalsEndpoint(t *testing.T) {
|
||||
es, svc, authn := newjournalServer()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
url string
|
||||
contentType string
|
||||
status int
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
desc: "successful",
|
||||
token: validToken,
|
||||
url: "/user/123",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "empty token",
|
||||
token: "",
|
||||
url: "/user/123",
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with service error",
|
||||
token: validToken,
|
||||
url: "/user/123",
|
||||
status: http.StatusForbidden,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "with offset",
|
||||
token: validToken,
|
||||
url: "/user/123?offset=10",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid offset",
|
||||
token: validToken,
|
||||
url: "/user/123?offset=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with limit",
|
||||
token: validToken,
|
||||
url: "/user/123?limit=10",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid limit",
|
||||
token: validToken,
|
||||
url: "/user/123?limit=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with operation",
|
||||
token: validToken,
|
||||
url: "/user/123?operation=user.create",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with malformed operation",
|
||||
token: validToken,
|
||||
url: "/user/123?operation=user.create&operation=user.update",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with from",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?from=%d", time.Now().Unix()),
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid from",
|
||||
token: validToken,
|
||||
url: "/user/123?from=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid from as UnixNano",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?from=%d", time.Now().UnixNano()),
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with to",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?to=%d", time.Now().Unix()),
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid to",
|
||||
token: validToken,
|
||||
url: "/user/123?to=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid to as UnixNano",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?to=%d", time.Now().UnixNano()),
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with attributes",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?with_attributes=%s", strconv.FormatBool(true)),
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid attributes",
|
||||
token: validToken,
|
||||
url: "/user/123?with_attributes=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with metadata",
|
||||
token: validToken,
|
||||
url: fmt.Sprintf("/user/123?with_metadata=%s", strconv.FormatBool(true)),
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid metadata",
|
||||
token: validToken,
|
||||
url: "/user/123?with_metadata=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with asc direction",
|
||||
token: validToken,
|
||||
url: "/user/123?dir=asc",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with desc direction",
|
||||
token: validToken,
|
||||
url: "/user/123?dir=desc",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid direction",
|
||||
token: validToken,
|
||||
url: "/user/123?dir=ten",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with malformed direction",
|
||||
token: validToken,
|
||||
url: "/user/123?dir=invalid&dir=invalid2",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty url",
|
||||
token: validToken,
|
||||
url: "",
|
||||
status: http.StatusNotFound,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty entity type",
|
||||
token: validToken,
|
||||
url: "//123",
|
||||
status: http.StatusNotFound,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty entity ID",
|
||||
token: validToken,
|
||||
url: "/user/",
|
||||
status: http.StatusNotFound,
|
||||
svcErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.token == validToken {
|
||||
c.session = smqauthn.Session{
|
||||
UserID: testsutil.GenerateUUID(t),
|
||||
}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, nil)
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, c.session, mock.Anything).Return(journal.JournalsPage{}, c.svcErr)
|
||||
req := testRequest{
|
||||
client: es.Client(),
|
||||
method: http.MethodGet,
|
||||
url: es.URL + "/journal" + c.url,
|
||||
token: c.token,
|
||||
}
|
||||
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, c.desc)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, c.status, resp.StatusCode, c.desc)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEntityJournalsEndpoint(t *testing.T) {
|
||||
es, svc, authn := newjournalServer()
|
||||
|
||||
domainID := testsutil.GenerateUUID(t)
|
||||
userID := testsutil.GenerateUUID(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
domainID string
|
||||
url string
|
||||
contentType string
|
||||
page journal.Page
|
||||
status int
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
desc: "with group type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/group/" + validID,
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.GroupEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with channel type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/channel/" + validID,
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.ChannelEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with client type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/client/" + validID,
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.ClientEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/client/" + validID,
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.ClientEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
},
|
||||
status: http.StatusForbidden,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "with channel operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/channel/" + validID + "?operation=channel.create",
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.ChannelEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
Operation: "channel.create",
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with group operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/group/" + validID + "?operation=group.create",
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.GroupEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
Operation: "group.create",
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with client operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/client/" + validID + "?operation=client.create",
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.ClientEntity,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Direction: api.DescDir,
|
||||
Operation: "client.create",
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with malformed operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/user/123?operation=user.create&operation=user.update",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid entity type",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/invalid/123",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with all query params",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/group/" + validID + "?offset=10&limit=10&operation=group.create&from=0&to=10&with_attributes=true&with_metadata=true&dir=asc",
|
||||
page: journal.Page{
|
||||
EntityID: validID,
|
||||
EntityType: journal.GroupEntity,
|
||||
Offset: 10,
|
||||
Limit: 10,
|
||||
Operation: "group.create",
|
||||
From: time.Time{},
|
||||
To: time.Unix(10, 0),
|
||||
WithAttributes: true,
|
||||
WithMetadata: true,
|
||||
Direction: api.AscDir,
|
||||
},
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: " with empty token",
|
||||
url: "/group/123",
|
||||
domainID: domainID,
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty domain ID",
|
||||
token: validToken,
|
||||
url: "/group/",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.token == validToken {
|
||||
c.session = smqauthn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
DomainUserID: domainID + "_" + userID,
|
||||
}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, c.authnErr)
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, c.session, c.page).Return(journal.JournalsPage{}, c.svcErr)
|
||||
req := testRequest{
|
||||
client: es.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/journal%s", es.URL, c.domainID, c.url),
|
||||
token: c.token,
|
||||
}
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, c.desc)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, c.status, resp.StatusCode, c.desc)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveClientTelemetryEndpoint(t *testing.T) {
|
||||
es, svc, authn := newjournalServer()
|
||||
|
||||
clientID := testsutil.GenerateUUID(t)
|
||||
userID := testsutil.GenerateUUID(t)
|
||||
domanID := testsutil.GenerateUUID(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
clientID string
|
||||
domainID string
|
||||
url string
|
||||
contentType string
|
||||
status int
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
desc: "successful",
|
||||
token: validToken,
|
||||
clientID: clientID,
|
||||
domainID: domanID,
|
||||
url: fmt.Sprintf("/client/%s/telemetry", clientID),
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with service error",
|
||||
token: validToken,
|
||||
clientID: clientID,
|
||||
domainID: domanID,
|
||||
url: fmt.Sprintf("/client/%s/telemetry", clientID),
|
||||
status: http.StatusForbidden,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "with empty token",
|
||||
clientID: clientID,
|
||||
domainID: domanID,
|
||||
url: fmt.Sprintf("/client/%s/telemetry", clientID),
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid client ID",
|
||||
token: validToken,
|
||||
domainID: domanID,
|
||||
clientID: "invalid",
|
||||
url: "/client/invalid/telemetry",
|
||||
status: http.StatusNotFound,
|
||||
svcErr: svcerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.token == validToken {
|
||||
c.session = smqauthn.Session{
|
||||
UserID: userID,
|
||||
DomainID: c.domainID,
|
||||
DomainUserID: c.domainID + "_" + userID,
|
||||
}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, c.authnErr)
|
||||
svcCall := svc.On("RetrieveClientTelemetry", mock.Anything, c.session, c.clientID).Return(journal.ClientTelemetry{}, c.svcErr)
|
||||
req := testRequest{
|
||||
client: es.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/journal%s", es.URL, c.domainID, c.url),
|
||||
token: c.token,
|
||||
}
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, c.desc)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, c.status, resp.StatusCode, c.desc)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user