Compare commits

...

52 Commits

Author SHA1 Message Date
dusan e35ac13390 NOISSUE - Archive
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 23:37:04 +02:00
dusan 94255e2393 Add warning
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 12:11:19 +02:00
dusan 02b13d8e0c Rename to magistrala-old to be replaced with magistrala
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 11:32:57 +02:00
Steve Munene 1f91de480e NOISSUE - Fix alarms metadata (#444)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-18 13:30:06 +01:00
dusan eb29b4e298 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-18 10:26:47 +01:00
dusan 5841d3f7e4 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 19:57:15 +01:00
dusan f44b910546 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 18:11:14 +01:00
dusan 52896241c5 NOISSUE - Update dependencies
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 18:10:29 +01:00
dependabot[bot] 1b36c6e1b6 NOISSUE - Bump the gh-dependency group in /.github/workflows with 3 updates (#443)
Bumps the gh-dependency group in /.github/workflows with 3 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [dorny/paths-filter](https://github.com/dorny/paths-filter).


Updates `docker/setup-buildx-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

Updates `docker/login-action` from 3 to 4
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

Updates `dorny/paths-filter` from 3 to 4
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
- dependency-name: dorny/paths-filter
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 17:19:02 +01:00
Steve Munene d5d5e8bf7e NOISSUE - Add build tag constraint for middlewares (#442)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-17 14:49:52 +01:00
Steve Munene b2967fb2e5 NOISSUE - Fix SDK test (#441)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-16 16:37:19 +01:00
Steve Munene 7fb5dd7b55 NOISSUE - Refactor listing for rules and reports (#433)
* add access control to rules engine

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update authorization method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert code

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* initial implementation

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove domain from method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix userid parameter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update checksuperadmin method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert changes

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-16 14:39:49 +01:00
Steve Munene 2ef8437d8b MG-370 - Add fine grained access control to alarms (#404)
* add access control to rules engine

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add access control to reports

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add access control to alarms

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused variables

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update authorization method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert code

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove roles

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update alarm permissions

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update alarm permissions

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert endpoint changes

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix make fetch

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert env variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove rule prefix

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove trailing line

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused constants

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* re consumer

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update listing

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix rule roles interface

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* refactor listing commands

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address coments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update script

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix time layout

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix role name

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove white spaces

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update check usperadmin method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update go mod file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add missing env variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-13 14:29:32 +01:00
dusan 6dbcfcae58 NOISSUE - Update SuperMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-12 11:37:31 +01:00
Ian Ngethe Muchiri ab8d335767 NOISSUE - Update UI docker compose (#439)
Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
2026-03-11 16:22:06 +01:00
dusan 99cea4abe8 NOISSUE - Update Compose to use the latest UI
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 15:58:59 +01:00
dusan 04379dc7a9 NOISSUE - Fix SDK bugs
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 12:48:15 +01:00
dependabot[bot] 0800b260d5 NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp (#434)
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.66.0 to 0.67.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.66.0...zpages/v0.67.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.67.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 10:59:45 +01:00
dusan 67feea693e NOISSUE - Fix Reports test status code bug
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 10:57:56 +01:00
dusan db1676cb0f NOISSUE - Update Mockery version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 09:54:10 +01:00
Steve Munene 4b57387110 NOISSUE - Remove Panic method from go scripts (#437)
* add regex

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-10 20:46:46 +01:00
Dušan Borovčanin e3373e1b49 NOISSUE - Fix SeaweedFS init (#436)
* Fix SeaweedFS init

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Use 20s wait period

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-10 16:29:59 +01:00
dependabot[bot] 453c880efc NOISSUE - Bump go.opentelemetry.io/otel/trace from 1.41.0 to 1.42.0 (#429)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:55:11 +01:00
dependabot[bot] dc2b063b6e NOISSUE - Bump go.opentelemetry.io/otel from 1.41.0 to 1.42.0 (#431)
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:54:48 +01:00
dependabot[bot] 65ee66dc32 NOISSUE - Bump github.com/slack-go/slack from 0.18.0 to 0.19.0 (#428)
Bumps [github.com/slack-go/slack](https://github.com/slack-go/slack) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/slack-go/slack/releases)
- [Changelog](https://github.com/slack-go/slack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/slack-go/slack/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: github.com/slack-go/slack
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:48:14 +01:00
dependabot[bot] df9fe93a98 NOISSUE - Bump golang.org/x/sync from 0.19.0 to 0.20.0 (#430)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/sync/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:47:57 +01:00
dependabot[bot] 84fba105c9 NOISSUE - Bump google.golang.org/grpc from 1.79.1 to 1.79.2 (#432)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:47:42 +01:00
Arvindh f75f08db4a NOISSUE - Fix alarms schema (#425)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2026-03-05 17:04:00 +01:00
Steve Munene be1dc130d6 NOISSUE - Add alarm relation to rules (#424)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-05 15:21:34 +01:00
Steve Munene 178a62c08f MG-370 - Add fine grained access control to reports (#403)
* add access control to rules engine

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix build

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix report database

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix variable naming

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix entity type

Signed-off-by: Arvindh <arvindh91@gmail.com>

* update authorize method

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix generate report

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert env changes

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update generate permission

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert go mod file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert go mod file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
2026-03-05 13:59:22 +01:00
Steve Munene 362a4fc76d MG-370 - Add fine grained access control to rules engine (#402)
* update go mod file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix rules endpoint tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix yaml file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix build

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove roles from alarms

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* change approach for schema combaine

Signed-off-by: Arvindh <arvindh91@gmail.com>

* change approach for schema combaine

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix permissions for rules

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix authorization file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
2026-03-05 11:42:51 +01:00
Ian Ngethe Muchiri 8e75edc9f5 NOISSUE - Add alarms, reports and rules sdk (#423)
* add alarms, reports and rules sdk

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix tests

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix linter

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

---------

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
2026-03-05 10:54:14 +01:00
Steve Munene a031426715 NOISSUE - Update metadata UI type to string (#422)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 16:01:48 +01:00
dusan 962b473a5f Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-03 15:35:30 +01:00
Steve Munene de6f3921a4 MG-406 - Fix panic issues in rules engine go script (#407)
* initial implementation

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* revert variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 15:34:27 +01:00
Steve Munene 0a45a96fac NOISSUE - Update Rules metadata migrations (#421)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 15:33:58 +01:00
dependabot[bot] c2afb88e79 NOISSUE - Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0 (#420)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.48.0 to 1.49.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.48.0...v1.49.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 13:07:29 +01:00
Steve Munene 9a3a07cd2e NOISSUE - Update Authorization method (#418)
* fix authorization

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fetch supermq

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-02 12:55:07 +01:00
dependabot[bot] 28e809b9d8 NOISSUE - Bump golang in /docker in the docker-dependency group (#419)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.26rc2-alpine3.22 to 1.26-alpine3.22

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26-alpine3.22
  dependency-type: direct:production
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 11:08:51 +01:00
Felix Gateru eb14615cf5 MG-344 - Update Provision Service (#386)
* feat: update provison service

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* refactor: remove duplicate env variables

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* ci: make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* docs(README.md): update README

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-28 17:55:22 +01:00
dusan d652652b79 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-02-27 21:33:18 +01:00
dependabot[bot] a2087a1f1f NOISSUE - Bump google.golang.org/grpc from 1.78.0 to 1.79.1 (#405)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.78.0 to 1.79.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:33:17 +01:00
dependabot[bot] f195ced6a0 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.3 to 9.18.0 (#410)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.3 to 9.18.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.3...v9.18.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:07:10 +01:00
dependabot[bot] a33d1cfe4f NOISSUE - Bump github.com/authzed/authzed-go from 1.7.0 to 1.8.0 (#413)
Bumps [github.com/authzed/authzed-go](https://github.com/authzed/authzed-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/authzed/authzed-go/releases)
- [Commits](https://github.com/authzed/authzed-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/authzed/authzed-go
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:01:31 +01:00
dependabot[bot] 40cbb66638 NOISSUE - Bump github.com/slack-go/slack from 0.17.3 to 0.18.0 (#411)
Bumps [github.com/slack-go/slack](https://github.com/slack-go/slack) from 0.17.3 to 0.18.0.
- [Release notes](https://github.com/slack-go/slack/releases)
- [Changelog](https://github.com/slack-go/slack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/slack-go/slack/compare/v0.17.3...v0.18.0)

---
updated-dependencies:
- dependency-name: github.com/slack-go/slack
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:00:26 +01:00
dependabot[bot] 08b5ac52cf NOISSUE - Bump github.com/caarlos0/env/v11 from 11.3.1 to 11.4.0 (#412)
Bumps [github.com/caarlos0/env/v11](https://github.com/caarlos0/env) from 11.3.1 to 11.4.0.
- [Release notes](https://github.com/caarlos0/env/releases)
- [Commits](https://github.com/caarlos0/env/compare/v11.3.1...v11.4.0)

---
updated-dependencies:
- dependency-name: github.com/caarlos0/env/v11
  dependency-version: 11.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:00:03 +01:00
Felix Gateru 0c1ccf1f04 MG-408 - Fix client authentication in readers (#409)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-25 09:59:43 +01:00
Felix Gateru f28a3e8390 MG-401 - Fix authorization error in Magistrala services (#400)
* chore: update SMQ version

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* chore: run make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* tests: fix provison tests

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* ci(build.yaml): get go version from go mod

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-11 11:17:13 +01:00
Felix Gateru 3685d231cf NOISSUE - Fix API docs page (#399)
* fix: fix api docs page

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* chore: run make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-09 16:21:58 +01:00
Steve Munene f3c5d603a0 MG-379 - Refactor makefile (#385)
* refactor command

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update makefile and readme file

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update commands

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* Add changes for apple silicon

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Co-authored-by: dorcaslitunya <anonolitunya@gmail.com>
2026-01-29 09:50:38 +01:00
dependabot[bot] 5050caa3d3 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.2 to 9.17.3 (#391)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.2 to 9.17.3.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/v9.17.3/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.2...v9.17.3)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.17.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 09:45:44 +01:00
dependabot[bot] 4ed31c2c66 NOISSUE - Bump github.com/absmach/certs from 0.18.4 to 0.18.5 (#392)
Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.4 to 0.18.5.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.4...v0.18.5)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 09:45:23 +01:00
132 changed files with 17778 additions and 1842 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SuperMQ API Documentation</title>
<title>Magistrala API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.30.3/swagger-ui.css">
<style>
body {
@@ -101,7 +101,7 @@ SPDX-License-Identifier: Apache-2.0
</head>
<body>
<div class="service-selector">
<h1>SuperMQ API Documentation</h1>
<h1>Magistrala API Documentation</h1>
<div class="service-dropdown-container">
<label for="serviceDropdown">Select Service:</label>
<select id="serviceDropdown" class="service-dropdown"></select>
+7 -3
View File
@@ -22,10 +22,14 @@ jobs:
run: |
git fetch --prune --unshallow --tags
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: 1.25.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Set GOBIN
@@ -46,10 +50,10 @@ jobs:
verbose: true
- name: Set up Docker Build
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.DOCKER_USERNAME }}
+1 -1
View File
@@ -42,4 +42,4 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./swagger-ui
cname: docs.api.supermq.absmach.eu
cname: docs.api.magistrala.absmach.eu
+12 -5
View File
@@ -17,10 +17,14 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v6
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: 1.25.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Set GOBIN
@@ -47,7 +51,7 @@ jobs:
- name: Run linters
uses: golangci/golangci-lint-action@v9
with:
version: v2.4.0
version: latest
args: --config ./tools/config/.golangci.yaml
run-tests:
@@ -61,14 +65,18 @@ jobs:
with:
fetch-depth: 0
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: 1.25.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Check for changes in specific paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@v4
id: changes
with:
base: main
@@ -133,7 +141,6 @@ jobs:
- "alarms/**"
- "cmd/alarms/**"
- name: Create coverage directory
run: |
mkdir coverage
+67 -17
View File
@@ -7,7 +7,31 @@ SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-wri
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOARCH ?= amd64
# Auto-detect architecture: use arm64 for Apple Silicon, default to amd64 otherwise
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),arm64)
GOARCH ?= arm64
else ifeq ($(UNAME_M),aarch64)
GOARCH ?= arm64
else
GOARCH ?= amd64
endif
# Detect OS for sed compatibility: macOS (BSD sed) vs Linux (GNU sed)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
else
SED_INPLACE := sed -i
endif
# For Apple Silicon: use amd64 platform for pre-built images (emulation via Rosetta)
# This is needed because upstream stable images may not have ARM64 builds
ifeq ($(UNAME_M),arm64)
DOCKER_PLATFORM := --platform linux/amd64
else
DOCKER_PLATFORM :=
endif
VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'unknown')
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)
@@ -20,7 +44,7 @@ DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config
DEFAULT_DOCKER_COMPOSE_COMMAND := up
GRPC_MTLS_CERT_FILES_EXISTS = 0
MOCKERY = $(GOBIN)/mockery
MOCKERY_VERSION=3.5.3
MOCKERY_VERSION=3.7.0
PKG_PROTO_GEN_OUT_DIR=api/grpc
INTERNAL_PROTO_DIR=internal/proto
INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed 's|$(INTERNAL_PROTO_DIR)/||')
@@ -71,6 +95,31 @@ define make_docker_dev
-f docker/Dockerfile.dev ./build
endef
define run_with_arch_detection
@echo "Detecting architecture..."
@if [ "$(DETECTED_ARCH)" = "arm64" ] || [ "$(DETECTED_ARCH)" = "aarch64" ]; then \
echo "ARM64 architecture detected."; \
git checkout $(1); \
GOARCH=arm64 $(MAKE) dockers; \
for svc in $(SERVICES); do \
docker tag ghcr.io/absmach/magistrala/$$svc ghcr.io/absmach/magistrala/$$svc:latest; \
done; \
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=latest/' docker/.env && rm -f docker/.env.bak; \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
else \
echo "x86_64 architecture detected."; \
git checkout $(1); \
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$(2)/' docker/.env && rm -f docker/.env.bak; \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
fi
endef
ADDON_SERVICES = bootstrap provision certs timescale-reader timescale-writer postgres-reader postgres-writer
EXTERNAL_SERVICES = prometheus
@@ -229,21 +278,21 @@ grpc_mtls_certs:
check_tls:
ifeq ($(GRPC_TLS),true)
@unset GRPC_MTLS
@bash -c 'unset GRPC_MTLS'
@echo "gRPC TLS is enabled"
GRPC_MTLS=
else
@unset GRPC_TLS
@bash -c 'unset GRPC_TLS'
GRPC_TLS=
endif
check_mtls:
ifeq ($(GRPC_MTLS),true)
@unset GRPC_TLS
@bash -c 'unset GRPC_TLS'
@echo "gRPC MTLS is enabled"
GRPC_TLS=
else
@unset GRPC_MTLS
@bash -c 'unset GRPC_MTLS'
GRPC_MTLS=
endif
@@ -260,25 +309,26 @@ fetch_supermq:
@./scripts/supermq.sh
run_latest: check_certs
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
run_stable: check_certs
$(eval version = $(shell git describe --abbrev=0 --tags))
git checkout $(version)
sed -i 's/^SMQ_RELEASE_TAG=.*/SMQ_RELEASE_TAG=$(version)/' docker/supermq-docker/.env
sed -i 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$(version)/' docker/.env
docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
@version=$$(git describe --abbrev=0 --tags 2>/dev/null) || { echo "Error: No git tags found. Please create a release tag first (e.g., git tag v0.1.0) or use 'make run_latest' instead."; exit 1; }; \
echo "Using stable version: $$version"; \
git checkout $$version; \
$(SED_INPLACE) "s/^SMQ_RELEASE_TAG=.*/SMQ_RELEASE_TAG=$$version/" docker/supermq-docker/.env; \
$(SED_INPLACE) "s/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$$version/" docker/.env; \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
run_addons: check_certs
$(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC))))
@for SVC in $(RUN_ADDON_ARGS); do \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
done
+86 -62
View File
@@ -1,64 +1,60 @@
> [!WARNING]
> This repository is obsolete. All of its content has been merged to [www.github.com/absmach/magistrala](https://www.github.com/absmach/magistrala).
> Please use that repository for all active development.
<div align="center">
# Magistrala
# SuperMQ
### A Modern IoT Platform Built on SuperMQ
### Planetary event-driven infrastructure
### Scalable • Secure • Open-Source
**Made with ❤️ by [Abstract Machines](https://absmach.eu/)**
[![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
[![Continuous Delivery](https://github.com/absmach/magistrala/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
[![Build Status](https://github.com/absmach/magistrala/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/absmach/magistrala)](https://goreportcard.com/report/github.com/absmach/magistrala)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/absmach/magistrala)
[![Coverage](https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=SEMDAO3L09)](https://codecov.io/gh/absmach/magistrala)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE)
[![Matrix](https://img.shields.io/matrix/magistrala:matrix.org?style=flat-square)](https://matrix.to/#/#magistrala:matrix.org)
### [Guide](https://docs.magistrala.absmach.eu) | [Contributing](CONTRIBUTING.md) | [Website](https://www.absmach.eu/magistrala) | [Chat](https://matrix.to/#/#magistrala:matrix.org)
Made with ❤️ by [Abstract Machines](https://www.absmach.eu)
[![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
[![Check Generated Files](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yaml)
[![Coverage](https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=nPCEr5nW8S)](https://codecov.io/gh/absmach/magistrala)
[![License](https://img.shields.io/badge/license-Apache%20v2.0-blue.svg)](LICENSE)
[![Matrix](https://img.shields.io/matrix/supermq%3Amatrix.org?label=Chat&style=flat&logo=matrix&logoColor=white)](https://matrix.to/#/#supermq:matrix.org)
### [Guide](https://magistrala.absmach.eu/docs/) | [Contributing](CONTRIBUTING.md) | [Website](https://absmach.eu/) | [Chat](https://matrix.to/#/#supermq:matrix.org)
</div>
## Introduction 🌍
## Introduction 📖
Magistrala is a cutting-edge, open-source IoT cloud platform built on top of [SuperMQ](https://github.com/absmach/supermq). It serves as a robust middleware solution for building complex IoT applications. With Magistrala, you can connect and manage IoT devices seamlessly using multi-protocol support, all while ensuring security and scalability.
SuperMQ is a distributed, highly scalable, and secure open-source cloud platform for messaging and event-driven architecture (EDA). It is a planetarily distributed, highly scalable, and secure platform that serves as a robust foundation for building advanced real-time and reactive systems.
### Key Benefits
## Why SuperMQ Stands Out 🚀
- **Unified IoT Management**: Connect sensors, actuators and applications over various network protocols.
- **Scalability and Performance**: Designed to handle enterprise-grade IoT deployments.
- **Secure by Design**: Features such as mutual TLS authentication and fine-grained access control.
- **Open-Source Freedom**: Patent-free, community-driven, and designed for extensibility.
SuperMQ bridges the gap between various network protocols (HTTP, MQTT, WebSocket, CoAP, and more) to provide a seamless messaging experience. Whether you're working on IoT solutions, real-time data pipelines, or event-driven systems, MagisSuperMQtrala has you covered. 🌐✨
## Features
## Key Features 🌟
- 🏢 **Multi-Tenancy**: Support for managing multiple independent domains seamlessly.
- 👥 **Multi-User Platform**: Unlimited organizational hierarchies and user roles for streamlined collaboration.
- 🌐 **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more (see [contrib repository](https://www.github.com/absmach/mg-contrib) for LoRa and OPC UA).
- 💻 **Device Management and Provisioning**: Including Zero-Touch provisioning for seamless device onboarding.
- 🛡️ **Mutual TLS Authentication (mTLS)**: Secure communication using X.509 certificates.
- 📜 **Fine-Grained Access Control**: Support for ABAC and RBAC policies.
- 💾 **Message Persistence**: Timescale and PostgreSQL support (see [contrib repository](https://www.github.com/absmach/mg-contrib) for Cassandra, InfluxDB, and MongoDB).
- 🔄 **Rules Engine (RE)**: Automate processes with flexible rules for decision-making.
- 🚨 **Alarms and Triggers**: Immediate notifications for critical IoT events.
- 📅 **Scheduled Actions**: Plan and execute tasks at predefined times.
- 📝 **Audit Logs**: Maintain a detailed history of platform activities for compliance and debugging.
- 📊 **Platform Logging and Instrumentation**: Integrated with Prometheus and OpenTelemetry.
- **Event Sourcing**: Streamlined architecture for real-time IoT event processing.
- 🐳 **Container-Based Deployment**: Fully compatible with Docker and Kubernetes.
- 🌍 **Edge and IoT Ready**: Agent and Export services for managing remote IoT gateways.
- 🛠️ **Developer Tools**: Comprehensive SDK and CLI for efficient development.
- 🏗️ **Domain-Driven Design**: High-quality codebase and extensive test coverage.
- **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more! 🌉
- **Secure by Design**: Mutual TLS (mTLS) with X.509 Certificates, JWT support, and multi-protocol authorization. 🔒
- **Fine-Grained Access Control**: Support for ABAC and RBAC policies. 📜
- **Multi-Tenant**: Manage multiple domains seamlessly. 🏢
- **Multi-User**: Unlimited organizational hierarchies for user management. 👥
- **Application Management**: Group and share messaging clients for streamlined operations. 📱
- **Ease of Use**: Simple and powerful communication channel management, grouping, and sharing. ✨
- **Personal Access Tokens (PATs)**: Scoped and revocable tokens for enhanced security. 🔑
- **Observability**: Integrated logging and instrumentation with Prometheus and OpenTelemetry. 📈
- **Event Sourcing**: Build robust and scalable architectures.
- **Edge and IoT Ready**: Supports MQTT and CoAP protocols for seamless IoT gateway and sensor communication and management. 🌍
- **Developer-Friendly**: SDKs, CLI tools, and comprehensive documentation to get you started. 👩‍💻👨‍💻
- **Production-Ready**: Container-based deployment using Docker and Kubernetes. 🐳☸️
## Installation 🛠️
There are multiple ways to run Magistrala.
There are multiple ways to run SuperMQ.
First, clone the repository and position to it:
```bash
git clone https://github.com/absmach/magistrala.git
cd magistrala
git clone https://github.com/absmach/supermq.git
cd supermq
```
To run the latest stable (tagged) version, use:
@@ -86,48 +82,76 @@ The `make run_stable` command will:
git checkout main
```
### Running on Apple Silicon (M1/M2/M3) Macs
## 📤 Usage
When running SuperMQ on Apple Silicon Macs, the Makefile will automatically detect your ARM64 architecture and build Docker images locally.
**If using Docker Desktop:**
1. **Enable Apple Virtualization Framework**: In Docker Desktop, go to:
- Settings → General → Enable "Use the new Virtualization framework"
2. **Enable Rosetta for x86_64 Emulation**: In Docker Desktop, go to:
- Settings → General → Enable "Use Rosetta for x86_64/amd64 emulation on Apple Silicon"
After enabling these options, restart Docker Desktop, then run `make run_stable` or `make run_latest` as usual.
To manually run SuperMQ, clone the repository and start all core services:
```bash
docker compose -f docker/docker-compose.yaml --env-file docker/.env up
```
### Usage 📤📥
**Using the CLI :**
Check the health of a specific service using the CLI:
```bash
make cli
./build/cli health <service>
./build/supermq-cli status
```
Replace `<service>` with the name of the service you want to check.
This command retrieves the status of the SuperMQ server and outputs it to the console.
**Using Curl :**
Alternatively, use a simple HTTP `GET` request to check the platform's health:
**Using HTTP with Curl :**
```bash
curl -X GET http://localhost:8080/health
curl -X GET http://localhost:8080/status
```
For additional usage examples and advanced configurations, visit the [official documentation](https://docs.magistrala.absmach.eu).
This request fetches the server status over HTTP and provides a JSON response.
## 📚 Documentation
See our [CLI documentation](https://magistrala.absmach.eu/docs/dev-guide/cli/introduction-to-cli/) for more details.
Complete documentation is available at the [Magistrala official docs page](https://docs.magistrala.absmach.eu).
## Documentation 📚
For CLI usage details, visit the [CLI Documentation](https://docs.magistrala.absmach.eu/dev-guide/cli/introduction-to-cli).
The official documentation is hosted at [SuperMQ docs page](https://magistrala.absmach.eu/docs/).
## 🌐 Community and Contributing
Documentation is auto-generated, check out the instructions in the [docs repository](https://github.com/absmach/magistrala-website).
If you spot an error or a need for corrections, please let us know - or even better: send us a PR! 💌
Join the community and contribute to the future of IoT middleware:
## Community and Contributing 🤝
- [Open Issues](https://github.com/absmach/magistrala/issues)
- [Contribution Guide](CONTRIBUTING.md)
- [Matrix Chat](https://matrix.to/#/#magistrala:matrix.org)
Thank you for your interest in SuperMQ and the desire to contribute!
## 📜 License
1. Take a look at our [open issues](https://github.com/absmach/magistrala/issues). The [good-first-issue](https://github.com/absmach/magistrala/labels/good-first-issue) label is specifically for issues that are great for getting started.
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
Magistrala is open-source software licensed under the [Apache-2.0](LICENSE) license. Contributions are welcome and encouraged!
Join our community:
## 💼 Professional Support
- [Matrix Room](https://matrix.to/#/#supermq\:matrix.org)
Need help deploying Magistrala or integrating it into your systems? Contact **[Abstract Machines](https://www.absmach.eu)** for expert guidance and support.
## Professional Support 💼
Need help deploying SuperMQ or integrating it into your system? Reach out to **[Abstract Machines](https://absmach.eu/)** for professional support and guidance.
## License 📜
SuperMQ is open-source software licensed under the [Apache License 2.0](LICENSE). Contributions are welcome!
## Acknowledgments 🙌
Special thanks to the amazing contributors who make SuperMQ possible. Check out the [MAINTAINERS](MAINTAINERS) file to see the team behind the magic.
Ready to build the future of messaging and event-driven systems? Let's get started! 🚀
+3 -1
View File
@@ -72,6 +72,7 @@ type PageMetadata struct {
AssignedBy string `json:"assigned_by" db:"assigned_by"`
AcknowledgedBy string `json:"acknowledged_by" db:"acknowledged_by"`
ResolvedBy string `json:"resolved_by" db:"resolved_by"`
UserID string `json:"user_id" db:"user_id"`
}
func (a Alarm) Validate() error {
@@ -116,6 +117,7 @@ type Repository interface {
CreateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
UpdateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
ViewAlarm(ctx context.Context, alarmID, domainID string) (Alarm, error)
ListAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
ListAllAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
ListUserAlarms(ctx context.Context, userID string, pm PageMetadata) (AlarmsPage, error)
DeleteAlarm(ctx context.Context, id string) error
}
+1 -1
View File
@@ -16,7 +16,7 @@ import (
func updateAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
return func(ctx context.Context, request any) (any, error) {
req := request.(alarmReq)
req := request.(updateAlarmReq)
if err := req.validate(); err != nil {
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
+15
View File
@@ -23,6 +23,21 @@ func (req alarmReq) validate() error {
return nil
}
type updateAlarmReq struct {
alarms.Alarm `json:",inline"`
}
func (req updateAlarmReq) validate() error {
if req.Alarm.ID == "" {
return errors.New("missing alarm id")
}
if req.Alarm.AssigneeID == "" && req.Alarm.AcknowledgedBy == "" && req.Alarm.ResolvedBy == "" && len(req.Alarm.Metadata) == 0 {
return errors.New("at least one of assignee_id, acknowledged_by, resolved_by, or metadata must be set")
}
return nil
}
type listAlarmsReq struct {
alarms.PageMetadata
}
+3 -3
View File
@@ -195,12 +195,12 @@ func decodeAlarmReq(_ context.Context, r *http.Request) (any, error) {
func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return alarmReq{}, apiutil.ErrUnsupportedContentType
return updateAlarmReq{}, apiutil.ErrUnsupportedContentType
}
req := alarmReq{}
req := updateAlarmReq{}
if err := json.NewDecoder(r.Body).Decode(&req.Alarm); err != nil {
return alarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
return updateAlarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
req.Alarm.ID = chi.URLParam(r, "alarmID")
+85 -25
View File
@@ -7,10 +7,12 @@ import (
"context"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/operations"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/pkg/authn"
smqauthz "github.com/absmach/supermq/pkg/authz"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
)
@@ -22,50 +24,71 @@ var (
)
type authorizationMiddleware struct {
svc alarms.Service
authz smqauthz.Authorization
svc alarms.Service
authz smqauthz.Authorization
entitiesOps permissions.EntitiesOperations[permissions.Operation]
}
var _ alarms.Service = (*authorizationMiddleware)(nil)
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization) alarms.Service {
return &authorizationMiddleware{
svc: svc,
authz: authz,
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation]) (alarms.Service, error) {
if err := entitiesOps.Validate(); err != nil {
return nil, err
}
return &authorizationMiddleware{
svc: svc,
authz: authz,
entitiesOps: entitiesOps,
}, nil
}
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (err error) {
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) error {
return am.svc.CreateAlarm(ctx, alarm)
}
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (dba alarms.Alarm, err error) {
// If assignee is present, check if assignee is member of domain
if err := am.authorize(ctx, alarms.OpUpdateAlarm, session); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
if len(alarm.Metadata) > 0 {
if err := am.authorize(ctx, operations.OpUpdateAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
if alarm.AssigneeID != "" {
domainUserId := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
if err := am.authorize(ctx, operations.OpAssignAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
domainUserID := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: domainUserId,
Subject: domainUserID,
Permission: policies.MembershipPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}); err != nil {
}, nil); err != nil {
return alarms.Alarm{}, err
}
}
if alarm.AcknowledgedBy != "" {
if err := am.authorize(ctx, operations.OpAcknowledgeAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
if alarm.ResolvedBy != "" {
if err := am.authorize(ctx, operations.OpResolveAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
return am.svc.UpdateAlarm(ctx, session, alarm)
}
func (am *authorizationMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, alarms.OpDeleteAlarm, session); err != nil {
if err := am.authorize(ctx, operations.OpDeleteAlarm, session, policies.DomainType, session.DomainID); err != nil {
return errors.Wrap(errDomainDeleteAlarms, err)
}
@@ -77,23 +100,27 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
pm.DomainID = session.DomainID
}
if err := am.authorize(ctx, alarms.OpListAlarms, session); err != nil {
return alarms.AlarmsPage{}, errors.Wrap(errDomainViewAlarms, err)
switch err := am.checkSuperAdmin(ctx, session); {
case err == nil:
session.SuperAdmin = true
case errors.Contains(err, svcerr.ErrSuperAdminAction):
default:
return alarms.AlarmsPage{}, err
}
return am.svc.ListAlarms(ctx, session, pm)
}
func (am *authorizationMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
if err := am.authorize(ctx, alarms.OpViewAlarm, session); err != nil {
if err := am.authorize(ctx, operations.OpViewAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainViewAlarms, err)
}
return am.svc.ViewAlarm(ctx, session, id)
}
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session) error {
perm, err := alarms.GetPermission(op)
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
if err != nil {
return err
}
@@ -103,10 +130,43 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: perm,
Object: obj,
ObjectType: objType,
Permission: perm.String(),
}
return am.authz.Authorize(ctx, pr)
var pat *smqauthz.PATReq
if session.PatID != "" {
opName := am.entitiesOps.OperationName(operations.EntityType, op)
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: session.DomainID,
EntityType: operations.EntityType,
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
return err
}
return nil
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if session.Role != authn.SuperAdminRole {
return svcerr.ErrSuperAdminAction
}
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
SubjectType: policies.UserType,
Subject: session.UserID,
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}, nil); err != nil {
return err
}
return nil
}
+83 -11
View File
@@ -165,12 +165,12 @@ func (_c *Repository_DeleteAlarm_Call) RunAndReturn(run func(ctx context.Context
return _c
}
// ListAlarms provides a mock function for the type Repository
func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
// ListAllAlarms provides a mock function for the type Repository
func (_mock *Repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
ret := _mock.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for ListAlarms")
panic("no return value specified for ListAllAlarms")
}
var r0 alarms.AlarmsPage
@@ -191,19 +191,19 @@ func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata)
return r0, r1
}
// Repository_ListAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAlarms'
type Repository_ListAlarms_Call struct {
// Repository_ListAllAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAllAlarms'
type Repository_ListAllAlarms_Call struct {
*mock.Call
}
// ListAlarms is a helper method to define mock.On call
// ListAllAlarms is a helper method to define mock.On call
// - ctx context.Context
// - pm alarms.PageMetadata
func (_e *Repository_Expecter) ListAlarms(ctx interface{}, pm interface{}) *Repository_ListAlarms_Call {
return &Repository_ListAlarms_Call{Call: _e.mock.On("ListAlarms", ctx, pm)}
func (_e *Repository_Expecter) ListAllAlarms(ctx interface{}, pm interface{}) *Repository_ListAllAlarms_Call {
return &Repository_ListAllAlarms_Call{Call: _e.mock.On("ListAllAlarms", ctx, pm)}
}
func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAllAlarms_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -221,12 +221,84 @@ func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarm
return _c
}
func (_c *Repository_ListAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAllAlarms_Call {
_c.Call.Return(alarmsPage, err)
return _c
}
func (_c *Repository_ListAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAllAlarms_Call {
_c.Call.Return(run)
return _c
}
// ListUserAlarms provides a mock function for the type Repository
func (_mock *Repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
ret := _mock.Called(ctx, userID, pm)
if len(ret) == 0 {
panic("no return value specified for ListUserAlarms")
}
var r0 alarms.AlarmsPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) (alarms.AlarmsPage, error)); ok {
return returnFunc(ctx, userID, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) alarms.AlarmsPage); ok {
r0 = returnFunc(ctx, userID, pm)
} else {
r0 = ret.Get(0).(alarms.AlarmsPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, alarms.PageMetadata) error); ok {
r1 = returnFunc(ctx, userID, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ListUserAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListUserAlarms'
type Repository_ListUserAlarms_Call struct {
*mock.Call
}
// ListUserAlarms is a helper method to define mock.On call
// - ctx context.Context
// - userID string
// - pm alarms.PageMetadata
func (_e *Repository_Expecter) ListUserAlarms(ctx interface{}, userID interface{}, pm interface{}) *Repository_ListUserAlarms_Call {
return &Repository_ListUserAlarms_Call{Call: _e.mock.On("ListUserAlarms", ctx, userID, pm)}
}
func (_c *Repository_ListUserAlarms_Call) Run(run func(ctx context.Context, userID string, pm alarms.PageMetadata)) *Repository_ListUserAlarms_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 string
if args[1] != nil {
arg1 = args[1].(string)
}
var arg2 alarms.PageMetadata
if args[2] != nil {
arg2 = args[2].(alarms.PageMetadata)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
}
func (_c *Repository_ListUserAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListUserAlarms_Call {
_c.Call.Return(alarmsPage, err)
return _c
}
func (_c *Repository_ListUserAlarms_Call) RunAndReturn(run func(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListUserAlarms_Call {
_c.Call.Return(run)
return _c
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package alarms
import (
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
)
const (
OpAddAlarm = iota
OpViewAlarm
OpListAlarms
OpUpdateAlarm
OpDeleteAlarm
)
func GetPermission(op permissions.Operation) (string, error) {
if op < OpAddAlarm || op > OpDeleteAlarm {
return "", errors.New("invalid operation")
}
if op == OpUpdateAlarm || op == OpDeleteAlarm {
return policies.AdminPermission, nil
}
return policies.MembershipPermission, nil
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package operations
import "github.com/absmach/supermq/pkg/permissions"
const EntityType = "alarm"
// Alarm Operations.
const (
OpViewAlarm permissions.Operation = iota
OpDeleteAlarm
OpListAlarms
OpAssignAlarm
OpAcknowledgeAlarm
OpResolveAlarm
OpUpdateAlarm
)
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
return map[permissions.Operation]permissions.OperationDetails{
OpViewAlarm: {
Name: "view",
PermissionRequired: true,
},
OpDeleteAlarm: {
Name: "delete",
PermissionRequired: true,
},
OpListAlarms: {
Name: "list",
PermissionRequired: true,
},
OpAssignAlarm: {
Name: "assign",
PermissionRequired: true,
},
OpAcknowledgeAlarm: {
Name: "acknowledge",
PermissionRequired: true,
},
OpResolveAlarm: {
Name: "resolve",
PermissionRequired: true,
},
OpUpdateAlarm: {
Name: "update",
PermissionRequired: true,
},
}
}
+46 -26
View File
@@ -20,6 +20,10 @@ import (
"github.com/jmoiron/sqlx"
)
const alarmColumns = `alarms.id, alarms.rule_id, alarms.domain_id, alarms.channel_id, alarms.client_id, alarms.subtopic, alarms.measurement, alarms.value, alarms.unit,
alarms.threshold, alarms.cause, alarms.status, alarms.severity, alarms.assignee_id, alarms.created_at, alarms.updated_at, alarms.updated_by, alarms.assigned_at,
alarms.assigned_by, alarms.acknowledged_at, alarms.acknowledged_by, alarms.resolved_at, alarms.resolved_by, alarms.metadata`
type repository struct {
db *sqlx.DB
}
@@ -183,32 +187,49 @@ func (r *repository) ViewAlarm(ctx context.Context, alarmID, domainID string) (a
return alarm, nil
}
func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
func (r *repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
query, err := pageQuery(pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
comQuery := fmt.Sprintf(`SELECT %s FROM alarms %s`, alarmColumns, query)
return r.alarmsPage(ctx, comQuery, pm)
}
func (r *repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
query, err := pageQuery(pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
pm.UserID = userID
comQuery := fmt.Sprintf(`SELECT DISTINCT %s
FROM alarms
INNER JOIN rules_roles rr ON rr.entity_id = alarms.rule_id
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
%s`, alarmColumns, query)
return r.alarmsPage(ctx, comQuery, pm)
}
func (r *repository) alarmsPage(ctx context.Context, comQuery string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
dir := api.DescDir
if pm.Dir == api.AscDir {
dir = api.AscDir
}
orderClause := ""
var orderClause string
switch pm.Order {
case api.CreatedAtOrder:
orderClause = fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
case api.UpdatedAtOrder:
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
default:
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
}
q := fmt.Sprintf(`SELECT id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit,
threshold, cause, status, severity, assignee_id, created_at, updated_at, updated_by, assigned_at,
assigned_by, acknowledged_at, acknowledged_by, resolved_at, resolved_by, metadata
FROM alarms %s %s LIMIT :limit OFFSET :offset;`, query, orderClause)
q := fmt.Sprintf(`SELECT * FROM (%s) AS sub_query %s LIMIT :limit OFFSET :offset;`, comQuery, orderClause)
cq := fmt.Sprintf(`SELECT COUNT(*) AS total_count FROM (%s) AS sub_query;`, comQuery)
rows, err := r.db.NamedQueryContext(ctx, q, pm)
if err != nil {
@@ -231,8 +252,7 @@ func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (al
items = append(items, a)
}
q = fmt.Sprintf(`SELECT COUNT(*) FROM alarms %s;`, query)
total, err := postgres.Total(ctx, r.db, q, pm)
total, err := postgres.Total(ctx, r.db, cq, pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
@@ -444,49 +464,49 @@ func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
func pageQuery(pm alarms.PageMetadata) (string, error) {
var query []string
if pm.DomainID != "" {
query = append(query, "domain_id = :domain_id")
query = append(query, "alarms.domain_id = :domain_id")
}
if pm.RuleID != "" {
query = append(query, "rule_id = :rule_id")
query = append(query, "alarms.rule_id = :rule_id")
}
if pm.ChannelID != "" {
query = append(query, "channel_id = :channel_id")
query = append(query, "alarms.channel_id = :channel_id")
}
if pm.Subtopic != "" {
query = append(query, "subtopic = :subtopic")
query = append(query, "alarms.subtopic = :subtopic")
}
if pm.ClientID != "" {
query = append(query, "client_id = :client_id")
query = append(query, "alarms.client_id = :client_id")
}
if pm.Measurement != "" {
query = append(query, "measurement = :measurement")
query = append(query, "alarms.measurement = :measurement")
}
if pm.Status != alarms.AllStatus {
query = append(query, "status = :status")
query = append(query, "alarms.status = :status")
}
if pm.Severity != math.MaxUint8 {
query = append(query, "severity = :severity")
query = append(query, "alarms.severity = :severity")
}
if pm.AssigneeID != "" {
query = append(query, "assignee_id = :assignee_id")
query = append(query, "alarms.assignee_id = :assignee_id")
}
if pm.UpdatedBy != "" {
query = append(query, "updated_by = :updated_by")
query = append(query, "alarms.updated_by = :updated_by")
}
if pm.ResolvedBy != "" {
query = append(query, "resolved_by = :resolved_by")
query = append(query, "alarms.resolved_by = :resolved_by")
}
if pm.AcknowledgedBy != "" {
query = append(query, "acknowledged_by = :acknowledged_by")
query = append(query, "alarms.acknowledged_by = :acknowledged_by")
}
if pm.AssignedBy != "" {
query = append(query, "assigned_by = :assigned_by")
query = append(query, "alarms.assigned_by = :assigned_by")
}
if !pm.CreatedFrom.IsZero() {
query = append(query, "created_at >= :created_from")
query = append(query, "alarms.created_at >= :created_from")
}
if !pm.CreatedTo.IsZero() {
query = append(query, "created_at <= :created_to")
query = append(query, "alarms.created_at <= :created_to")
}
var emq string
+179 -1
View File
@@ -403,7 +403,7 @@ func TestListAlarms(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
alarms, err := repo.ListAlarms(context.Background(), tc.pm)
alarms, err := repo.ListAllAlarms(context.Background(), tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
@@ -415,6 +415,184 @@ func TestListAlarms(t *testing.T) {
}
}
func TestListUserAlarms(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM alarms")
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
_, err = db.Exec("DELETE FROM rules")
require.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
})
repo := postgres.NewAlarmsRepo(db)
domainID := generateUUID(t)
userID := generateUUID(t)
otherUserID := generateUUID(t)
adminUserID := generateUUID(t)
// Create 10 rules and 10 alarms referencing them.
// Assign userID to the first 6 rules via role membership.
var ruleIDs []string
var createdAlarms []alarms.Alarm
for i := range 10 {
ruleID := generateUUID(t)
_, err := db.Exec(`INSERT INTO rules (id, name, domain_id, status, logic_type, logic_value) VALUES ($1, $2, $3, 0, 0, '')`,
ruleID, fmt.Sprintf("rule-%d", i), domainID)
require.Nil(t, err, fmt.Sprintf("insert rule unexpected error: %s", err))
ruleIDs = append(ruleIDs, ruleID)
alarm := alarms.Alarm{
ID: generateUUID(t),
RuleID: ruleID,
DomainID: domainID,
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute),
}
alarm, err = repo.CreateAlarm(context.Background(), alarm)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
createdAlarms = append(createdAlarms, alarm)
}
// Assign userID to the first 6 rules via rules_roles + rules_role_members.
userRoleIDs := make([]string, 6)
for i := range 6 {
roleID := generateUUID(t)
userRoleIDs[i] = roleID
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
_, err = db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
}
for i := range 10 {
var roleID string
if i < 6 {
roleID = userRoleIDs[i]
} else {
roleID = generateUUID(t)
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
}
_, err := db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, adminUserID, ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
}
_ = createdAlarms
cases := []struct {
desc string
userID string
pm alarms.PageMetadata
count int
err error
}{
{
desc: "list user alarms returns only accessible alarms",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 6,
err: nil,
},
{
desc: "list user alarms with limit",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 3,
},
count: 3,
err: nil,
},
{
desc: "list user alarms with offset",
userID: userID,
pm: alarms.PageMetadata{
Offset: 4,
Limit: 100,
},
count: 2,
err: nil,
},
{
desc: "list user alarms with domain filter",
userID: userID,
pm: alarms.PageMetadata{
DomainID: domainID,
Offset: 0,
Limit: 100,
},
count: 6,
err: nil,
},
{
desc: "list user alarms with non-existing domain returns 0",
userID: userID,
pm: alarms.PageMetadata{
DomainID: generateUUID(t),
Offset: 0,
Limit: 100,
},
count: 0,
err: nil,
},
{
desc: "list alarms for user with no role assignments returns 0",
userID: otherUserID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 0,
err: nil,
},
{
desc: "list alarms for admin user with role on all rules returns all alarms",
userID: adminUserID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 10,
err: nil,
},
{
desc: "list user alarms ordered by created_at ascending",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
Order: "created_at",
Dir: "asc",
},
count: 6,
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
page, err := repo.ListUserAlarms(context.Background(), tc.userID, tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.Equal(t, tc.count, len(page.Alarms), fmt.Sprintf("%s: expected %d alarms, got %d", tc.desc, tc.count, len(page.Alarms)))
})
}
}
func TestDeleteAlarm(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM alarms")
+15 -3
View File
@@ -4,13 +4,16 @@
package postgres
import (
rpostgres "github.com/absmach/magistrala/re/postgres"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
// Migration of Users service.
func Migration() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
// Migration of Alarms service.
func Migration() (*migrate.MemoryMigrationSource, error) {
alarmsMigration := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "alarms_01",
@@ -50,4 +53,13 @@ func Migration() *migrate.MemoryMigrationSource {
},
},
}
rulesMigration, err := rpostgres.Migration()
if err != nil {
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
}
alarmsMigration.Migrations = append(alarmsMigration.Migrations, rulesMigration.Migrations...)
return alarmsMigration, nil
}
+5 -1
View File
@@ -75,7 +75,11 @@ func TestMain(m *testing.M) {
SSLRootCert: "",
}
if db, err = postgres.Setup(dbConfig, *apostgres.Migration()); err != nil {
migration, err := apostgres.Migration()
if err != nil {
log.Fatalf("Could not get migration: %s", err)
}
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
+5 -1
View File
@@ -43,6 +43,7 @@ func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) error {
if _, err = s.repo.CreateAlarm(ctx, alarm); err != nil && err != repoerr.ErrNotFound {
return err
}
return nil
}
@@ -51,7 +52,10 @@ func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID
}
func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) {
return s.repo.ListAlarms(ctx, pm)
if session.SuperAdmin {
return s.repo.ListAllAlarms(ctx, pm)
}
return s.repo.ListUserAlarms(ctx, session.UserID, pm)
}
func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error {
+11 -25
View File
@@ -6,7 +6,6 @@ package alarms_test
import (
"context"
"fmt"
"math"
"testing"
"time"
@@ -22,9 +21,13 @@ import (
var idp = uuid.New()
func newService(t *testing.T, repo *mocks.Repository) alarms.Service {
return alarms.NewService(idp, repo)
}
func TestCreateAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
ts := time.Now()
cases := []struct {
desc string
@@ -69,33 +72,16 @@ func TestCreateAlarm(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("CreateAlarm", context.Background(), mock.Anything).Return(tc.alarm, tc.err)
repoCall1 := repo.On("ListAlarms", context.Background(), alarms.PageMetadata{
Offset: 0, Limit: 1,
DomainID: tc.alarm.DomainID,
ChannelID: tc.alarm.ChannelID,
ClientID: tc.alarm.ClientID,
Subtopic: tc.alarm.Subtopic,
Measurement: tc.alarm.Measurement,
RuleID: tc.alarm.RuleID,
Status: alarms.AllStatus,
Severity: math.MaxUint8,
CreatedTo: tc.alarm.CreatedAt,
}).Return(alarms.AlarmsPage{}, tc.err)
err := svc.CreateAlarm(context.Background(), tc.alarm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
})
}
}
func TestViewAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -134,7 +120,7 @@ func TestViewAlarm(t *testing.T) {
func TestUpdateAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -192,7 +178,7 @@ func TestUpdateAlarm(t *testing.T) {
func TestListAlarms(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -219,7 +205,7 @@ func TestListAlarms(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
s := authn.Session{DomainID: tc.pm.DomainID}
repoCall := repo.On("ListAlarms", context.Background(), tc.pm).Return(tc.page, tc.err)
repoCall := repo.On("ListUserAlarms", context.Background(), s.UserID, tc.pm).Return(tc.page, tc.err)
_, err := svc.ListAlarms(context.Background(), s, tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
@@ -233,7 +219,7 @@ func TestListAlarms(t *testing.T) {
func TestDeleteAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
+1 -1
View File
@@ -13,7 +13,7 @@ info:
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:8050
+1 -1
View File
@@ -13,7 +13,7 @@ info:
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9013
+1 -1
View File
@@ -13,7 +13,7 @@ info:
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9014
+1 -1
View File
@@ -13,7 +13,7 @@ info:
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9003
+1 -1
View File
@@ -6,7 +6,7 @@ info:
title: Magistrala Reports Service API
description: |
HTTP API for managing reports service.
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9017
tags:
+1 -1
View File
@@ -13,7 +13,7 @@ info:
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9008
+2 -2
View File
@@ -127,7 +127,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
@@ -143,7 +143,7 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjTy
ObjectType: objType,
Object: obj,
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, nil); err != nil {
return err
}
return nil
+76 -7
View File
@@ -15,17 +15,23 @@ import (
"github.com/absmach/magistrala/alarms/brokers"
"github.com/absmach/magistrala/alarms/consumer"
"github.com/absmach/magistrala/alarms/middleware"
"github.com/absmach/magistrala/alarms/operations"
alarmsRepo "github.com/absmach/magistrala/alarms/postgres"
"github.com/absmach/magistrala/pkg/prometheus"
rconsumer "github.com/absmach/magistrala/pkg/re/events/consumer"
rpostgres "github.com/absmach/magistrala/re/postgres"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/authn/authsvc"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/messaging"
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
@@ -42,14 +48,18 @@ const (
defDB = "alarms"
defSvcHTTPPort = "8050"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
alarmEntity = "alarm"
)
type config struct {
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
ESConsumerName string `env:"MG_ALARMS_EVENT_CONSUMER" envDefault:"alarms"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -87,7 +97,14 @@ func main() {
logger.Error(err.Error())
}
db, err := postgres.Setup(dbConfig, *alarmsRepo.Migration())
migrations, err := alarmsRepo.Migration()
if err != nil {
logger.Error(fmt.Sprintf("failed to load migrations: %s", err))
exitCode = 1
return
}
db, err := postgres.Setup(dbConfig, *migrations)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -138,11 +155,63 @@ func main() {
logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure())
ddatabase := postgres.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
rdatabase := postgres.NewDatabase(db, dbConfig, tracer)
rrepo := rpostgres.NewRepository(rdatabase)
if err := rconsumer.RulesEventsSubscribe(ctx, rrepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to subscribe to rules events: %s", err))
exitCode = 1
return
}
idp := uuid.New()
svc := alarms.NewService(idp, repo)
svc = middleware.NewAuthorizationMiddleware(svc, authz)
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
logger.Error(fmt.Sprintf("failed to parse permissions file: %s", err))
exitCode = 1
return
}
alarmOps, _, err := permConfig.GetEntityPermissions(alarmEntity)
if err != nil {
logger.Error(fmt.Sprintf("failed to get alarm permissions: %s", err))
exitCode = 1
return
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: alarmOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
logger.Error(fmt.Sprintf("failed to create entity operations: %s", err))
exitCode = 1
return
}
svc, err = middleware.NewAuthorizationMiddleware(svc, authz, entitiesOps)
if err != nil {
logger.Error(fmt.Sprintf("failed to create authorization middleware: %s", err))
exitCode = 1
return
}
svc = middleware.NewLoggingMiddleware(logger, svc)
counter, latency := prometheus.MakeMetrics("alarms", "api")
svc = middleware.NewMetricsMiddleware(counter, latency, svc)
+30 -6
View File
@@ -17,11 +17,15 @@ import (
mgsdk "github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/provision"
httpapi "github.com/absmach/magistrala/provision/api"
"github.com/absmach/magistrala/provision/middleware"
"github.com/absmach/supermq"
"github.com/absmach/supermq/channels"
"github.com/absmach/supermq/clients"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
"github.com/absmach/supermq/pkg/uuid"
@@ -30,8 +34,9 @@ import (
)
const (
svcName = "provision"
contentType = "application/json"
svcName = "provision"
contentType = "application/json"
envPrefixAuth = "SMQ_AUTH_GRPC_"
)
var (
@@ -65,6 +70,24 @@ func main() {
}
}
grpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
exitCode = 1
return
}
authn, authnClient, err := authnsvc.NewAuthentication(ctx, grpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
am := smqauthn.NewAuthNMiddleware(authn)
if cfgFromFile, err := loadConfigFromFile(cfg.File); err != nil {
logger.Warn(fmt.Sprintf("Continue with settings from env, failed to load from: %s: %s", cfg.File, err))
} else {
@@ -76,9 +99,10 @@ func main() {
SDKCfg := mgsdk.Config{
UsersURL: cfg.Server.UsersURL,
ChannelsURL: cfg.Server.ChannelsURL,
ClientsURL: cfg.Server.ClientsURL,
BootstrapURL: cfg.Server.MgBSURL,
CertsURL: cfg.Server.MgCertsURL,
CertsURL: cfg.Server.CertsURL,
MsgContentType: contentType,
TLSVerification: cfg.Server.TLS,
}
@@ -91,10 +115,10 @@ func main() {
cSdk := csdk.NewSDK(csdkConf)
svc := provision.New(cfg, mgSdk, cSdk, logger)
svc = httpapi.NewLoggingMiddleware(svc, logger)
svc = middleware.NewLogging(svc, logger)
httpServerConfig := server.Config{Host: "", Port: cfg.Server.HTTPPort, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, logger, cfg.InstanceID), logger)
httpServerConfig := server.Config{Host: "", Port: cfg.Server.Port, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
+124 -15
View File
@@ -20,33 +20,47 @@ import (
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/ticker"
"github.com/absmach/magistrala/re"
httpapi "github.com/absmach/magistrala/re/api"
"github.com/absmach/magistrala/re/events"
"github.com/absmach/magistrala/re/middleware"
"github.com/absmach/magistrala/re/operations"
repg "github.com/absmach/magistrala/re/postgres"
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
"github.com/absmach/supermq"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
mgauthz "github.com/absmach/supermq/pkg/authz"
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
"github.com/absmach/supermq/pkg/callout"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/messaging"
smqbrokers "github.com/absmach/supermq/pkg/messaging/brokers"
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/policies/spicedb"
pgclient "github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/roles"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
"github.com/absmach/supermq/pkg/uuid"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/grpcutil"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
@@ -67,15 +81,21 @@ const (
const channBuffer = 256
type config struct {
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
InstanceID string `env:"MG_RE_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
InstanceID string `env:"MG_RE_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
ESConsumerName string `env:"MG_RE_EVENT_CONSUMER" envDefault:"rules_engine"`
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -126,7 +146,14 @@ func main() {
return
}
db, err := pgclient.Setup(dbConfig, *repg.Migration())
migration, err := repg.Migration()
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *migration)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -238,6 +265,16 @@ func main() {
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
database := pgclient.NewDatabase(db, dbConfig, tracer)
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
regrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&regrpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err))
@@ -255,7 +292,7 @@ func main() {
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
svc, err := newService(ctx, database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient, callout, cfg)
svc, err := newService(ctx, cfg, database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient, callout, tracer)
if err != nil {
logger.Error(fmt.Sprintf("failed to create services: %s", err))
exitCode = 1
@@ -307,7 +344,7 @@ func main() {
}
}
func newService(ctx context.Context, db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, callout callout.Callout, cfg config) (re.Service, error) {
func newService(ctx context.Context, cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, callout callout.Callout, tracer trace.Tracer) (re.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
@@ -316,21 +353,93 @@ func newService(ctx context.Context, db pgclient.Database, runInfo chan pkglog.R
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
}
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
if err != nil {
return nil, err
}
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
if err != nil {
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
}
csvc, err := re.NewService(repo, runInfo, policyService, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient, availableActions, builtInRoles)
if err != nil {
return nil, fmt.Errorf("failed to create RE service: %w", err)
}
csvc, err = events.NewEventStoreMiddleware(ctx, csvc, cfg.ESURL)
if err != nil {
return nil, fmt.Errorf("failed to init re event store middleware: %w", err)
}
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
}
ruleOps, ruleRoleOps, err := permConfig.GetEntityPermissions(operations.EntityType)
if err != nil {
return nil, fmt.Errorf("failed to get rule permissions: %w", err)
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: ruleOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
return nil, fmt.Errorf("failed to create entities operations: %w", err)
}
roleOps, err := permissions.NewOperations(roles.Operations(), ruleRoleOps)
if err != nil {
return nil, fmt.Errorf("failed to create role operations: %w", err)
}
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc, err = middleware.NewCallout(csvc, callout)
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc = middleware.LoggingMiddleware(csvc, logger)
counter, latency := prometheus.MakeMetrics("re", "api")
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
csvc = middleware.NewTracingMiddleware(tracer, csvc)
return csvc, nil
}
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
client, err := authzed.NewClientWithExperimentalAPIs(
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
)
if err != nil {
return nil, err
}
ps := spicedb.NewPolicyService(client, logger)
return ps, nil
}
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, operations.EntityType)
if err != nil {
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
}
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
re.BuiltInRoleAdmin: availableActions,
}
return availableActions, builtInRoles, err
}
+136 -6
View File
@@ -19,40 +19,57 @@ import (
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/ticker"
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
"github.com/absmach/magistrala/reports"
httpapi "github.com/absmach/magistrala/reports/api"
"github.com/absmach/magistrala/reports/middleware"
"github.com/absmach/magistrala/reports/operations"
repg "github.com/absmach/magistrala/reports/postgres"
"github.com/absmach/supermq"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
mgauthz "github.com/absmach/supermq/pkg/authz"
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
"github.com/absmach/supermq/pkg/callout"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/policies/spicedb"
pgclient "github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/roles"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
"github.com/absmach/supermq/pkg/uuid"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/grpcutil"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
svcName = "reports"
envPrefixDB = "MG_REPORTS_DB_"
envPrefixHTTP = "MG_REPORTS_HTTP_"
envPrefixCallout = "MG_REPORTS_CALLOUT_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "repo"
defSvcHTTPPort = "9017"
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
templatePath = "template/reports_default_template.html"
reportEntity = "report"
)
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
@@ -67,10 +84,16 @@ type config struct {
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
ESConsumerName string `env:"MG_REPORTS_EVENT_CONSUMER" envDefault:"reports"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
DefaultTemplatePath string `env:"MG_REPORTS_DEFAULT_TEMPLATE" envDefault:""`
ConverterURL string `env:"MG_PDF_CONVERTER_URL" envDefault:"http://localhost:4000/pdf"`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -131,6 +154,13 @@ func main() {
return
}
callCfg := callout.Config{}
if err := env.ParseWithOptions(&callCfg, env.Options{Prefix: envPrefixCallout}); err != nil {
logger.Error(fmt.Sprintf("failed to parse callout config : %s", err))
exitCode = 1
return
}
dbConfig := pgclient.Config{Name: defDB}
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(err.Error())
@@ -139,7 +169,15 @@ func main() {
return
}
db, err := pgclient.Setup(dbConfig, *repg.Migration())
migration, err := repg.Migration()
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *migration)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -170,6 +208,13 @@ func main() {
return
}
callout, err := callout.New(callCfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
exitCode = 1
return
}
grpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
@@ -211,6 +256,15 @@ func main() {
defer authzClient.Close()
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
database := pgclient.NewDatabase(db, dbConfig, tracer)
regrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&regrpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
@@ -231,7 +285,7 @@ func main() {
runInfo := make(chan pkglog.RunInfo, channBuffer)
svc, err := newService(database, runInfo, authz, ec, logger, readersClient, template, cfg.ConverterURL)
svc, err := newService(cfg, database, runInfo, authz, ec, logger, readersClient, template, callout, tracer)
if err != nil {
logger.Error(fmt.Sprintf("failed to create services: %s", err))
exitCode = 1
@@ -271,21 +325,97 @@ func main() {
}
}
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, converterURL string) (reports.Service, error) {
func newService(cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, callout callout.Callout, tracer trace.Tracer) (reports.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
emailerClient, err := emailer.New(&ec)
emailClient, err := emailer.New(&ec)
if err != nil {
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
}
csvc := reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailerClient, readersClient, template, converterURL)
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
if err != nil {
return nil, err
}
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
if err != nil {
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
}
csvc, err := reports.NewService(repo, runInfo, policyService, idp, ticker.NewTicker(time.Second*30), emailClient, readersClient, template, cfg.ConverterURL, availableActions, builtInRoles)
if err != nil {
return nil, fmt.Errorf("failed to create reports service: %w", err)
}
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
}
reportOps, reportRoleOps, err := permConfig.GetEntityPermissions(reportEntity)
if err != nil {
return nil, fmt.Errorf("failed to get report permissions: %w", err)
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: reportOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
return nil, fmt.Errorf("failed to create entities operations: %w", err)
}
roleOps, err := permissions.NewOperations(roles.Operations(), reportRoleOps)
if err != nil {
return nil, fmt.Errorf("failed to create role operations: %w", err)
}
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc = middleware.LoggingMiddleware(csvc, logger)
counter, latency := prometheus.MakeMetrics("reports", "api")
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
csvc = middleware.NewTracingMiddleware(tracer, csvc)
return csvc, nil
}
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
client, err := authzed.NewClientWithExperimentalAPIs(
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
)
if err != nil {
return nil, err
}
ps := spicedb.NewPolicyService(client, logger)
return ps, nil
}
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, reportEntity)
if err != nil {
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
}
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
reports.BuiltInRoleAdmin: availableActions,
}
return availableActions, builtInRoles, err
}
+30 -6
View File
@@ -52,6 +52,8 @@ SMQ_AUTH_GRPC_TIMEOUT=300s
SMQ_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
SMQ_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key}
SMQ_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
SMQ_AUTH_ACCESS_TOKEN_DURATION=1h
SMQ_AUTH_REFRESH_TOKEN_DURATION=24h
#### Clients Client Config
SMQ_CLIENTS_URL=http://clients:9006
@@ -85,11 +87,14 @@ SMQ_SPICEDB_DB_PORT=5432
### SpiceDB config
SMQ_SPICEDB_PRE_SHARED_KEY="12345678"
SMQ_SPICEDB_SCHEMA_FILE="/schema.zed"
SMQ_SPICEDB_SCHEMA_FILE="/schemas/combined-schema.zed"
SMQ_SPICEDB_HOST=supermq-spicedb
SMQ_SPICEDB_PORT=50051
SMQ_SPICEDB_DATASTORE_ENGINE=postgres
### Permissions
SMQ_PERMISSIONS_FILE=/schemas/permission.yaml
### UI
SMQ_UI_PATH_PREFIX=/ui
@@ -145,6 +150,7 @@ MG_ALARMS_DB_SSL_CERT=
MG_ALARMS_DB_SSL_KEY=
MG_ALARMS_DB_SSL_ROOT_CERT=
MG_ALARMS_INSTANCE_ID=
MG_ALARMS_EVENT_CONSUMER=alarms
### REPORTS
MG_REPORTS_LOG_LEVEL=debug
@@ -286,13 +292,14 @@ MG_PROVISION_HTTP_PORT=9016
MG_PROVISION_ENV_CLIENTS_TLS=false
MG_PROVISION_SERVER_CERT=
MG_PROVISION_SERVER_KEY=
MG_PROVISION_USERS_LOCATION=http://users:9002
MG_PROVISION_CLIENTS_LOCATION=http://clients:9006
MG_PROVISION_USERS_URL=http://users:9002
MG_PROVISION_CHANNELS_URL=http://channels:9005
MG_PROVISION_CLIENTS_URL=http://clients:9006
MG_PROVISION_CERTS_URL=http://certs:9019
MG_PROVISION_USER=
MG_PROVISION_USERNAME=
MG_PROVISION_PASS=
MG_PROVISION_API_KEY=
MG_PROVISION_CERTS_SVC_URL=http://certs:9019
MG_PROVISION_X509_PROVISIONING=false
MG_PROVISION_BS_SVC_URL=http://bootstrap:9013
MG_PROVISION_BS_CONFIG_PROVISIONING=true
@@ -407,10 +414,12 @@ MG_BACKEND_OBJECT_STORAGE_REGION=fra1
MG_BACKEND_OBJECT_STORAGE_BUCKET=mg-ui-images
MG_BACKEND_OBJECT_STORAGE_ENDPOINT=http://seaweedfs-s3:8333
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE=true
MG_BACKEND_OBJECT_STORAGE_REWRITE_URL=localhost
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT=http://localhost:8333
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY=localKey
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY=localSecret
MG_BACKEND_OBJECT_STORAGE_TTL=1h
MG_BACKEND_OBJECT_STORAGE_WRITE_TTL=1m
MG_BACKEND_OBJECT_STORAGE_READ_TTL=15m
MG_BACKEND_OBJECT_STORAGE_TTL=15m
#### Auth GRPC Client Config
MG_AUTH_GRPC_URL=auth:7001
@@ -466,6 +475,21 @@ MG_UI_BASEURL=http://localhost:3000
MG_SUPPORT_EMAIL=
MG_SUPPORT_EMAIL_PASS=
## SMTP Variables
MG_UI_SMTP_HOST=host.docker.internal
MG_UI_SMTP_PORT=2525
MG_UI_SMTP_SECURE=
MG_UI_SUPPORT_FROM=from@example.com
# Message cli variables
MG_UI_CLI_MQTT_HOST=localhost
MG_UI_CLI_MQTT_PORT=8883
MG_UI_CLI_WS_URL=ws://localhost:8186
MG_UI_CLI_COAP_HOST=0.0.0.0
MG_UI_CLI_COAP_PORT=5684
MG_UI_CLI_HTTP_URL=http://localhost:8008
MG_UI_CLI_CA_FILE=/etc/ssl/certs/ca-certificates.crt
# Docker image tag
MG_RELEASE_TAG=latest
+1 -1
View File
@@ -1,7 +1,7 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.26rc2-alpine3.22 AS builder
FROM golang:1.26-alpine3.22 AS builder
ARG SVC
ARG GOARCH
ARG GOARM
@@ -8,6 +8,7 @@
networks:
magistrala-base-net:
driver: bridge
volumes:
magistrala-bootstrap-db-volume:
+3 -3
View File
@@ -55,10 +55,10 @@
type = "plain"
workers = 10
[[things]]
name = "thing"
[[clients]]
name = "client"
[things.metadata]
[clients.metadata]
external_id = "xxxxxx"
[[channels]]
+11 -3
View File
@@ -8,6 +8,7 @@
networks:
magistrala-base-net:
driver: bridge
services:
provision:
@@ -25,13 +26,14 @@ services:
MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS}
MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT}
MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY}
MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION}
MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION}
MG_PROVISION_USERS_URL: ${MG_PROVISION_USERS_URL}
MG_PROVISION_CHANNELS_URL: ${MG_PROVISION_CHANNELS_URL}
MG_PROVISION_CLIENTS_URL: ${MG_PROVISION_CLIENTS_URL}
MG_PROVISION_USER: ${MG_PROVISION_USER}
MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME}
MG_PROVISION_PASS: ${MG_PROVISION_PASS}
MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY}
MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL}
MG_PROVISION_CERTS_URL: ${MG_PROVISION_CERTS_URL}
MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING}
MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL}
MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING}
@@ -40,6 +42,12 @@ services:
MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID}
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT}
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
volumes:
- ./configs:/configs
- ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
+18
View File
@@ -0,0 +1,18 @@
{
"identities": [
{
"name": "magistrala",
"credentials": [
{
"accessKey": "localKey",
"secretKey": "localSecret"
}
],
"actions": [
"Admin",
"Read",
"Write"
]
}
]
}
+82 -40
View File
@@ -46,7 +46,6 @@ services:
MG_READER_URL: ${MG_READER_URL}
MG_BACKEND_URL: ${MG_UI_BACKEND_URL}
MG_JOURNAL_URL: ${MG_JOURNAL_URL}
MG_BILLING_URL: ${MG_BILLING_URL}
MG_ALARMS_URL: ${MG_ALARMS_URL}
MG_RE_URL: ${MG_RE_URL}
MG_REPORTS_URL: ${MG_REPORTS_URL}
@@ -66,6 +65,20 @@ services:
MG_UI_DOCKER_ACCEPT_EULA: ${MG_UI_DOCKER_ACCEPT_EULA}
MG_SUPPORT_EMAIL: ${MG_SUPPORT_EMAIL}
MG_SUPPORT_EMAIL_PASS: ${MG_SUPPORT_EMAIL_PASS}
MG_UI_CLI_MQTT_HOST: ${MG_UI_CLI_MQTT_HOST}
MG_UI_CLI_WS_URL: ${MG_UI_CLI_WS_URL}
MG_UI_CLI_COAP_HOST: ${MG_UI_CLI_COAP_HOST}
MG_UI_CLI_COAP_PORT: ${MG_UI_CLI_COAP_PORT}
MG_UI_CLI_HTTP_URL: ${MG_UI_CLI_HTTP_URL}
MG_UI_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
MG_ACCESS_TOKEN_EXPIRY: ${SMQ_AUTH_ACCESS_TOKEN_DURATION}
MG_REFRESH_TOKEN_EXPIRY: ${SMQ_AUTH_REFRESH_TOKEN_DURATION}
MG_UI_SMTP_HOST: ${MG_UI_SMTP_HOST}
MG_UI_SMTP_PORT: ${MG_UI_SMTP_PORT}
MG_UI_SMTP_SECURE: ${MG_UI_SMTP_SECURE}
MG_UI_SUPPORT_FROM: ${MG_UI_SUPPORT_FROM}
ui-backend:
image: ghcr.io/absmach/magistrala/ui-backend:latest
@@ -114,10 +127,11 @@ services:
MG_BACKEND_OBJECT_STORAGE_BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
MG_BACKEND_OBJECT_STORAGE_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_ENDPOINT}
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE: ${MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE}
MG_BACKEND_OBJECT_STORAGE_REWRITE_URL: ${MG_BACKEND_OBJECT_STORAGE_REWRITE_URL}
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT}
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
MG_BACKEND_OBJECT_STORAGE_TTL: ${MG_BACKEND_OBJECT_STORAGE_TTL}
MG_BACKEND_OBJECT_STORAGE_READ_TTL: ${MG_BACKEND_OBJECT_STORAGE_READ_TTL}
depends_on:
ui-backend-db:
condition: service_healthy
@@ -127,49 +141,49 @@ services:
# Auth gRPC client certificates
- type: bind
source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Reader gRPC client certificates
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:+.crt}
target: /readers-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:+.key}
target: /readers-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /readers-grpc-server-ca${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:+.crt}
target: /readers-grpc-server-ca.crt
bind:
create_host_path: true
@@ -195,9 +209,9 @@ services:
timeout: 3s
retries: 60
seaweedfs-s3:
image: chrislusf/seaweedfs:latest
image: chrislusf/seaweedfs:4.16
container_name: magistrala-seaweedfs-s3
command: server -s3 -dir=/data
command: server -s3 -s3.config=/etc/seaweedfs/s3.json -dir=/data
ports:
- "8333:8333" # S3 endpoint
- "9333:9333" # master UI
@@ -205,32 +219,39 @@ services:
- "8888:8888" # filer UI
volumes:
- ./data/seaweedfs:/data
- ./configs/seaweedfs-s3.json:/etc/seaweedfs/s3.json:ro
networks:
- magistrala-base-net
seaweedfs-init:
image: curlimages/curl
image: amazon/aws-cli
container_name: magistrala-seaweedfs-init
entrypoint: /bin/sh
depends_on:
- seaweedfs-s3
command: >
/bin/sh -c "
echo \"Creating bucket $${BUCKET}, wait for 25s...\";
sleep 25;
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' -f -X PUT http://seaweedfs-s3:8333/$${BUCKET});
if [ $${HTTP_CODE} = '200' ] || [ $${HTTP_CODE} = '201' ]; then
echo \"[INIT] Bucket $${BUCKET} created successfully!\";
curl -s http://seaweedfs-s3:8333/ | grep -o '<Name>[^<]*</Name>';
exit 0;
else
echo \"[INIT] Failed to create bucket $${BUCKET}! HTTP code $${HTTP_CODE}.\" >&2;
exit 1;
fi
"
command:
- -c
- |
echo "[INIT] Waiting 20s for SeaweedFS S3 to be ready...";
sleep 20;
OUT=$(aws --endpoint-url http://seaweedfs-s3:8333 s3api create-bucket --bucket $${BUCKET} 2>&1);
EXIT=$$?;
if [ $$EXIT -eq 0 ]; then
echo "[INIT] Bucket $${BUCKET} created successfully.";
elif echo "$$OUT" | grep -q 'BucketAlreadyOwnedByYou\|BucketAlreadyExists'; then
echo "[INIT] Bucket $${BUCKET} already exists, skipping.";
else
echo "[INIT] Failed to create bucket $${BUCKET}: $$OUT" >&2;
exit 1;
fi
networks:
- magistrala-base-net
environment:
BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
AWS_ACCESS_KEY_ID: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
AWS_SECRET_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
AWS_DEFAULT_REGION: ${MG_BACKEND_OBJECT_STORAGE_REGION}
AWS_EC2_METADATA_DISABLED: "true"
re-db:
image: docker.io/postgres:18.0-alpine3.22
@@ -253,6 +274,7 @@ services:
container_name: magistrala-re
depends_on:
- re-db
- spicedb-migrate
restart: on-failure
environment:
MG_RE_LOG_LEVEL: ${MG_RE_LOG_LEVEL}
@@ -290,6 +312,8 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_RE_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
@@ -314,21 +338,23 @@ services:
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
- ./templates/${MG_RE_EMAIL_TEMPLATE}:/email.tmpl
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
@@ -353,6 +379,7 @@ services:
container_name: magistrala-alarms
depends_on:
- alarms-db
- spicedb-migrate
restart: on-failure
environment:
MG_ALARMS_LOG_LEVEL: ${MG_ALARMS_LOG_LEVEL}
@@ -370,6 +397,7 @@ services:
MG_ALARMS_DB_SSL_KEY: ${MG_ALARMS_DB_SSL_KEY}
MG_ALARMS_DB_SSL_ROOT_CERT: ${MG_ALARMS_DB_SSL_ROOT_CERT}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
@@ -382,42 +410,50 @@ services:
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_ALARMS_INSTANCE_ID: ${MG_ALARMS_INSTANCE_ID}
MG_ALARMS_EVENT_CONSUMER: ${MG_ALARMS_EVENT_CONSUMER}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
ports:
- ${MG_ALARMS_HTTP_PORT}:${MG_ALARMS_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -442,6 +478,7 @@ services:
container_name: magistrala-reports
depends_on:
- reports-db
- spicedb-migrate
restart: on-failure
environment:
MG_REPORTS_LOG_LEVEL: ${MG_REPORTS_LOG_LEVEL}
@@ -461,6 +498,7 @@ services:
MG_REPORTS_DEFAULT_TEMPLATE: ${MG_REPORTS_DEFAULT_TEMPLATE}
MG_PDF_CONVERTER_URL: ${MG_PDF_CONVERTER_URL}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
@@ -472,6 +510,8 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_REPORTS_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
@@ -496,21 +536,23 @@ services:
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
- ./templates/${MG_REPORTS_EMAIL_TEMPLATE}:/email.tmpl
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
+74
View File
@@ -0,0 +1,74 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
alarm:
operations:
- list: alarm_read_permission
- view: alarm_read_permission
- update: alarm_update_permission
- delete: alarm_delete_permission
- assign: alarm_assign_permission
- acknowledge: alarm_acknowledge_permission
- resolve: alarm_resolve_permission
rule:
operations:
- add: rule_create_permission
- list: rule_read_permission
- view: read_permission
- update: update_permission
- update_tags: update_permission
- update_schedule: update_permission
- enable: update_permission
- disable: update_permission
- delete: delete_permission
- alarm_assign: alarm_assign_permission
- alarm_acknowledge: alarm_acknowledge_permission
- alarm_resolve: alarm_resolve_permission
roles_operations:
- add: manage_role_permission
- remove: manage_role_permission
- update: manage_role_permission
- retrieve: view_role_users_permission
- retrieve_all: view_role_users_permission
- add_actions: manage_role_permission
- list_actions: view_role_users_permission
- check_actions_exists: view_role_users_permission
- remove_actions: manage_role_permission
- remove_all_actions: manage_role_permission
- add_members: add_role_users_permission
- list_members: view_role_users_permission
- check_members_exists: view_role_users_permission
- remove_members: remove_role_users_permission
- remove_all_members: remove_role_users_permission
report:
operations:
- add: report_create_permission
- list: report_read_permission
- generate: report_read_permission
- view: read_permission
- update: update_permission
- update_schedule: update_permission
- enable: update_permission
- disable: update_permission
- delete: delete_permission
- update_template: update_permission
- view_template: read_permission
- delete_template: delete_permission
roles_operations:
- add: manage_role_permission
- remove: manage_role_permission
- update: manage_role_permission
- retrieve: view_role_users_permission
- retrieve_all: view_role_users_permission
- add_actions: manage_role_permission
- list_actions: view_role_users_permission
- check_actions_exists: view_role_users_permission
- remove_actions: manage_role_permission
- remove_all_actions: manage_role_permission
- add_members: add_role_users_permission
- list_members: view_role_users_permission
- check_members_exists: view_role_users_permission
- remove_members: remove_role_users_permission
- remove_all_members: remove_role_users_permission
+688
View File
@@ -0,0 +1,688 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by scripts/combine-schema.sh. DO NOT EDIT.
//
// Combined from:
// - docker/supermq-docker/spicedb/schema.zed
// - docker/spicedb/override-schema.zed
definition user {}
definition role {
relation entity: domain | group | channel | client
relation member: user
relation built_in_role: domain | group | channel | client
permission delete = entity->manage_role_permission - built_in_role->manage_role_permission
permission update = entity->manage_role_permission - built_in_role->manage_role_permission
permission read = entity->manage_role_permission - built_in_role->manage_role_permission
permission add_user = entity->add_role_users_permission
permission remove_user = entity->remove_role_users_permission
permission view_user = entity->view_role_users_permission
}
definition client {
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain
relation parent_group: group
relation update: role#member
relation read: role#member
relation delete: role#member
relation set_parent_group: role#member
relation connect_to_channel: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
permission update_permission = update + parent_group->client_update_permission + domain->client_update_permission
permission read_permission = read + parent_group->client_read_permission + domain->client_read_permission
permission delete_permission = delete + parent_group->client_delete_permission + domain->client_delete_permission
permission set_parent_group_permission = set_parent_group + parent_group->client_set_parent_group_permission + domain->client_set_parent_group_permission
permission connect_to_channel_permission = connect_to_channel + parent_group->client_connect_to_channel_permission + domain->client_connect_to_channel_permission
permission manage_role_permission = manage_role + parent_group->client_manage_role_permission + domain->client_manage_role_permission
permission add_role_users_permission = add_role_users + parent_group->client_add_role_users_permission + domain->client_add_role_users_permission
permission remove_role_users_permission = remove_role_users + parent_group->client_remove_role_users_permission + domain->client_remove_role_users_permission
permission view_role_users_permission = view_role_users + parent_group->client_view_role_users_permission + domain->client_view_role_users_permission
}
definition channel {
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain
relation parent_group: group
relation update: role#member
relation read: role#member
relation delete: role#member
relation set_parent_group: role#member
relation connect_to_client: role#member
relation publish: role#member | client
relation subscribe: role#member | client
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
permission update_permission = update + parent_group->channel_update_permission + domain->channel_update_permission
permission read_permission = read + parent_group->channel_read_permission + domain->channel_read_permission
permission delete_permission = delete + parent_group->channel_delete_permission + domain->channel_delete_permission
permission set_parent_group_permission = set_parent_group + parent_group->channel_set_parent_group_permission + domain->channel_set_parent_group_permission
permission connect_to_client_permission = connect_to_client + parent_group->channel_connect_to_client_permission + domain->channel_connect_to_client_permission
permission publish_permission = publish + parent_group->channel_publish_permission + domain->channel_publish_permission
permission subscribe_permission = subscribe + parent_group->channel_subscribe_permission + domain->channel_subscribe_permission
permission manage_role_permission = manage_role + parent_group->channel_manage_role_permission + domain->channel_manage_role_permission
permission add_role_users_permission = add_role_users + parent_group->channel_add_role_users_permission + domain->channel_add_role_users_permission
permission remove_role_users_permission = remove_role_users + parent_group->channel_remove_role_users_permission + domain->channel_remove_role_users_permission
permission view_role_users_permission = view_role_users + parent_group->channel_view_role_users_permission + domain->channel_view_role_users_permission
}
definition group {
relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it is safe to add domain
relation parent_group: group
relation update: role#member
relation read: role#member
relation membership: role#member
relation delete: role#member
relation set_child: role#member
relation set_parent: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
relation client_create: role#member
relation channel_create: role#member
// this allows to add parent for group during the new group creation
relation subgroup_create: role#member
relation subgroup_client_create: role#member
relation subgroup_channel_create: role#member
relation client_update: role#member
relation client_read: role#member
relation client_delete: role#member
relation client_set_parent_group: role#member
relation client_connect_to_channel: role#member
relation client_manage_role: role#member
relation client_add_role_users: role#member
relation client_remove_role_users: role#member
relation client_view_role_users: role#member
relation channel_update: role#member
relation channel_read: role#member
relation channel_delete: role#member
relation channel_set_parent_group: role#member
relation channel_connect_to_client: role#member
relation channel_publish: role#member
relation channel_subscribe: role#member
relation channel_manage_role: role#member
relation channel_add_role_users: role#member
relation channel_remove_role_users: role#member
relation channel_view_role_users: role#member
relation subgroup_update: role#member
relation subgroup_read: role#member
relation subgroup_membership: role#member
relation subgroup_delete: role#member
relation subgroup_set_child: role#member
relation subgroup_set_parent: role#member
relation subgroup_manage_role: role#member
relation subgroup_add_role_users: role#member
relation subgroup_remove_role_users: role#member
relation subgroup_view_role_users: role#member
relation subgroup_client_update: role#member
relation subgroup_client_read: role#member
relation subgroup_client_delete: role#member
relation subgroup_client_set_parent_group: role#member
relation subgroup_client_connect_to_channel: role#member
relation subgroup_client_manage_role: role#member
relation subgroup_client_add_role_users: role#member
relation subgroup_client_remove_role_users: role#member
relation subgroup_client_view_role_users: role#member
relation subgroup_channel_update: role#member
relation subgroup_channel_read: role#member
relation subgroup_channel_delete: role#member
relation subgroup_channel_set_parent_group: role#member
relation subgroup_channel_connect_to_client: role#member
relation subgroup_channel_publish: role#member
relation subgroup_channel_subscribe: role#member
relation subgroup_channel_manage_role: role#member
relation subgroup_channel_add_role_users: role#member
relation subgroup_channel_remove_role_users: role#member
relation subgroup_channel_view_role_users: role#member
// Subgroup permission
permission subgroup_create_permission = subgroup_create + parent_group->subgroup_create_permission
permission subgroup_client_create_permission = subgroup_client_create + parent_group->subgroup_client_create_permission
permission subgroup_channel_create_permission = subgroup_channel_create + parent_group->subgroup_channel_create_permission
permission subgroup_update_permission = subgroup_update + parent_group->subgroup_update_permission
permission subgroup_membership_permission = subgroup_membership + parent_group->subgroup_membership_permission
permission subgroup_read_permission = subgroup_read + parent_group->subgroup_read_permission
permission subgroup_delete_permission = subgroup_delete + parent_group->subgroup_delete_permission
permission subgroup_set_child_permission = subgroup_set_child + parent_group->subgroup_set_child_permission
permission subgroup_set_parent_permission = subgroup_set_parent + parent_group->subgroup_set_parent_permission
permission subgroup_manage_role_permission = subgroup_manage_role + parent_group->subgroup_manage_role_permission
permission subgroup_add_role_users_permission = subgroup_add_role_users + parent_group->subgroup_add_role_users_permission
permission subgroup_remove_role_users_permission = subgroup_remove_role_users + parent_group->subgroup_remove_role_users_permission
permission subgroup_view_role_users_permission = subgroup_view_role_users + parent_group->subgroup_view_role_users_permission
// Group permission
permission update_permission = update + parent_group->subgroup_create_permission + domain->group_update_permission
permission membership_permission = membership + parent_group->subgroup_membership_permission + domain->group_membership_permission
permission read_permission = read + parent_group->subgroup_read_permission + domain->group_read_permission
permission delete_permission = delete + parent_group->subgroup_delete_permission + domain->group_delete_permission
permission set_child_permission = set_child + parent_group->subgroup_set_child_permission + domain->group_set_child_permission
permission set_parent_permission = set_parent + parent_group->subgroup_set_parent_permission + domain->group_set_parent_permission
permission manage_role_permission = manage_role + parent_group->subgroup_manage_role_permission + domain->group_manage_role_permission
permission add_role_users_permission = add_role_users + parent_group->subgroup_add_role_users_permission + domain->group_add_role_users_permission
permission remove_role_users_permission = remove_role_users + parent_group->subgroup_remove_role_users_permission + domain->group_remove_role_users_permission
permission view_role_users_permission = view_role_users + parent_group->subgroup_view_role_users_permission + domain->group_view_role_users_permission
// Subgroup clients permission
permission subgroup_client_update_permission = subgroup_client_update + parent_group->subgroup_client_update_permission
permission subgroup_client_read_permission = subgroup_client_read + parent_group->subgroup_client_read_permission
permission subgroup_client_delete_permission = subgroup_client_delete + parent_group->subgroup_client_delete_permission
permission subgroup_client_set_parent_group_permission = subgroup_client_set_parent_group + parent_group->subgroup_client_set_parent_group_permission
permission subgroup_client_connect_to_channel_permission = subgroup_client_connect_to_channel + parent_group->subgroup_client_connect_to_channel_permission
permission subgroup_client_manage_role_permission = subgroup_client_manage_role + parent_group->subgroup_client_manage_role_permission
permission subgroup_client_add_role_users_permission = subgroup_client_add_role_users + parent_group->subgroup_client_add_role_users_permission
permission subgroup_client_remove_role_users_permission = subgroup_client_remove_role_users + parent_group->subgroup_client_remove_role_users_permission
permission subgroup_client_view_role_users_permission = subgroup_client_view_role_users + parent_group->subgroup_client_view_role_users_permission
// Group clients permission
permission client_create_permission = client_create + parent_group->subgroup_client_create_permission + domain->client_create_permission
permission client_update_permission = client_update + parent_group->subgroup_client_update_permission + domain->client_update_permission
permission client_read_permission = client_read + parent_group->subgroup_client_read_permission + domain->client_read_permission
permission client_delete_permission = client_delete + parent_group->subgroup_client_delete_permission + domain->client_delete_permission
permission client_set_parent_group_permission = client_set_parent_group + parent_group->subgroup_client_set_parent_group_permission + domain->client_set_parent_group_permission
permission client_connect_to_channel_permission = client_connect_to_channel + parent_group->subgroup_client_connect_to_channel_permission + domain->client_connect_to_channel_permission
permission client_manage_role_permission = client_manage_role + parent_group->subgroup_client_manage_role_permission + domain->client_manage_role_permission
permission client_add_role_users_permission = client_add_role_users + parent_group->subgroup_client_add_role_users_permission + domain->client_add_role_users_permission
permission client_remove_role_users_permission = client_remove_role_users + parent_group->subgroup_client_remove_role_users_permission + domain->client_remove_role_users_permission
permission client_view_role_users_permission = client_view_role_users + parent_group->subgroup_client_view_role_users_permission + domain->client_view_role_users_permission
// Subgroup channels permission
permission subgroup_channel_update_permission = subgroup_channel_update + parent_group->subgroup_channel_update_permission
permission subgroup_channel_read_permission = subgroup_channel_read + parent_group->subgroup_channel_read_permission
permission subgroup_channel_delete_permission = subgroup_channel_delete + parent_group->subgroup_channel_delete_permission
permission subgroup_channel_set_parent_group_permission = subgroup_channel_set_parent_group + parent_group->subgroup_channel_set_parent_group_permission
permission subgroup_channel_connect_to_client_permission = subgroup_channel_connect_to_client + parent_group->subgroup_channel_connect_to_client_permission
permission subgroup_channel_publish_permission = subgroup_channel_publish + parent_group->subgroup_channel_publish_permission
permission subgroup_channel_subscribe_permission = subgroup_channel_subscribe + parent_group->subgroup_channel_subscribe_permission
permission subgroup_channel_manage_role_permission = subgroup_channel_manage_role + parent_group->subgroup_channel_manage_role_permission
permission subgroup_channel_add_role_users_permission = subgroup_channel_add_role_users + parent_group->subgroup_channel_add_role_users_permission
permission subgroup_channel_remove_role_users_permission = subgroup_channel_remove_role_users + parent_group->subgroup_channel_remove_role_users_permission
permission subgroup_channel_view_role_users_permission = subgroup_channel_view_role_users + parent_group->subgroup_channel_view_role_users_permission
// Group channels permission
permission channel_create_permission = channel_create + parent_group->subgroup_channel_create_permission + domain->channel_create_permission
permission channel_update_permission = channel_update + parent_group->subgroup_channel_update_permission + domain->channel_update_permission
permission channel_read_permission = channel_read + parent_group->subgroup_channel_read_permission + domain->channel_read_permission
permission channel_delete_permission = channel_delete + parent_group->subgroup_channel_delete_permission + domain->channel_delete_permission
permission channel_set_parent_group_permission = channel_set_parent_group + parent_group->subgroup_channel_set_parent_group_permission + domain->channel_set_parent_group_permission
permission channel_connect_to_client_permission = channel_connect_to_client + parent_group->subgroup_channel_connect_to_client_permission + domain->channel_connect_to_client_permission
permission channel_publish_permission = channel_publish + parent_group->subgroup_channel_publish_permission + domain->channel_publish_permission
permission channel_subscribe_permission = channel_subscribe + parent_group->subgroup_channel_subscribe_permission + domain->channel_subscribe_permission
permission channel_manage_role_permission = channel_manage_role + parent_group->channel_manage_role_permission + domain->channel_manage_role_permission
permission channel_add_role_users_permission = channel_add_role_users + parent_group->channel_add_role_users_permission + domain->channel_add_role_users_permission
permission channel_remove_role_users_permission = channel_remove_role_users + parent_group->channel_remove_role_users_permission + domain->channel_remove_role_users_permission
permission channel_view_role_users_permission = channel_view_role_users + parent_group->channel_view_role_users_permission + domain->channel_view_role_users_permission
}
definition domain {
//Replace platform with organization in future
relation organization: platform
relation team: team
relation update: role#member | team#member
relation enable: role#member | team#member
relation disable: role#member | team#member
relation read: role#member | team#member
relation delete: role#member | team#member
relation manage_role: role#member | team#member
relation add_role_users: role#member | team#member
relation remove_role_users: role#member | team#member
relation view_role_users: role#member | team#member
relation client_create: role#member | team#member
relation channel_create: role#member | team#member
relation group_create: role#member | team#member
relation client_update: role#member | team#member
relation client_read: role#member | team#member
relation client_delete: role#member | team#member
relation client_set_parent_group: role#member | team#member
relation client_connect_to_channel: role#member | team#member
relation client_manage_role: role#member | team#member
relation client_add_role_users: role#member | team#member
relation client_remove_role_users: role#member | team#member
relation client_view_role_users: role#member | team#member
relation channel_update: role#member | team#member
relation channel_read: role#member | team#member
relation channel_delete: role#member | team#member
relation channel_set_parent_group: role#member | team#member
relation channel_connect_to_client: role#member | team#member
relation channel_publish: role#member | team#member
relation channel_subscribe: role#member | team#member
relation channel_manage_role: role#member | team#member
relation channel_add_role_users: role#member | team#member
relation channel_remove_role_users: role#member | team#member
relation channel_view_role_users: role#member | team#member
relation group_update: role#member | team#member
relation group_membership: role#member | team#member
relation group_read: role#member | team#member
relation group_delete: role#member | team#member
relation group_set_child: role#member | team#member
relation group_set_parent: role#member | team#member
relation group_manage_role: role#member | team#member
relation group_add_role_users: role#member | team#member
relation group_remove_role_users: role#member | team#member
relation group_view_role_users: role#member | team#member
// Magistrala-specific relations
relation alarm_update: role#member | team#member
relation alarm_read: role#member | team#member
relation alarm_delete: role#member | team#member
relation rule_create: role#member | team#member
relation rule_update: role#member | team#member
relation rule_read: role#member | team#member
relation rule_delete: role#member | team#member
relation rule_manage_role: role#member | team#member
relation rule_add_role_users: role#member | team#member
relation rule_remove_role_users: role#member | team#member
relation rule_view_role_users: role#member | team#member
relation alarm_assign: role#member | team#member
relation alarm_acknowledge: role#member | team#member
relation alarm_resolve: role#member | team#member
relation report_create: role#member | team#member
relation report_update: role#member | team#member
relation report_read: role#member | team#member
relation report_delete: role#member | team#member
relation report_manage_role: role#member | team#member
relation report_add_role_users: role#member | team#member
relation report_remove_role_users: role#member | team#member
relation report_view_role_users: role#member | team#member
permission update_permission = update + team->domain_update + organization->admin
permission read_permission = read + team->domain_read + organization->admin
permission enable_permission = enable + team->domain_update + organization->admin
permission disable_permission = disable + team->domain_update + organization->admin
permission delete_permission = delete + team->domain_delete + organization->admin
permission manage_role_permission = manage_role + team->domain_manage_role + organization->admin
permission add_role_users_permission = add_role_users + team->domain_add_role_users + organization->admin
permission remove_role_users_permission = remove_role_users + team->domain_remove_role_users + organization->admin
permission view_role_users_permission = view_role_users + team->domain_view_role_users + organization->admin
permission membership = read + update + enable + disable + delete +
manage_role + add_role_users + remove_role_users + view_role_users +
client_create + channel_create + group_create +
client_update + client_read + client_delete + client_set_parent_group + client_connect_to_channel +
client_manage_role + client_add_role_users + client_remove_role_users + client_view_role_users +
channel_update + channel_read + channel_delete + channel_set_parent_group + channel_connect_to_client + channel_publish + channel_subscribe +
channel_manage_role + channel_add_role_users + channel_remove_role_users + channel_view_role_users +
group_update + group_membership + group_read + group_delete + group_set_child + group_set_parent +
group_manage_role + group_add_role_users + group_remove_role_users + group_view_role_users +
alarm_update + alarm_read + alarm_delete + rule_create + rule_update + rule_read + rule_delete + rule_manage_role + rule_add_role_users + rule_remove_role_users + rule_view_role_users + alarm_assign + alarm_acknowledge + alarm_resolve + report_create + report_update + report_read + report_delete + report_manage_role + report_add_role_users + report_remove_role_users + report_view_role_users +
organization->admin
permission admin = (read & update & enable & disable & delete & manage_role & add_role_users & remove_role_users & view_role_users) + organization->admin
permission client_create_permission = client_create + team->client_create + organization->admin
permission channel_create_permission = channel_create + team->channel_create + organization->admin
permission group_create_permission = group_create + team->group_create + organization->admin
permission client_update_permission = client_update + team->client_update + organization->admin
permission client_read_permission = client_read + team->client_read + organization->admin
permission client_delete_permission = client_delete + team->client_delete + organization->admin
permission client_set_parent_group_permission = client_set_parent_group + team->client_set_parent_group + organization->admin
permission client_connect_to_channel_permission = client_connect_to_channel + team->client_connect_to_channel + organization->admin
permission client_manage_role_permission = client_manage_role + team->client_manage_role + organization->admin
permission client_add_role_users_permission = client_add_role_users + team->client_add_role_users + organization->admin
permission client_remove_role_users_permission = client_remove_role_users + team->client_remove_role_users + organization->admin
permission client_view_role_users_permission = client_view_role_users + team->client_view_role_users + organization->admin
permission channel_update_permission = channel_update + team->channel_update + organization->admin
permission channel_read_permission = channel_read + team->channel_read + organization->admin
permission channel_delete_permission = channel_delete + team->channel_delete + organization->admin
permission channel_set_parent_group_permission = channel_set_parent_group + team->channel_set_parent_group + organization->admin
permission channel_connect_to_client_permission = channel_connect_to_client + team->channel_connect_to_client + organization->admin
permission channel_publish_permission = channel_publish + team->channel_publish + organization->admin
permission channel_subscribe_permission = channel_subscribe + team->channel_subscribe + organization->admin
permission channel_manage_role_permission = channel_manage_role + team->channel_manage_role + organization->admin
permission channel_add_role_users_permission = channel_add_role_users + team->channel_add_role_users + organization->admin
permission channel_remove_role_users_permission = channel_remove_role_users + team->channel_remove_role_users + organization->admin
permission channel_view_role_users_permission = channel_view_role_users + team->channel_view_role_users + organization->admin
permission group_update_permission = group_update + team->group_update + organization->admin
permission group_membership_permission = group_membership + team->group_membership + organization->admin
permission group_read_permission = group_read + team->group_read + organization->admin
permission group_delete_permission = group_delete + team->group_delete + organization->admin
permission group_set_child_permission = group_set_child + team->group_set_child + organization->admin
permission group_set_parent_permission = group_set_parent + team->group_set_parent + organization->admin
permission group_manage_role_permission = group_manage_role + team->group_manage_role + organization->admin
permission group_add_role_users_permission = group_add_role_users + team->group_add_role_users + organization->admin
permission group_remove_role_users_permission = group_remove_role_users + team->group_remove_role_users + organization->admin
permission group_view_role_users_permission = group_view_role_users + team->group_view_role_users + organization->admin
// Magistrala-specific permissions
permission alarm_update_permission = alarm_update + team->alarm_update + organization->admin
permission alarm_read_permission = alarm_read + team->alarm_read + organization->admin
permission alarm_delete_permission = alarm_delete + team->alarm_delete + organization->admin
permission rule_create_permission = rule_create + team->rule_create + organization->admin
permission rule_update_permission = rule_update + team->rule_update + organization->admin
permission rule_read_permission = rule_read + team->rule_read + organization->admin
permission rule_delete_permission = rule_delete + team->rule_delete + organization->admin
permission rule_manage_role_permission = rule_manage_role + team->rule_manage_role + organization->admin
permission rule_add_role_users_permission = rule_add_role_users + team->rule_add_role_users + organization->admin
permission rule_remove_role_users_permission = rule_remove_role_users + team->rule_remove_role_users + organization->admin
permission rule_view_role_users_permission = rule_view_role_users + team->rule_view_role_users + organization->admin
permission alarm_assign_permission = alarm_assign + team->alarm_assign + organization->admin
permission alarm_acknowledge_permission = alarm_acknowledge + team->alarm_acknowledge + organization->admin
permission alarm_resolve_permission = alarm_resolve + team->alarm_resolve + organization->admin
permission report_create_permission = report_create + team->report_create + organization->admin
permission report_update_permission = report_update + team->report_update + organization->admin
permission report_read_permission = report_read + team->report_read + organization->admin
permission report_delete_permission = report_delete + team->report_delete + organization->admin
permission report_manage_role_permission = report_manage_role + team->report_manage_role + organization->admin
permission report_add_role_users_permission = report_add_role_users + team->report_add_role_users + organization->admin
permission report_remove_role_users_permission = report_remove_role_users + team->report_remove_role_users + organization->admin
permission report_view_role_users_permission = report_view_role_users + team->report_view_role_users + organization->admin
}
// Add this relation and permission in future while adding organization
definition team {
relation organization: organization
relation parent_team: team
relation delete: role#member
relation enable: role#member | team#member
relation disable: role#member | team#member
relation update: role#member
relation read: role#member
relation set_parent: role#member
relation set_child: role#member
relation member: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
relation subteam_delete: role#member
relation subteam_update: role#member
relation subteam_read: role#member
relation subteam_member: role#member
relation subteam_set_child: role#member
relation subteam_set_parent: role#member
relation subteam_manage_role: role#member
relation subteam_add_role_users: role#member
relation subteam_remove_role_users: role#member
relation subteam_view_role_users: role#member
// Domain related permission
relation domain_update: role#member | team#member
relation domain_read: role#member | team#member
relation domain_membership: role#member | team#member
relation domain_delete: role#member | team#member
relation domain_manage_role: role#member | team#member
relation domain_add_role_users: role#member | team#member
relation domain_remove_role_users: role#member | team#member
relation domain_view_role_users: role#member | team#member
relation client_create: role#member | team#member
relation channel_create: role#member | team#member
relation group_create: role#member | team#member
relation client_update: role#member | team#member
relation client_read: role#member | team#member
relation client_delete: role#member | team#member
relation client_set_parent_group: role#member | team#member
relation client_connect_to_channel: role#member | team#member
relation client_manage_role: role#member | team#member
relation client_add_role_users: role#member | team#member
relation client_remove_role_users: role#member | team#member
relation client_view_role_users: role#member | team#member
relation channel_update: role#member | team#member
relation channel_read: role#member | team#member
relation channel_delete: role#member | team#member
relation channel_set_parent_group: role#member | team#member
relation channel_connect_to_client: role#member | team#member
relation channel_publish: role#member | team#member
relation channel_subscribe: role#member | team#member
relation channel_manage_role: role#member | team#member
relation channel_add_role_users: role#member | team#member
relation channel_remove_role_users: role#member | team#member
relation channel_view_role_users: role#member | team#member
relation group_update: role#member | team#member
relation group_membership: role#member | team#member
relation group_read: role#member | team#member
relation group_delete: role#member | team#member
relation group_set_child: role#member | team#member
relation group_set_parent: role#member | team#member
relation group_manage_role: role#member | team#member
relation group_add_role_users: role#member | team#member
relation group_remove_role_users: role#member | team#member
relation group_view_role_users: role#member | team#member
// Magistrala-specific relations
relation alarm_update: role#member | team#member
relation alarm_read: role#member | team#member
relation alarm_delete: role#member | team#member
relation rule_create: role#member | team#member
relation rule_update: role#member | team#member
relation rule_read: role#member | team#member
relation rule_delete: role#member | team#member
relation rule_manage_role: role#member | team#member
relation rule_add_role_users: role#member | team#member
relation rule_remove_role_users: role#member | team#member
relation rule_view_role_users: role#member | team#member
relation alarm_assign: role#member | team#member
relation alarm_acknowledge: role#member | team#member
relation alarm_resolve: role#member | team#member
relation report_create: role#member | team#member
relation report_update: role#member | team#member
relation report_read: role#member | team#member
relation report_delete: role#member | team#member
relation report_manage_role: role#member | team#member
relation report_add_role_users: role#member | team#member
relation report_remove_role_users: role#member | team#member
relation report_view_role_users: role#member | team#member
permission delete_permission = delete + organization->team_delete + parent_team->subteam_delete + organization->admin
permission update_permission = update + organization->team_update + parent_team->subteam_update + organization->admin
permission read_permission = read + organization->team_read + parent_team->subteam_read + organization->admin
permission set_parent_permission = set_parent + organization->team_set_parent + parent_team->subteam_set_parent + organization->admin
permission set_child_permisssion = set_child + organization->team_set_child + parent_team->subteam_set_child + organization->admin
permission membership = member + organization->team_member + parent_team->subteam_member + organization->admin
permission manage_role_permission = manage_role + organization->team_manage_role + parent_team->subteam_manage_role + organization->admin
permission add_role_users_permission = add_role_users + organization->team_add_role_users + parent_team->subteam_add_role_users + organization->admin
permission remove_role_users_permission = remove_role_users + organization->team_remove_role_users + parent_team->subteam_remove_role_users + organization->admin
permission view_role_users_permission = view_role_users + organization->team_view_role_users + parent_team->subteam_view_role_users + organization->admin
}
definition organization {
relation platform: platform
relation administrator: user
relation delete: role#member
relation update: role#member
relation read: role#member
relation member: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
relation team_create: role#member
relation team_delete: role#member
relation team_update: role#member
relation team_read: role#member
relation team_member: role#member // Will be member of all the teams in the organization
relation team_set_child: role#member
relation team_set_parent: role#member
relation team_manage_role: role#member
relation team_add_role_users: role#member
relation team_remove_role_users: role#member
relation team_view_role_users: role#member
permission admin = administrator + platform->administrator
permission delete_permission = admin + delete->member
permission update_permission = admin + update->member
permission read_permission = admin + read->member
permission membership = admin + member->member
permission team_create_permission = admin + team_create->member
permission manage_role_permission = admin + manage_role
permission add_role_users_permisson = admin + add_role_users
permission remove_role_users_permission = admin + remove_role_users
permission view_role_users_permission = admin + view_role_users
}
definition platform {
relation administrator: user
relation member: user
permission admin = administrator
permission membership = administrator + member
}
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Merge documentation
// - Source A (base): docker/supermq-docker/spicedb/schema.zed (SuperMQ upstream schema)
// - Source B (overlay): docker/spicedb/override-schema.zed (Magistrala schema extensions)
// - Merge script: scripts/combined-schema.sh
// - Output: docker/spicedb/combined-schema.zed
//
// How merge works:
// 1. The first `definition domain { ... }` block is treated as explicit domain overlay.
// 2. The first `definition team { ... }` block is treated as explicit team overlay.
// 3. Domain overlay relations/permissions are injected into SuperMQ `definition domain`.
// 4. Team overlay relations are injected into SuperMQ `definition team`.
// 5. `permission membership_extension = ...` from the domain overlay is injected into
// SuperMQ `domain.permission membership` before `organization->admin`.
// 6. Overlay `definition domain` and `definition team` blocks are removed before append,
// so only merged SuperMQ domain/team definitions remain.
// 7. Remaining definitions in this file (for example `alarm`, `rule`, `report`) are appended.
//
// Maintenance notes:
// - Keep all custom domain/team merge lines inside the two overlay blocks below.
// - Update `permission membership_extension` whenever domain membership additions change.
// - Regenerate combined schema with: `sh scripts/combine-schema.sh`
// - `scripts/supermq.sh` also regenerates combined schema after refreshing SuperMQ docker files.
// Overlay domain block consumed by scripts/combine-schema.sh during merge.
// Overlay team block consumed by scripts/combine-schema.sh during merge.
definition rule {
relation domain: domain
relation update: role#member
relation read: role#member
relation delete: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
relation alarm_read: role#member
relation alarm_assign: role#member
relation alarm_acknowledge: role#member
relation alarm_resolve: role#member
permission update_permission = update + domain->rule_update_permission
permission read_permission = read + domain->rule_read_permission
permission delete_permission = delete + domain->rule_delete_permission
permission manage_role_permission = manage_role + domain->rule_manage_role_permission
permission add_role_users_permission = add_role_users + domain->rule_add_role_users_permission
permission remove_role_users_permission = remove_role_users + domain->rule_remove_role_users_permission
permission view_role_users_permission = view_role_users + domain->rule_view_role_users_permission
permission alarm_read_permission = alarm_read + domain->alarm_read_permission
permission alarm_assign_permission = alarm_assign + domain->alarm_assign_permission
permission alarm_acknowledge_permission = alarm_acknowledge + domain->alarm_acknowledge_permission
permission alarm_resolve_permission = alarm_resolve + domain->alarm_resolve_permission
}
definition report {
relation domain: domain
relation update: role#member
relation read: role#member
relation delete: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
permission update_permission = update + domain->report_update_permission
permission read_permission = read + domain->report_read_permission
permission delete_permission = delete + domain->report_delete_permission
permission manage_role_permission = manage_role + domain->report_manage_role_permission
permission add_role_users_permission = add_role_users + domain->report_add_role_users_permission
permission remove_role_users_permission = remove_role_users + domain->report_remove_role_users_permission
permission view_role_users_permission = view_role_users + domain->report_view_role_users_permission
}
+168
View File
@@ -0,0 +1,168 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Merge documentation
// - Source A (base): docker/supermq-docker/spicedb/schema.zed (SuperMQ upstream schema)
// - Source B (overlay): docker/spicedb/override-schema.zed (Magistrala schema extensions)
// - Merge script: scripts/combined-schema.sh
// - Output: docker/spicedb/combined-schema.zed
//
// How merge works:
// 1. The first `definition domain { ... }` block is treated as explicit domain overlay.
// 2. The first `definition team { ... }` block is treated as explicit team overlay.
// 3. Domain overlay relations/permissions are injected into SuperMQ `definition domain`.
// 4. Team overlay relations are injected into SuperMQ `definition team`.
// 5. `permission membership_extension = ...` from the domain overlay is injected into
// SuperMQ `domain.permission membership` before `organization->admin`.
// 6. Overlay `definition domain` and `definition team` blocks are removed before append,
// so only merged SuperMQ domain/team definitions remain.
// 7. Remaining definitions in this file (for example `alarm`, `rule`, `report`) are appended.
//
// Maintenance notes:
// - Keep all custom domain/team merge lines inside the two overlay blocks below.
// - Update `permission membership_extension` whenever domain membership additions change.
// - Regenerate combined schema with: `sh scripts/combine-schema.sh`
// - `scripts/supermq.sh` also regenerates combined schema after refreshing SuperMQ docker files.
// Overlay domain block consumed by scripts/combine-schema.sh during merge.
definition domain {
// Magistrala-specific relations
relation alarm_update: role#member | team#member
relation alarm_read: role#member | team#member
relation alarm_delete: role#member | team#member
relation rule_create: role#member | team#member
relation rule_update: role#member | team#member
relation rule_read: role#member | team#member
relation rule_delete: role#member | team#member
relation rule_manage_role: role#member | team#member
relation rule_add_role_users: role#member | team#member
relation rule_remove_role_users: role#member | team#member
relation rule_view_role_users: role#member | team#member
relation alarm_assign: role#member | team#member
relation alarm_acknowledge: role#member | team#member
relation alarm_resolve: role#member | team#member
relation report_create: role#member | team#member
relation report_update: role#member | team#member
relation report_read: role#member | team#member
relation report_delete: role#member | team#member
relation report_manage_role: role#member | team#member
relation report_add_role_users: role#member | team#member
relation report_remove_role_users: role#member | team#member
relation report_view_role_users: role#member | team#member
// Magistrala-specific permissions
permission alarm_update_permission = alarm_update + team->alarm_update + organization->admin
permission alarm_read_permission = alarm_read + team->alarm_read + organization->admin
permission alarm_delete_permission = alarm_delete + team->alarm_delete + organization->admin
permission rule_create_permission = rule_create + team->rule_create + organization->admin
permission rule_update_permission = rule_update + team->rule_update + organization->admin
permission rule_read_permission = rule_read + team->rule_read + organization->admin
permission rule_delete_permission = rule_delete + team->rule_delete + organization->admin
permission rule_manage_role_permission = rule_manage_role + team->rule_manage_role + organization->admin
permission rule_add_role_users_permission = rule_add_role_users + team->rule_add_role_users + organization->admin
permission rule_remove_role_users_permission = rule_remove_role_users + team->rule_remove_role_users + organization->admin
permission rule_view_role_users_permission = rule_view_role_users + team->rule_view_role_users + organization->admin
permission alarm_assign_permission = alarm_assign + team->alarm_assign + organization->admin
permission alarm_acknowledge_permission = alarm_acknowledge + team->alarm_acknowledge + organization->admin
permission alarm_resolve_permission = alarm_resolve + team->alarm_resolve + organization->admin
permission report_create_permission = report_create + team->report_create + organization->admin
permission report_update_permission = report_update + team->report_update + organization->admin
permission report_read_permission = report_read + team->report_read + organization->admin
permission report_delete_permission = report_delete + team->report_delete + organization->admin
permission report_manage_role_permission = report_manage_role + team->report_manage_role + organization->admin
permission report_add_role_users_permission = report_add_role_users + team->report_add_role_users + organization->admin
permission report_remove_role_users_permission = report_remove_role_users + team->report_remove_role_users + organization->admin
permission report_view_role_users_permission = report_view_role_users + team->report_view_role_users + organization->admin
// Explicit extension injected into SuperMQ domain `permission membership`.
permission membership_extension = alarm_update + alarm_read + alarm_delete + rule_create + rule_update + rule_read + rule_delete + rule_manage_role + rule_add_role_users + rule_remove_role_users + rule_view_role_users + alarm_assign + alarm_acknowledge + alarm_resolve + report_create + report_update + report_read + report_delete + report_manage_role + report_add_role_users + report_remove_role_users + report_view_role_users
}
// Overlay team block consumed by scripts/combine-schema.sh during merge.
definition team {
relation alarm_update: role#member | team#member
relation alarm_read: role#member | team#member
relation alarm_delete: role#member | team#member
relation rule_create: role#member | team#member
relation rule_update: role#member | team#member
relation rule_read: role#member | team#member
relation rule_delete: role#member | team#member
relation rule_manage_role: role#member | team#member
relation rule_add_role_users: role#member | team#member
relation rule_remove_role_users: role#member | team#member
relation rule_view_role_users: role#member | team#member
relation alarm_assign: role#member | team#member
relation alarm_acknowledge: role#member | team#member
relation alarm_resolve: role#member | team#member
relation report_create: role#member | team#member
relation report_update: role#member | team#member
relation report_read: role#member | team#member
relation report_delete: role#member | team#member
relation report_manage_role: role#member | team#member
relation report_add_role_users: role#member | team#member
relation report_remove_role_users: role#member | team#member
relation report_view_role_users: role#member | team#member
}
definition rule {
relation domain: domain
relation update: role#member
relation read: role#member
relation delete: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
relation alarm_read: role#member
relation alarm_assign: role#member
relation alarm_acknowledge: role#member
relation alarm_resolve: role#member
permission update_permission = update + domain->rule_update_permission
permission read_permission = read + domain->rule_read_permission
permission delete_permission = delete + domain->rule_delete_permission
permission manage_role_permission = manage_role + domain->rule_manage_role_permission
permission add_role_users_permission = add_role_users + domain->rule_add_role_users_permission
permission remove_role_users_permission = remove_role_users + domain->rule_remove_role_users_permission
permission view_role_users_permission = view_role_users + domain->rule_view_role_users_permission
permission alarm_read_permission = alarm_read + domain->alarm_read_permission
permission alarm_assign_permission = alarm_assign + domain->alarm_assign_permission
permission alarm_acknowledge_permission = alarm_acknowledge + domain->alarm_acknowledge_permission
permission alarm_resolve_permission = alarm_resolve + domain->alarm_resolve_permission
}
definition report {
relation domain: domain
relation update: role#member
relation read: role#member
relation delete: role#member
relation manage_role: role#member
relation add_role_users: role#member
relation remove_role_users: role#member
relation view_role_users: role#member
permission update_permission = update + domain->report_update_permission
permission read_permission = read + domain->report_read_permission
permission delete_permission = delete + domain->report_delete_permission
permission manage_role_permission = manage_role + domain->report_manage_role_permission
permission add_role_users_permission = add_role_users + domain->report_add_role_users_permission
permission remove_role_users_permission = remove_role_users + domain->report_remove_role_users_permission
permission view_role_users_permission = view_role_users + domain->report_view_role_users_permission
}
+26 -11
View File
@@ -2,9 +2,12 @@
# SPDX-License-Identifier: Apache-2.0
services:
spicedb:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
spicedb-migrate:
networks: !override
@@ -17,7 +20,8 @@ services:
auth-db:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
auth-redis:
networks: !override
- magistrala-base-net
@@ -25,6 +29,8 @@ services:
auth:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
domains-db:
networks: !override
@@ -37,6 +43,8 @@ services:
domains:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
clients-db:
networks: !override
@@ -49,6 +57,8 @@ services:
clients:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
channels-redis:
networks: !override
@@ -61,6 +71,8 @@ services:
channels:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
users-db:
networks: !override
@@ -77,6 +89,8 @@ services:
groups:
networks: !override
- magistrala-base-net
volumes:
- ../spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
jaeger:
networks: !override
@@ -170,20 +184,20 @@ services:
AM_CERTS_OPENBAO_UNSEAL_KEY_2: ${AM_CERTS_OPENBAO_UNSEAL_KEY_2}
AM_CERTS_OPENBAO_UNSEAL_KEY_3: ${AM_CERTS_OPENBAO_UNSEAL_KEY_3}
AM_CERTS_OPENBAO_ROOT_TOKEN: ${AM_CERTS_OPENBAO_ROOT_TOKEN}
AM_JAEGER_URL: ${AM_JAEGER_URL}
AM_JAEGER_TRACE_RATIO: ${AM_JAEGER_TRACE_RATIO}
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT}
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY}
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS}
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
networks: !override
- magistrala-base-net
@@ -194,10 +208,10 @@ services:
- ../../docker/nginx/entrypoint.sh:/docker-entrypoint.d/entrypoint.sh
- type: bind
source: ${SMQ_NGINX_SERVER_CERT:-../../docker/ssl/certs/magistrala-server.crt}
target: /etc/ssl/certs/magistrala-server.crt
target: /etc/ssl/certs/supermq-server.crt
- type: bind
source: ${SMQ_NGINX_SERVER_KEY:-../../docker/ssl/certs/magistrala-server.key}
target: /etc/ssl/private/magistrala-server.key
target: /etc/ssl/private/supermq-server.key
- type: bind
source: ${SMQ_NGINX_SERVER_CLIENT_CA:-../../docker/ssl/certs/ca.crt}
target: /etc/ssl/certs/ca.crt
@@ -209,3 +223,4 @@ services:
env_file: !override
- ./.env
- ../../docker/.env
+7 -4
View File
@@ -238,8 +238,6 @@ SMQ_USERS_ADMIN_USERNAME=admin
SMQ_USERS_ADMIN_FIRST_NAME=super
SMQ_USERS_ADMIN_LAST_NAME=admin
SMQ_USERS_PASS_REGEX=^.{8,}$
SMQ_USERS_ACCESS_TOKEN_DURATION=15m
SMQ_USERS_REFRESH_TOKEN_DURATION=24h
SMQ_USERS_HTTP_HOST=users
SMQ_USERS_HTTP_PORT=9002
SMQ_USERS_HTTP_SERVER_CERT=
@@ -263,8 +261,6 @@ SMQ_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH
SMQ_USERS_ADMIN_EMAIL=admin@example.com
SMQ_USERS_ADMIN_PASSWORD=12345678
SMQ_USERS_PASS_REGEX=^.{8,}$
SMQ_USERS_ACCESS_TOKEN_DURATION=15m
SMQ_USERS_REFRESH_TOKEN_DURATION=24h
SMQ_USERS_ALLOW_SELF_REGISTER=true
SMQ_OAUTH_UI_REDIRECT_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/tokens/secure
SMQ_OAUTH_UI_ERROR_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/error
@@ -418,6 +414,12 @@ SMQ_MQTT_ADAPTER_ES_DB=0
SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS=200000
SMQ_MQTT_ADAPTER_CACHE_MAX_COST=1048576
SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS=64
SMQ_MQTT_ADAPTER_CERT_FILE=
SMQ_MQTT_ADAPTER_KEY_FILE=
SMQ_MQTT_ADAPTER_SERVER_CA_FILE=
SMQ_MQTT_ADAPTER_CLIENT_CA_FILE=
SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS=
SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL=
### CoAP
## If enabled run make all inside docker/ssl directory to generate the DTLS certs
@@ -494,6 +496,7 @@ AM_JAEGER_URL=http://jaeger:4318/v1/traces
AM_JAEGER_TRACE_RATIO=1.0
#### Auth Client Config for Certs Service
SMQ_ADDONS_CERTS_PATH_PREFIX=../../
AM_AUTH_GRPC_URL=auth:7001
AM_AUTH_GRPC_TIMEOUT=300s
AM_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
+1 -1
View File
@@ -1,7 +1,7 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.26rc2-alpine3.22 AS builder
FROM golang:1.26.1-alpine3.22 AS builder
ARG SVC
ARG GOARCH
ARG GOARM
@@ -1,7 +1,7 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.25-alpine AS builder
FROM golang:1.26-alpine AS builder
ARG SVC
ARG GOARCH
@@ -46,9 +46,9 @@ services:
AM_CERTS_DB_SSL_MODE: ${AM_CERTS_DB_SSL_MODE}
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
@@ -65,6 +65,36 @@ services:
- ${AM_CERTS_GRPC_PORT}:${AM_CERTS_GRPC_PORT}
volumes:
- openbao-data:/openbao:ro
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_ADDONS_CERTS_PATH_PREFIX}${AM_DOMAINS_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
certs-db:
image: postgres:16.2-alpine
@@ -49,14 +49,14 @@ services:
AM_JAEGER_TRACE_RATIO: ${AM_JAEGER_TRACE_RATIO}
AM_AUTH_GRPC_URL: ${AM_AUTH_GRPC_URL}
AM_AUTH_GRPC_TIMEOUT: ${AM_AUTH_GRPC_TIMEOUT}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS}
AM_AUTH_GRPC_CLIENT_CERT: ${AM_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
AM_AUTH_GRPC_CLIENT_KEY: ${AM_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
AM_AUTH_GRPC_SERVER_CA_CERTS: ${AM_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
AM_DOMAINS_GRPC_URL: ${AM_DOMAINS_GRPC_URL}
AM_DOMAINS_GRPC_TIMEOUT: ${AM_DOMAINS_GRPC_TIMEOUT}
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT}
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY}
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS}
AM_DOMAINS_GRPC_CLIENT_CERT: ${AM_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
AM_DOMAINS_GRPC_CLIENT_KEY: ${AM_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
AM_DOMAINS_GRPC_SERVER_CA_CERTS: ${AM_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
networks: !override
- supermq-base-net
+144 -117
View File
@@ -95,6 +95,8 @@ services:
- supermq-base-net
volumes:
- supermq-auth-redis-volume:/data
- ./redis/redis.conf:/etc/redis/redis.conf:ro
command: ["redis-server", "/etc/redis/redis.conf"]
auth:
image: docker.io/supermq/auth:${SMQ_RELEASE_TAG}
@@ -161,45 +163,45 @@ services:
# Auth retiring private key file (optional, for key rotation)
- type: bind
source: ${SMQ_AUTH_KEYS_RETIRING_KEY_PATH:-ssl/certs/dummy/retiring_key}
target: /keys/retiring${SMQ_AUTH_KEYS_RETIRING_KEY_PATH:+.key}
target: /keys/retiring.key
read_only: true
bind:
create_host_path: true
# Auth gRPC mTLS server certificates
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_CERT:+.crt}
target: /auth-grpc-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_KEY:+.key}
target: /auth-grpc-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /auth-grpc-client-ca${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:+.crt}
target: /auth-grpc-client-ca.crt
bind:
create_host_path: true
# Auth Callout Client Certificates
- type: bind
source: ${SMQ_AUTH_CALLOUT_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-callout-client${SMQ_AUTH_CALLOUT_CLIENT_CERT:+.crt}
target: /auth-callout-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_CALLOUT_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-callout-client${SMQ_AUTH_CALLOUT_CLIENT_KEY:+.key}
target: /auth-callout-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_CALLOUT_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /auth-callout-client-ca${SMQ_AUTH_CALLOUT_CLIENT_CA_CERTS:+.crt}
target: /auth-callout-client-ca.crt
bind:
create_host_path: true
@@ -313,86 +315,86 @@ services:
# Auth gRPC mTLS server certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
target: /domains-grpc-server${SMQ_DOMAINS_GRPC_SERVER_CERT:+.crt}
target: /domains-grpc-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
target: /domains-grpc-server${SMQ_DOMAINS_GRPC_SERVER_KEY:+.key}
target: /domains-grpc-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /domains-grpc-client-ca${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:+.crt}
target: /domains-grpc-client-ca.crt
bind:
create_host_path: true
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Groups gRPC client certificates
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
target: /groups-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
target: /groups-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
target: /groups-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Clients gRPC client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
@@ -550,86 +552,86 @@ services:
# Clients gRPC server certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
target: /clients-grpc-server${SMQ_CLIENTS_GRPC_SERVER_CERT:+.crt}
target: /clients-grpc-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
target: /clients-grpc-server${SMQ_CLIENTS_GRPC_SERVER_KEY:+.key}
target: /clients-grpc-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /clients-grpc-client-ca${SMQ_CLIENTS_GRPC_CLIENT_CA_CERTS:+.crt}
target: /clients-grpc-client-ca.crt
bind:
create_host_path: true
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Channel gRPC client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Group gRPC client certificates
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
target: /groups-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
target: /groups-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
target: /groups-grpc-server-ca.crt
bind:
create_host_path: true
# Domain gRPC client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -743,86 +745,86 @@ services:
# Channels gRPC server certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
target: /channels-grpc-server${SMQ_CHANNELS_GRPC_SERVER_CERT:+.crt}
target: /channels-grpc-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
target: /channels-grpc-server${SMQ_CHANNELS_GRPC_SERVER_KEY:+.key}
target: /channels-grpc-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /channels-grpc-client-ca${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:+.crt}
target: /channels-grpc-client-ca.crt
bind:
create_host_path: true
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Clients gRPC client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
# Groups gRPC client certificates
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_CERT:+.crt}
target: /groups-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /groups-grpc-client${SMQ_GROUPS_GRPC_CLIENT_KEY:+.key}
target: /groups-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
target: /groups-grpc-server-ca.crt
bind:
create_host_path: true
# Domains gRPC client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -860,8 +862,6 @@ services:
SMQ_USERS_ADMIN_FIRST_NAME: ${SMQ_USERS_ADMIN_FIRST_NAME}
SMQ_USERS_ADMIN_LAST_NAME: ${SMQ_USERS_ADMIN_LAST_NAME}
SMQ_USERS_PASS_REGEX: ${SMQ_USERS_PASS_REGEX}
SMQ_USERS_ACCESS_TOKEN_DURATION: ${SMQ_USERS_ACCESS_TOKEN_DURATION}
SMQ_USERS_REFRESH_TOKEN_DURATION: ${SMQ_USERS_REFRESH_TOKEN_DURATION}
SMQ_USERS_HTTP_HOST: ${SMQ_USERS_HTTP_HOST}
SMQ_USERS_HTTP_PORT: ${SMQ_USERS_HTTP_PORT}
SMQ_USERS_HTTP_SERVER_CERT: ${SMQ_USERS_HTTP_SERVER_CERT}
@@ -933,33 +933,33 @@ services:
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Domains gRPC client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -999,17 +999,17 @@ services:
# Users gRPC client certificates
- type: bind
source: ${SMQ_USERS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /users-grpc-client${SMQ_USERS_GRPC_CLIENT_CERT:+.crt}
target: /users-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_USERS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /users-grpc-client${SMQ_USERS_GRPC_CLIENT_KEY:+.key}
target: /users-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_USERS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /users-grpc-server-ca${SMQ_USERS_GRPC_SERVER_CA_CERTS:+.crt}
target: /users-grpc-server-ca.crt
bind:
create_host_path: true
@@ -1112,86 +1112,86 @@ services:
# Groups gRPC server certificates
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert}
target: /groups-grpc-server${SMQ_GROUPS_GRPC_SERVER_CERT:+.crt}
target: /groups-grpc-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key}
target: /groups-grpc-server${SMQ_GROUPS_GRPC_SERVER_KEY:+.key}
target: /groups-grpc-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /groups-grpc-server-ca${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+.crt}
target: /groups-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_GROUPS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs}
target: /groups-grpc-client-ca${SMQ_GROUPS_GRPC_CLIENT_CA_CERTS:+.crt}
target: /groups-grpc-client-ca.crt
bind:
create_host_path: true
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Clients gRPC client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Domains gRPC client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -1234,6 +1234,12 @@ services:
SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS: ${SMQ_MQTT_ADAPTER_CACHE_NUM_COUNTERS}
SMQ_MQTT_ADAPTER_CACHE_MAX_COST: ${SMQ_MQTT_ADAPTER_CACHE_MAX_COST}
SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS: ${SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS}
SMQ_MQTT_ADAPTER_CERT_FILE: ${SMQ_MQTT_ADAPTER_CERT_FILE:+/mqtt-adapter.crt}
SMQ_MQTT_ADAPTER_KEY_FILE: ${SMQ_MQTT_ADAPTER_KEY_FILE:+/mqtt-adapter.key}
SMQ_MQTT_ADAPTER_SERVER_CA_FILE: ${SMQ_MQTT_ADAPTER_SERVER_CA_FILE:+/mqtt-adapter-server-ca.crt}
SMQ_MQTT_ADAPTER_CLIENT_CA_FILE: ${SMQ_MQTT_ADAPTER_CLIENT_CA_FILE:+/mqtt-adapter-client-ca.crt}
SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS: ${SMQ_MQTT_ADAPTER_CERT_VERIFICATION_METHODS}
SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL: ${SMQ_MQTT_ADAPTER_OCSP_RESPONDER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_CLIENTS_GRPC_URL: ${SMQ_CLIENTS_GRPC_URL}
SMQ_CLIENTS_GRPC_TIMEOUT: ${SMQ_CLIENTS_GRPC_TIMEOUT}
@@ -1257,52 +1263,73 @@ services:
networks:
- supermq-base-net
volumes:
# TLS certificate for MQTT
- type: bind
source: ${SMQ_MQTT_ADAPTER_CERT_FILE:-ssl/certs/dummy/server_cert}
target: /mqtt-adapter.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_MQTT_ADAPTER_KEY_FILE:-ssl/certs/dummy/server_key}
target: /mqtt-adapter.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_MQTT_ADAPTER_SERVER_CA_FILE:-ssl/certs/dummy/server_ca}
target: /mqtt-adapter-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_MQTT_ADAPTER_CLIENT_CA_FILE:-ssl/certs/dummy/client_ca}
target: /mqtt-adapter-client-ca.crt
bind:
create_host_path: true
# Clients gRPC mTLS client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC mTLS client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Domains gRPC mTLS client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -1357,65 +1384,65 @@ services:
# Clients gRPC mTLS client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC mTLS client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Auth gRPC mTLS client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Domains gRPC mTLS client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
@@ -1470,70 +1497,70 @@ services:
# DTLS certificates for CoAP
- type: bind
source: ${SMQ_COAP_ADAPTER_SERVER_CERT_FILE:-ssl/certs/dummy/server_cert}
target: /coap-server${SMQ_COAP_ADAPTER_SERVER_CERT_FILE:+.crt}
target: /coap-server.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_COAP_ADAPTER_SERVER_KEY_FILE:-ssl/certs/dummy/server_key}
target: /coap-server${SMQ_COAP_ADAPTER_SERVER_KEY_FILE:+.key}
target: /coap-server.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_COAP_ADAPTER_SERVER_CA_FILE:-ssl/certs/dummy/server_ca}
target: /coap-server-ca${SMQ_COAP_ADAPTER_SERVER_CA_FILE:+.crt}
target: /coap-server-ca.crt
bind:
create_host_path: true
# Clients gRPC mTLS client certificates
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_CERT:+.crt}
target: /clients-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /clients-grpc-client${SMQ_CLIENTS_GRPC_CLIENT_KEY:+.key}
target: /clients-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /clients-grpc-server-ca${SMQ_CLIENTS_GRPC_SERVER_CA_CERTS:+.crt}
target: /clients-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC mTLS client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca}
target: /channels-grpc-client-ca${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:+.crt}
target: /channels-grpc-client-ca.crt
bind:
create_host_path: true
# Domains gRPC mTLS client certificates
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
-2
View File
@@ -130,5 +130,3 @@ domains:
- check_members_exists: view_role_users_permission
- remove_members: remove_role_users_permission
- remove_all_members: remove_role_users_permission
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# Enable AOF persistence
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# Enable periodic snapshots
save 300 10
# Persist data in Docker volume
dir /data
+62 -43
View File
@@ -1,19 +1,20 @@
module github.com/absmach/magistrala
module github.com/absmach/magistrala-old
go 1.25.5
go 1.26.0
require (
github.com/0x6flab/namegenerator v1.4.0
github.com/absmach/callhome v0.18.2
github.com/absmach/certs v0.18.4
github.com/absmach/supermq v0.18.5
github.com/authzed/authzed-go v1.7.0
github.com/absmach/certs v0.18.5
github.com/absmach/magistrala v0.19.1
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4
github.com/authzed/authzed-go v1.8.0
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8
github.com/caarlos0/env/v11 v11.3.1
github.com/caarlos0/env/v11 v11.4.0
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/fatih/color v1.18.0
github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba
github.com/go-chi/chi/v5 v5.2.4
github.com/go-chi/chi/v5 v5.2.5
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid/v5 v5.4.0
github.com/gookit/color v1.6.0
@@ -27,31 +28,53 @@ require (
github.com/ory/dockertest/v3 v3.12.0
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.23.2
github.com/redis/go-redis/v9 v9.17.2
github.com/redis/go-redis/v9 v9.18.0
github.com/rubenv/sql-migrate v1.8.1
github.com/slack-go/slack v0.17.3
github.com/slack-go/slack v0.19.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/traefik/yaegi v0.16.1
github.com/vadv/gopher-lua-libs v0.8.0
github.com/yuin/gopher-lua v1.1.1
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
golang.org/x/sync v0.19.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
golang.org/x/sync v0.20.0
gonum.org/v1/gonum v0.17.0
google.golang.org/grpc v1.78.0
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
moul.io/http2curl v1.0.0
)
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/authzed/cel-go v0.20.2 // indirect
github.com/authzed/spicedb v1.49.2 // indirect
github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-logr/zerologr v1.2.3 // indirect
github.com/moby/moby/api v1.53.0 // indirect
github.com/moby/moby/client v0.2.2 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect
sigs.k8s.io/controller-runtime v0.22.4 // indirect
)
require (
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
)
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
@@ -63,11 +86,10 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/cli v27.4.1+incompatible // indirect
github.com/docker/docker v28.0.0+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
@@ -81,43 +103,40 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jzelinskie/stringz v0.0.3 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/nats.go v1.48.0
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nats.go v1.49.0
github.com/nats-io/nkeys v0.4.12 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -130,20 +149,20 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+146 -86
View File
@@ -1,20 +1,25 @@
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.122.0 h1:0JTLGrcSIs3HIGsgVPvTx3cfyFSP/k9CI8vLPHTd6Wc=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/0x6flab/namegenerator v1.4.0 h1:QnkI813SZsI/hYnKD9pg3mkIlcYzCx0N4hnzb0YYME4=
github.com/0x6flab/namegenerator v1.4.0/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gBdD5oAU8hd0SyY=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
@@ -24,23 +29,31 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/absmach/callhome v0.18.2 h1:dmopRHm2qTheHN1hdUKRRYpKwRrj7X9d8AWCFrb+K6s=
github.com/absmach/callhome v0.18.2/go.mod h1:LEXKhES9JJtj3tBgTZv7VPNjOi5ukJQB0mFic0QP60Q=
github.com/absmach/certs v0.18.4 h1:45nocGNOt+IUqJHEM/lS3XpOYWYWdyOb61R6VPbxsFA=
github.com/absmach/certs v0.18.4/go.mod h1:ixBMssgL8e6NjN6BGiLYBIYGDe24DqgN0UnHgFJ2fNE=
github.com/absmach/certs v0.18.5 h1:eYlvitou+LoDtt7ETVLTp6d/1xCejGL3EmVOg+rHGTU=
github.com/absmach/certs v0.18.5/go.mod h1:31dtVe1VYF16W+IvjAE/uPAIz4f3uLHgh+moBezjqIc=
github.com/absmach/magistrala v0.19.1 h1:WLT1B3WOOO8KL43HxZOWKnt1JJQh65uGiFhwQB4DzC8=
github.com/absmach/magistrala v0.19.1/go.mod h1:5pNI533Mf2tN1ihDETR4lOhRdGuijmRuAhxVw7+pKCI=
github.com/absmach/mgate v0.5.0 h1:RV2Aalra3xIm+XTs13TM7iE7v4WTL2SKhKcPbKr22Ac=
github.com/absmach/mgate v0.5.0/go.mod h1:0KVq7mxM0wayosmyXPPxp1EL0c2d9kRp5V8NZCKdetA=
github.com/absmach/senml v1.0.8 h1:+opem/r4g6c6eA/JLyCIuksyEhj7eBdysY3pEmy1mqo=
github.com/absmach/senml v1.0.8/go.mod h1:DRhzHLgvQoIUHroBgpFrSWso+bJZO9E96RlHAHy+VRI=
github.com/absmach/supermq v0.18.5 h1:mBWfhzCDWzWZ4GaiKB8y4h17qddCBKSZhlBGMsTZNHs=
github.com/absmach/supermq v0.18.5/go.mod h1:wBPp1RSd8Rf4sC+2ljiD1aXJpgwacZVnU7WQEvGLmBU=
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 h1:533pRc6R7perWDqJuZq+ofBQfYfmyj7n49V4LFY4zpo=
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4/go.mod h1:xDAX/O3VcOsHWCx2fk85VD7FI17hAUOvoOhho7DA7g0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/authzed/authzed-go v1.7.0 h1:mgBC1dZLRan+t6oKvrf0RMfpZx5ggXCH16OkodtAbVw=
github.com/authzed/authzed-go v1.7.0/go.mod h1:2PVaUUQavKGsjK22dFdovY5djkeHAiByiySRwbWR8tU=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/authzed/authzed-go v1.8.0 h1:cRka8J8QXGl+nyNrhsiPSFJUluIG1tuTXnG8ad2LZ1Y=
github.com/authzed/authzed-go v1.8.0/go.mod h1:WC3x/SuVvclBlDYMg9V7e5c/J/KGGwG+cSw2WQBbodk=
github.com/authzed/cel-go v0.20.2 h1:GlmLecGry7Z8HU0k+hmaHHUV05ZHrsFxduXHtIePvck=
github.com/authzed/cel-go v0.20.2/go.mod h1:pJHVFWbqUHV1J+klQoZubdKswlbxcsbojda3mye9kiU=
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8 h1:y17oq4U8n+k1OcIGGDsjYdIdp4QywGcE7ZphIvtfEbo=
github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8/go.mod h1:Pf1ZSi41EePvx1GC1DeEJw5dn35iUcxZHqpHuG1Rpic=
github.com/authzed/spicedb v1.49.2 h1:6LKOxiNN7K18x4xs2NB0dhnDNDypbpuOP7s06AwjCH8=
github.com/authzed/spicedb v1.49.2/go.mod h1:I9t8PtFBxUHsSZKfrkK6bxbMy8La7LYjkpgw6UpNHQs=
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI=
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
@@ -53,10 +66,12 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc=
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cbroglie/mustache v1.0.1 h1:ivMg8MguXq/rrz2eu3tw6g3b16+PQhoTn6EZAhst2mw=
github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g=
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
@@ -76,32 +91,37 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -109,6 +129,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -129,8 +151,10 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -150,22 +174,26 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -191,6 +219,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -206,8 +236,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -289,8 +319,10 @@ github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfU
github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -321,12 +353,14 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -334,6 +368,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
@@ -347,10 +383,14 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -360,18 +400,22 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -382,15 +426,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
@@ -414,8 +457,8 @@ github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvM
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@@ -424,22 +467,23 @@ github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
@@ -452,8 +496,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
github.com/slack-go/slack v0.19.0 h1:J8lL/nGTsIUX53HU8YxZeI3PDkA+sxZsFrI2Dew7h44=
github.com/slack-go/slack v0.19.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE=
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
@@ -473,6 +517,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/sqids/sqids-go v0.4.1 h1:eQKYzmAZbLlRwHeHYPF35QhgxwZHLnlmVj9AkIj/rrw=
github.com/sqids/sqids-go v0.4.1/go.mod h1:EMwHuPQgSNFS0A49jESTfIQS+066XQTVhukrzEPScl8=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -518,27 +564,29 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -546,6 +594,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -575,8 +625,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw=
golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
@@ -590,6 +640,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -609,12 +661,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -624,8 +676,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -655,12 +707,14 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -676,8 +730,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
@@ -698,6 +752,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -711,17 +767,17 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -757,10 +813,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package policies
const (
RulesType = "rules"
ReportsType = "reports"
AlarmsType = "alarms"
)
+204
View File
@@ -0,0 +1,204 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"encoding/json"
"time"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/roles"
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
)
var (
errDecodeAddRuleEvent = errors.New("failed to decode rule add event")
errDecodeUpdateRuleEvent = errors.New("failed to decode rule update event")
errDecodeUpdateRuleTagsEvent = errors.New("failed to decode rule update tags event")
errDecodeUpdateRuleScheduleEvent = errors.New("failed to decode rule update schedule event")
errDecodeEnableRuleEvent = errors.New("failed to decode rule enable event")
errDecodeDisableRuleEvent = errors.New("failed to decode rule disable event")
errDecodeRemoveRuleEvent = errors.New("failed to decode rule remove event")
errID = errors.New("missing or invalid 'id'")
errName = errors.New("missing or invalid 'name'")
errTags = errors.New("invalid 'tags'")
errStatus = errors.New("missing or invalid 'status'")
errConvertStatus = errors.New("failed to convert status")
errCreatedBy = errors.New("missing or invalid 'created_by'")
errCreatedAt = errors.New("failed to parse 'created_at' time")
errUpdatedAt = errors.New("failed to parse 'updated_at' time")
errDecodeLogic = errors.New("failed to decode 'logic'")
errDecodeSchedule = errors.New("failed to decode 'schedule'")
)
// ToRule decodes a map[string]any event payload into a re.Rule.
func ToRule(data map[string]any) (re.Rule, error) {
var r re.Rule
id, ok := data["id"].(string)
if !ok {
return re.Rule{}, errID
}
r.ID = id
name, ok := data["name"].(string)
if !ok {
return re.Rule{}, errName
}
r.Name = name
stat, ok := data["status"].(string)
if !ok {
return re.Rule{}, errStatus
}
st, err := re.ToStatus(stat)
if err != nil {
return re.Rule{}, errors.Wrap(errConvertStatus, err)
}
r.Status = st
cby, ok := data["created_by"].(string)
if !ok {
return re.Rule{}, errCreatedBy
}
r.CreatedBy = cby
cat, ok := data["created_at"].(string)
if !ok {
return re.Rule{}, errCreatedAt
}
ct, err := time.Parse(re.TimeLayout, cat)
if err != nil {
return re.Rule{}, errors.Wrap(errCreatedAt, err)
}
r.CreatedAt = ct
if domain, ok := data["domain"].(string); ok {
r.DomainID = domain
}
if itags, ok := data["tags"].([]any); ok {
tags, err := rconsumer.ToStrings(itags)
if err != nil {
return re.Rule{}, errors.Wrap(errTags, err)
}
r.Tags = tags
}
if meta, ok := data["metadata"].(map[string]any); ok {
r.Metadata = meta
}
if uby, ok := data["updated_by"].(string); ok {
r.UpdatedBy = uby
}
if uat, ok := data["updated_at"].(string); ok {
ut, err := time.Parse(re.TimeLayout, uat)
if err != nil {
return re.Rule{}, errors.Wrap(errUpdatedAt, err)
}
r.UpdatedAt = ut
}
if ic, ok := data["input_channel"].(string); ok {
r.InputChannel = ic
}
if it, ok := data["input_topic"].(string); ok {
r.InputTopic = it
}
if rawLogic, ok := data["logic"].(map[string]any); ok {
b, err := json.Marshal(rawLogic)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
}
if err := json.Unmarshal(b, &r.Logic); err != nil {
return re.Rule{}, errors.Wrap(errDecodeLogic, err)
}
}
if rawSched, ok := data["schedule"].(map[string]any); ok {
b, err := json.Marshal(rawSched)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
}
var sched schedule.Schedule
if err := json.Unmarshal(b, &sched); err != nil {
return re.Rule{}, errors.Wrap(errDecodeSchedule, err)
}
r.Schedule = sched
}
return r, nil
}
func decodeAddRuleEvent(data map[string]any) (re.Rule, []roles.RoleProvision, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
}
var rps []roles.RoleProvision
if irps, ok := data["roles_provisioned"].([]any); ok {
rps, err = rconsumer.ToRoleProvisions(irps)
if err != nil {
return re.Rule{}, nil, errors.Wrap(errDecodeAddRuleEvent, err)
}
}
return r, rps, nil
}
func decodeUpdateRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleEvent, err)
}
return r, nil
}
func decodeUpdateRuleTagsEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleTagsEvent, err)
}
return r, nil
}
func decodeUpdateRuleScheduleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeUpdateRuleScheduleEvent, err)
}
return r, nil
}
func decodeEnableRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeEnableRuleEvent, err)
}
return r, nil
}
func decodeDisableRuleEvent(data map[string]any) (re.Rule, error) {
r, err := ToRule(data)
if err != nil {
return re.Rule{}, errors.Wrap(errDecodeDisableRuleEvent, err)
}
return r, nil
}
func decodeRemoveRuleEvent(data map[string]any) (string, error) {
id, ok := data["id"].(string)
if !ok {
return "", errors.Wrap(errDecodeRemoveRuleEvent, errID)
}
return id, nil
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package consumer contains events consumer for events
// published by the Rules Engine service.
package consumer
+193
View File
@@ -0,0 +1,193 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"context"
"log/slog"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/events"
"github.com/absmach/supermq/pkg/events/store"
rconsumer "github.com/absmach/supermq/pkg/roles/rolemanager/events/consumer"
)
const (
stream = "events.supermq.rule.*"
create = "rule.create"
update = "rule.update"
updateTags = "rule.update_tags"
updateSchedule = "rule.update_schedule"
enable = "rule.enable"
disable = "rule.disable"
remove = "rule.remove"
)
var (
errNoOperationKey = errors.New("operation key is not found in event message")
errAddRuleEvent = errors.New("failed to consume rule create event")
errUpdateRuleEvent = errors.New("failed to consume rule update event")
errUpdateRuleTagsEvent = errors.New("failed to consume rule update tags event")
errUpdateRuleScheduleEvent = errors.New("failed to consume rule update schedule event")
errEnableRuleEvent = errors.New("failed to consume rule enable event")
errDisableRuleEvent = errors.New("failed to consume rule disable event")
errRemoveRuleEvent = errors.New("failed to consume rule remove event")
)
type eventHandler struct {
repo re.Repository
rolesEventHandler rconsumer.EventHandler
}
func RulesEventsSubscribe(ctx context.Context, repo re.Repository, esURL, esConsumerName string, logger *slog.Logger) error {
subscriber, err := store.NewSubscriber(ctx, esURL, logger)
if err != nil {
return err
}
subConfig := events.SubscriberConfig{
Stream: stream,
Consumer: esConsumerName,
Handler: NewEventHandler(repo),
Ordered: true,
}
return subscriber.Subscribe(ctx, subConfig)
}
// NewEventHandler returns new event store handler.
func NewEventHandler(repo re.Repository) events.EventHandler {
reh := rconsumer.NewEventHandler("rule", repo)
return &eventHandler{
repo: repo,
rolesEventHandler: reh,
}
}
func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
msg, err := event.Encode()
if err != nil {
return err
}
op, ok := msg["operation"]
if !ok {
return errNoOperationKey
}
switch op {
case create:
return es.addRuleHandler(ctx, msg)
case update:
return es.updateRuleHandler(ctx, msg)
case updateTags:
return es.updateRuleTagsHandler(ctx, msg)
case updateSchedule:
return es.updateRuleScheduleHandler(ctx, msg)
case enable:
return es.enableRuleHandler(ctx, msg)
case disable:
return es.disableRuleHandler(ctx, msg)
case remove:
return es.removeRuleHandler(ctx, msg)
}
return es.rolesEventHandler.Handle(ctx, op, msg)
}
func (es *eventHandler) addRuleHandler(ctx context.Context, data map[string]any) error {
r, rps, err := decodeAddRuleEvent(data)
if err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
if _, err := es.repo.AddRule(ctx, r); err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
if _, err := es.repo.AddRoles(ctx, rps); err != nil {
return errors.Wrap(errAddRuleEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleEvent, err)
}
if _, err := es.repo.UpdateRule(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleTagsHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleTagsEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleTagsEvent, err)
}
if _, err := es.repo.UpdateRuleTags(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleTagsEvent, err)
}
return nil
}
func (es *eventHandler) updateRuleScheduleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeUpdateRuleScheduleEvent(data)
if err != nil {
return errors.Wrap(errUpdateRuleScheduleEvent, err)
}
if _, err := es.repo.UpdateRuleSchedule(ctx, r); err != nil {
return errors.Wrap(errUpdateRuleScheduleEvent, err)
}
return nil
}
func (es *eventHandler) enableRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeEnableRuleEvent(data)
if err != nil {
return errors.Wrap(errEnableRuleEvent, err)
}
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
return errors.Wrap(errEnableRuleEvent, err)
}
return nil
}
func (es *eventHandler) disableRuleHandler(ctx context.Context, data map[string]any) error {
r, err := decodeDisableRuleEvent(data)
if err != nil {
return errors.Wrap(errDisableRuleEvent, err)
}
if _, err := es.repo.UpdateRuleStatus(ctx, r); err != nil {
return errors.Wrap(errDisableRuleEvent, err)
}
return nil
}
func (es *eventHandler) removeRuleHandler(ctx context.Context, data map[string]any) error {
id, err := decodeRemoveRuleEvent(data)
if err != nil {
return errors.Wrap(errRemoveRuleEvent, err)
}
if err := es.repo.RemoveRule(ctx, id); err != nil {
return errors.Wrap(errRemoveRuleEvent, err)
}
return nil
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/absmach/supermq/pkg/errors"
)
const alarmsEndpoint = "alarms"
// Alarm represents an alarm instance.
type Alarm struct {
ID string `json:"id,omitempty"`
RuleID string `json:"rule_id,omitempty"`
DomainID string `json:"domain_id,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
ClientID string `json:"client_id,omitempty"`
Subtopic string `json:"subtopic,omitempty"`
Status string `json:"status,omitempty"`
Measurement string `json:"measurement,omitempty"`
Value string `json:"value,omitempty"`
Unit string `json:"unit,omitempty"`
Threshold string `json:"threshold,omitempty"`
Cause string `json:"cause,omitempty"`
Severity uint8 `json:"severity,omitempty"`
AssigneeID string `json:"assignee_id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
AssignedAt time.Time `json:"assigned_at,omitempty"`
AssignedBy string `json:"assigned_by,omitempty"`
AcknowledgedAt time.Time `json:"acknowledged_at,omitempty"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
ResolvedAt time.Time `json:"resolved_at,omitempty"`
ResolvedBy string `json:"resolved_by,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
}
type AlarmsPage struct {
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Total uint64 `json:"total"`
Alarms []Alarm `json:"alarms"`
}
func (sdk mgSDK) UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError) {
data, err := json.Marshal(alarm)
if err != nil {
return Alarm{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, alarm.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Alarm{}, sdkerr
}
var a Alarm
if err := json.Unmarshal(body, &a); err != nil {
return Alarm{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Alarm{}, sdkerr
}
var a Alarm
if err := json.Unmarshal(body, &a); err != nil {
return Alarm{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s", domainID, alarmsEndpoint)
url, err := sdk.withQueryParams(sdk.alarmsURL, endpoint, pm)
if err != nil {
return AlarmsPage{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return AlarmsPage{}, sdkerr
}
var ap AlarmsPage
if err := json.Unmarshal(body, &ap); err != nil {
return AlarmsPage{}, errors.NewSDKError(err)
}
return ap, nil
}
func (sdk mgSDK) DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
+390
View File
@@ -0,0 +1,390 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"net/http/httptest"
"testing"
"time"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/api"
amocks "github.com/absmach/magistrala/alarms/mocks"
"github.com/absmach/magistrala/pkg/sdk"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const alarmID = "alarm-1"
var testAlarm = sdk.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: "active",
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: sdk.Metadata{"key": "value"},
}
func setupAlarms() (*httptest.Server, *amocks.Service, *authnmocks.Authentication) {
asvc := new(amocks.Service)
logger := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
idp := uuid.NewMock()
mux := api.MakeHandler(asvc, logger, idp, "", am)
return httptest.NewServer(mux), asvc, authn
}
func TestUpdateAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
updated := testAlarm
updated.Status = "cleared"
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ClearedStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
cases := []struct {
desc string
alarm sdk.Alarm
token string
session smqauthn.Session
svcRes alarms.Alarm
svcErr error
authenticateErr error
wantErr bool
resp sdk.Alarm
}{
{
desc: "update alarm successfully",
alarm: updated,
token: validToken,
svcRes: svcAlarm,
resp: testAlarm,
},
{
desc: "update alarm with empty token",
alarm: updated,
token: "",
wantErr: true,
},
{
desc: "update non-existent alarm",
alarm: sdk.Alarm{ID: "non-existent"},
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("UpdateAlarm", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateAlarm(context.Background(), tc.alarm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ActiveStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes alarms.Alarm
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view alarm successfully",
id: alarmID,
token: validToken,
svcRes: svcAlarm,
},
{
desc: "view alarm with empty token",
id: alarmID,
token: "",
wantErr: true,
},
{
desc: "view non-existent alarm",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("ViewAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewAlarm(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListAlarms(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ActiveStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
svcAlarmsPage := alarms.AlarmsPage{
Total: 2,
Offset: 0,
Limit: 10,
Alarms: []alarms.Alarm{svcAlarm},
}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes alarms.AlarmsPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list alarms successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with status and entity filters",
pm: sdk.PageMetadata{
Limit: 5,
Status: "active",
ChannelID: "chan-1",
ClientID: "client-1",
RuleID: "rule-1",
AssigneeID: "user-1",
Severity: 80,
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with time range and sorting",
pm: sdk.PageMetadata{
Limit: 10,
CreatedFrom: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
CreatedTo: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC),
Order: "created_at",
Dir: "asc",
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with actor filters",
pm: sdk.PageMetadata{
Limit: 10,
UpdatedBy: "user-2",
AssignedBy: "user-3",
AcknowledgedBy: "user-4",
ResolvedBy: "user-5",
Subtopic: "subtopic-1",
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with empty metadata excludes severity",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: alarms.AlarmsPage{},
},
{
desc: "list alarms with zero severity excluded",
pm: sdk.PageMetadata{Status: "active", Severity: 0},
token: validToken,
svcRes: alarms.AlarmsPage{},
},
{
desc: "list alarms with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("ListAlarms", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListAlarms(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.Equal(t, tc.svcRes.Total, result.Total)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "delete alarm successfully",
id: alarmID,
token: validToken,
},
{
desc: "delete alarm with empty token",
id: alarmID,
token: "",
wantErr: true,
},
{
desc: "delete non-existent alarm",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("DeleteAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.DeleteAlarm(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
+1971
View File
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/absmach/supermq/pkg/errors"
)
const (
reportsEndpoint = "reports"
configsEndpointReports = "configs"
)
// ReportConfig represents a report configuration.
type ReportConfig struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Schedule any `json:"schedule,omitempty"`
Config any `json:"config,omitempty"`
Email any `json:"email,omitempty"`
Metrics any `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type ReportTemplate any
type ReportFile struct {
Name string
Format string
Data []byte
}
type ReportPage struct {
Total uint64 `json:"total"`
From time.Time `json:"from,omitempty"`
To time.Time `json:"to,omitempty"`
Aggregation any `json:"aggregation,omitempty"`
Reports any `json:"reports,omitempty"`
File any `json:"file,omitempty"`
}
type ReportConfigPage struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
ReportConfigs []ReportConfig `json:"report_configs"`
}
type ReportAction string
const (
ViewReportAction ReportAction = "view"
DownloadReportAction ReportAction = "download"
EmailReportAction ReportAction = "email"
)
func (sdk mgSDK) AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(cfg)
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(cfg)
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(map[string]any{"schedule": cfg.Schedule})
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s/schedule", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s/%s", domainID, reportsEndpoint, configsEndpointReports)
url, err := sdk.withQueryParams(sdk.reportsURL, endpoint, pm)
if err != nil {
return ReportConfigPage{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfigPage{}, sdkerr
}
var rcp ReportConfigPage
if err := json.Unmarshal(body, &rcp); err != nil {
return ReportConfigPage{}, errors.NewSDKError(err)
}
return rcp, nil
}
func (sdk mgSDK) EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/enable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/disable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError {
data, err := json.Marshal(cfg)
if err != nil {
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusNoContent)
return sdkerr
}
func (sdk mgSDK) ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return "", sdkerr
}
var rt ReportTemplate
if err := json.Unmarshal(body, &rt); err != nil {
return "", errors.NewSDKError(err)
}
return rt, nil
}
func (sdk mgSDK) DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) GenerateReport(
ctx context.Context,
config ReportConfig,
action ReportAction,
domainID,
token string,
) (ReportPage, *ReportFile, errors.SDKError) {
data, err := json.Marshal(config)
if err != nil {
return ReportPage{}, nil, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s?action=%s",
sdk.reportsURL,
domainID,
reportsEndpoint,
action,
)
headers, body, sdkerr := sdk.processRequest(
ctx,
http.MethodPost,
url,
token,
data,
nil,
http.StatusOK,
)
if sdkerr != nil {
return ReportPage{}, nil, sdkerr
}
// ✅ Handle Download Action
if action == DownloadReportAction {
file := &ReportFile{
Name: extractFilename(headers.Get("Content-Disposition")),
Format: "pdf",
Data: body,
}
return ReportPage{}, file, nil
}
// ✅ Handle JSON response (view/email)
var rp ReportPage
if err := json.Unmarshal(body, &rp); err != nil {
return ReportPage{}, nil, errors.NewSDKError(err)
}
return rp, nil, nil
}
func extractFilename(contentDisposition string) string {
const prefix = "filename="
if idx := strings.Index(contentDisposition, prefix); idx != -1 {
return strings.Trim(contentDisposition[idx+len(prefix):], `"`)
}
return "report"
}
+867
View File
@@ -0,0 +1,867 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"errors"
"net/http/httptest"
"testing"
"time"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/reports"
"github.com/absmach/magistrala/reports/api"
rmocks "github.com/absmach/magistrala/reports/mocks"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const (
reportConfigID = "report-config-1"
name = "daily-report"
updatedName = "updated daily-report"
description = "Daily temperature report"
updatedDescription = "updated Daily temperature report"
validTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #f0f0f0; padding: 10px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{$.Title}}</h1>
<p>Generated on: {{$.GeneratedDate}}</p>
</div>
<div class="content">
<h2>Messages</h2>
{{range .Messages}}
<div class="message">
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
</div>
{{end}}
</div>
</body>
</html>`
)
var (
now = time.Now().UTC().Truncate(time.Minute)
future = now.Add(1 * time.Hour)
schedule = pkgSch.Schedule{
StartDateTime: future,
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: future,
}
metrics = []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
}
config = reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: "test_title",
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
}
email = reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
}
testReportConfig = sdk.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Status: "enabled",
Schedule: schedule,
Metrics: metrics,
Config: &config,
Email: &email,
}
)
func setupReports() (*httptest.Server, *rmocks.Service, *authnmocks.Authentication) {
rsvc := new(rmocks.Service)
log := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := chi.NewRouter()
_ = api.MakeHandler(rsvc, am, mux, log, "")
return httptest.NewServer(mux), rsvc, authn
}
func TestAddReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: "daily-report",
Description: "Daily temperature report",
DomainID: domainID,
Status: reports.EnabledStatus,
Schedule: schedule,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: "test_title",
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
Email: &reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
},
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "add report config successfully",
cfg: testReportConfig,
token: validToken,
svcRes: svcCfg,
},
{
desc: "add report config with empty token",
cfg: sdk.ReportConfig{Name: "daily-report"},
token: "",
wantErr: true,
svcErr: errors.New("missing or invalid bearer user token"),
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("AddReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.AddReportConfig(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Status: reports.EnabledStatus,
Metrics: metrics,
Config: &config,
Email: &email,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "view report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
{
desc: "view non-existent report config",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewReportConfig", mock.Anything, tc.session, tc.id, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
updatedConfig := testReportConfig
updatedConfig.Name = updatedName
updatedConfig.Description = updatedDescription
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: updatedName,
Description: updatedDescription,
DomainID: domainID,
Status: reports.EnabledStatus,
Metrics: metrics,
Config: &config,
Email: &email,
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report config successfully",
cfg: updatedConfig,
token: validToken,
svcRes: svcCfg,
},
{
desc: "update report config with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID, Name: "updated-report"},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateReportConfig(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportSchedule(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: name,
Status: reports.EnabledStatus,
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report schedule successfully",
cfg: sdk.ReportConfig{ID: reportConfigID, Schedule: map[string]any{"cron": "0 9 * * *"}},
token: validToken,
svcRes: svcCfg,
},
{
desc: "update report schedule with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateReportSchedule(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestRemoveReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "remove report config successfully",
id: reportConfigID,
token: validToken,
},
{
desc: "remove report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
{
desc: "remove non-existent report config",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.RemoveReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListReportsConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := reports.ReportConfigPage{}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes reports.ReportConfigPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list reports config successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcPage,
},
{
desc: "list reports config with filters",
pm: sdk.PageMetadata{
Limit: 10,
Name: "daily",
Status: "enabled",
Dir: "desc",
Order: "created_at",
},
token: validToken,
svcRes: svcPage,
},
{
desc: "list reports config with empty metadata excludes filter params",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: reports.ReportConfigPage{},
},
{
desc: "list reports config with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListReportsConfig(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotNil(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Status: reports.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "enable report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "enable report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.EnableReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Status: reports.DisabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "disable report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "disable report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.DisableReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report template successfully",
cfg: sdk.ReportConfig{
ID: reportConfigID,
ReportTemplate: validTemplate,
},
token: validToken,
},
{
desc: "update report template with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportTemplate", mock.Anything, tc.session, mock.Anything).Return(tc.svcErr)
err := mgsdk.UpdateReportTemplate(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcTmpl := reports.ReportTemplate(validTemplate)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportTemplate
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view report template successfully",
id: reportConfigID,
token: validToken,
svcRes: svcTmpl,
},
{
desc: "view report template with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewReportTemplate(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "delete report template successfully",
id: reportConfigID,
token: validToken,
},
{
desc: "delete report template with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DeleteReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.DeleteReportTemplate(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestGenerateReport(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := reports.ReportPage{}
config := sdk.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Metrics: metrics,
Config: &config,
ReportTemplate: reports.ReportTemplate(validTemplate),
}
cases := []struct {
desc string
cfg sdk.ReportConfig
action sdk.ReportAction
token string
session smqauthn.Session
svcRes reports.ReportPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "generate report successfully",
cfg: config,
action: sdk.ViewReportAction,
token: validToken,
svcRes: svcPage,
},
{
desc: "generate report with download action",
cfg: config,
action: sdk.DownloadReportAction,
token: validToken,
svcRes: svcPage,
},
{
desc: "generate report with empty token",
cfg: config,
action: sdk.ViewReportAction,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{
DomainUserID: domainID + "_" + validID,
UserID: validID,
DomainID: domainID,
}
}
authCall := auth.On(
"Authenticate",
mock.Anything,
tc.token,
).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On(
"GenerateReport",
mock.Anything,
tc.session,
mock.Anything,
mock.Anything,
).Return(tc.svcRes, tc.svcErr)
page, file, err := mgsdk.GenerateReport(
context.Background(),
tc.cfg,
tc.action,
domainID,
tc.token,
)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
if tc.action == sdk.DownloadReportAction {
// download should return file
assert.NotNil(t, file)
} else {
// view/email should return page
assert.Equal(t, tc.svcRes.Total, page.Total)
}
}
svcCall.Unset()
authCall.Unset()
})
}
}
+200
View File
@@ -0,0 +1,200 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/absmach/supermq/pkg/errors"
)
const rulesEndpoint = "rules"
// Rule represents a rule configuration.
type Rule struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DomainID string `json:"domain,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
InputTopic string `json:"input_topic,omitempty"`
Logic any `json:"logic,omitempty"`
Outputs any `json:"outputs,omitempty"`
Schedule any `json:"schedule,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type Page struct {
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Total uint64 `json:"total"`
Rules []Rule `json:"rules"`
}
func (sdk mgSDK) AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(r)
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(r)
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(map[string]any{"tags": r.Tags})
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/tags", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(map[string]any{"schedule": r.Schedule})
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/schedule", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s", domainID, rulesEndpoint)
url, err := sdk.withQueryParams(sdk.rulesEngineURL, endpoint, pm)
if err != nil {
return Page{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Page{}, sdkerr
}
var ap Page
if err := json.Unmarshal(body, &ap); err != nil {
return Page{}, errors.NewSDKError(err)
}
return ap, nil
}
func (sdk mgSDK) RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/enable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/disable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
+586
View File
@@ -0,0 +1,586 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"errors"
"net/http/httptest"
"testing"
"github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/api"
remocks "github.com/absmach/magistrala/re/mocks"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/roles"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const ruleID = "rule-1"
var testRule = sdk.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
InputTopic: "sensors/temperature",
Status: "enabled",
Tags: []string{"temperature", "alerts"},
}
func setupRules() (*httptest.Server, *remocks.Service, *authnmocks.Authentication) {
rsvc := new(remocks.Service)
log := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := chi.NewRouter()
_ = api.MakeHandler(rsvc, am, mux, log, "")
return httptest.NewServer(mux), rsvc, authn
}
func TestAddRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
Status: re.EnabledStatus,
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "add rule successfully",
rule: sdk.Rule{Name: "temp-rule", InputChannel: "chan-1"},
token: validToken,
svcRes: svcRule,
},
{
desc: "add rule with empty token",
rule: sdk.Rule{Name: "temp-rule"},
token: "",
wantErr: true,
},
{
desc: "add rule with bad request",
rule: sdk.Rule{},
token: validToken,
svcErr: errors.New("bad request"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("AddRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, []roles.RoleProvision(nil), tc.svcErr)
result, err := mgsdk.AddRule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
Status: re.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "view rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
{
desc: "view non-existent rule",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewRule", mock.Anything, tc.session, tc.id, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
updatedRule := testRule
updatedRule.Name = "updated-rule"
svcRule := re.Rule{
ID: ruleID,
Name: "updated-rule",
InputChannel: "chan-1",
InputTopic: "sensors/temperature",
Status: re.EnabledStatus,
Tags: []string{"temperature", "alerts"},
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule successfully",
rule: updatedRule,
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule with empty token",
rule: updatedRule,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRuleTags(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Tags: []string{"new-tag"},
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule tags successfully",
rule: sdk.Rule{ID: ruleID, Tags: []string{"new-tag"}},
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule tags with empty token",
rule: sdk.Rule{ID: ruleID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRuleTags", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRuleTags(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRuleSchedule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule schedule successfully",
rule: sdk.Rule{ID: ruleID, Schedule: map[string]any{"cron": "0 * * * *"}},
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule schedule with empty token",
rule: sdk.Rule{ID: ruleID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRuleSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRuleSchedule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListRules(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := re.Page{}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes re.Page
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list rules successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcPage,
},
{
desc: "list rules with filters",
pm: sdk.PageMetadata{
Limit: 5,
Name: "temp",
Status: "enabled",
InputChannel: "chan-1",
Tag: "temperature",
Dir: "desc",
Order: "created_at",
},
token: validToken,
svcRes: svcPage,
},
{
desc: "list rules with empty metadata excludes filter params",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: re.Page{},
},
{
desc: "list rules with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ListRules", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListRules(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotNil(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Status: re.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "enable rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "enable rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("EnableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.EnableRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Status: re.DisabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "disable rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "disable rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DisableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.DisableRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestRemoveRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "remove rule successfully",
id: ruleID,
token: validToken,
},
{
desc: "remove rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
{
desc: "remove non-existent rule",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("RemoveRule", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.RemoveRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
+171 -10
View File
@@ -15,6 +15,7 @@ import (
"net/url"
"strconv"
"strings"
"time"
"github.com/absmach/supermq/pkg/errors"
smqSDK "github.com/absmach/supermq/pkg/sdk"
@@ -26,16 +27,33 @@ var _ SDK = (*mgSDK)(nil)
type Metadata map[string]any
type PageMetadata struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Metadata Metadata `json:"metadata,omitempty"`
Topic string `json:"topic,omitempty"`
Contact string `json:"contact,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Level uint64 `json:"level,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name,omitempty"`
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Metadata Metadata `json:"metadata,omitempty"`
Topic string `json:"topic,omitempty"`
Contact string `json:"contact,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Level uint64 `json:"level,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
Dir string `json:"dir,omitempty"`
Order string `json:"order,omitempty"`
Tag string `json:"tag,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
RuleID string `json:"rule_id,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
ClientID string `json:"client_id,omitempty"`
Subtopic string `json:"subtopic,omitempty"`
AssigneeID string `json:"assignee_id,omitempty"`
Severity uint8 `json:"severity,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
AssignedBy string `json:"assigned_by,omitempty"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
ResolvedBy string `json:"resolved_by,omitempty"`
CreatedFrom time.Time `json:"created_from,omitempty"`
CreatedTo time.Time `json:"created_to,omitempty"`
}
type MessagePageMetadata struct {
@@ -190,12 +208,95 @@ type SDK interface {
// err := sdk.DeleteSubscription(ctx, "id", "token")
// fmt.Println(err)
DeleteSubscription(ctx context.Context, id, token string) errors.SDKError
// Alarms API
// UpdateAlarm updates an existing alarm.
UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError)
// ViewAlarm retrieves an alarm by its ID.
ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError)
// ListAlarms retrieves a page of alarms.
ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError)
// DeleteAlarm deletes an alarm.
DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError
// Reports API
// AddReportConfig creates a new report configuration.
AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// ViewReportConfig retrieves a report config by its ID.
ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportConfig updates an existing report configuration.
UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportSchedule updates an existing report configuration's schedule.
UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// RemoveReportConfig deletes a report config.
RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError
// ListReportsConfig retrieves a page of report configs.
ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError)
// EnableReportConfig enables a report config.
EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// DisableReportConfig disables a report config.
DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportTemplate updates a report template.
UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError
// ViewReportTemplate retrieves a report template.
ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError)
// DeleteReportTemplate deletes a report template.
DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError
// GenerateReport generates a report from a configuration.
GenerateReport(ctx context.Context, config ReportConfig, action ReportAction, domainID, token string) (ReportPage, *ReportFile, errors.SDKError)
// Rules Engine API
// AddRule creates a new rule.
AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// ViewRule retrieves a rule by its ID.
ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
// UpdateRule updates an existing rule.
UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// UpdateRuleTags updates an existing rule's tags.
UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// UpdateRuleSchedule updates an existing rule's schedule.
UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// ListRules retrieves a page of rules.
ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError)
// RemoveRule deletes a rule.
RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError
// EnableRule enables a rule.
EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
// DisableRule disables a rule.
DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
}
type mgSDK struct {
bootstrapURL string
readersURL string
usersURL string
alarmsURL string
reportsURL string
rulesEngineURL string
client *http.Client
curlFlag bool
msgContentType smqSDK.ContentType
@@ -216,6 +317,9 @@ type Config struct {
DomainsURL string
JournalURL string
HostURL string
AlarmsURL string
ReportsURL string
RulesEngineURL string
MsgContentType smqSDK.ContentType
TLSVerification bool
@@ -244,6 +348,9 @@ func NewSDK(conf Config) SDK {
bootstrapURL: conf.BootstrapURL,
readersURL: conf.ReaderURL,
usersURL: conf.UsersURL,
alarmsURL: conf.AlarmsURL,
reportsURL: conf.ReportsURL,
rulesEngineURL: conf.RulesEngineURL,
msgContentType: conf.MsgContentType,
client: &http.Client{
@@ -347,6 +454,60 @@ func (pm PageMetadata) query() (string, error) {
if pm.Level != 0 {
q.Add("level", strconv.FormatUint(pm.Level, 10))
}
if pm.Name != "" {
q.Add("name", pm.Name)
}
if pm.Status != "" {
q.Add("status", pm.Status)
}
if pm.Dir != "" {
q.Add("dir", pm.Dir)
}
if pm.Order != "" {
q.Add("order", pm.Order)
}
if pm.Tag != "" {
q.Add("tag", pm.Tag)
}
if pm.InputChannel != "" {
q.Add("input_channel", pm.InputChannel)
}
if pm.RuleID != "" {
q.Add("rule_id", pm.RuleID)
}
if pm.ChannelID != "" {
q.Add("channel_id", pm.ChannelID)
}
if pm.ClientID != "" {
q.Add("client_id", pm.ClientID)
}
if pm.Subtopic != "" {
q.Add("subtopic", pm.Subtopic)
}
if pm.AssigneeID != "" {
q.Add("assignee_id", pm.AssigneeID)
}
if pm.Severity != 0 {
q.Add("severity", strconv.FormatUint(uint64(pm.Severity), 10))
}
if pm.UpdatedBy != "" {
q.Add("updated_by", pm.UpdatedBy)
}
if pm.AssignedBy != "" {
q.Add("assigned_by", pm.AssignedBy)
}
if pm.AcknowledgedBy != "" {
q.Add("acknowledged_by", pm.AcknowledgedBy)
}
if pm.ResolvedBy != "" {
q.Add("resolved_by", pm.ResolvedBy)
}
if !pm.CreatedFrom.IsZero() {
q.Add("created_from", pm.CreatedFrom.UTC().Format(time.RFC3339))
}
if !pm.CreatedTo.IsZero() {
q.Add("created_to", pm.CreatedTo.UTC().Format(time.RFC3339))
}
return q.Encode(), nil
}
+43 -43
View File
@@ -1,52 +1,52 @@
# Provision service
Provision service provides an HTTP API to create initial SuperMQ resources for gateways or edge deployments. It can create clients and channels based on a configurable layout, optionally create bootstrap configurations, whitelist clients, and issue X.509 certificates for mTLS.
Provision service provides an HTTP API to create initial Magistrala resources for gateways or edge deployments. It can create clients and channels based on a configurable layout, optionally create bootstrap configurations, whitelist clients, and issue X.509 certificates for mTLS.
For gateways to communicate with [SuperMQ][supermq], configuration is required (MQTT host, client, channels, certificates). A gateway can fetch bootstrap configuration from the [Bootstrap][bootstrap] service using its `<external_id>` and `<external_key>`. The [Agent][agent] service is typically used on gateways to retrieve that configuration.
For gateways to communicate with [Magistrala][magistrala], configuration is required (MQTT host, client, channels, certificates). A gateway can fetch bootstrap configuration from the [Bootstrap][bootstrap] service using its `<external_id>` and `<external_key>`. The [Agent][agent] service is typically used on gateways to retrieve that configuration.
You can create bootstrap configuration directly via [Bootstrap][bootstrap] or through Provision. [SuperMQ UI][mgxui] uses the Bootstrap service; Provision is intended to automate gateway setups where one physical gateway may require multiple clients and channels (for example, [Agent][agent] and [Export][export]). This setup is defined as a **provision layout**.
You can create bootstrap configuration directly via [Bootstrap][bootstrap] or through Provision. [Magistrala UI][mgxui] uses the Bootstrap service; Provision is intended to automate gateway setups where one physical gateway may require multiple clients and channels (for example, [Agent][agent] and [Export][export]). This setup is defined as a **provision layout**.
## Configuration
The service is configured using environment variables and/or a TOML config file. Defaults below are from `provision/config.go`. Docker add-on examples are in `docker/addons/provision/docker-compose.yaml` and [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). The binary reads `SMQ_PROVISION_*` variables; the add-on compose file uses `MG_PROVISION_*`, so ensure the container receives the expected names.
The service is configured using environment variables and/or a TOML config file. Defaults below are from `provision/config.go`. Docker add-on examples are in `docker/addons/provision/docker-compose.yaml` and [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). The binary reads `MG_PROVISION_*` variables; the add-on compose file uses `MG_PROVISION_*`, so ensure the container receives the expected names.
### Core service
| Variable | Description | Default |
| --- | --- | --- |
| `SMQ_PROVISION_HTTP_PORT` | Provision service listening port | `9016` |
| `SMQ_PROVISION_LOG_LEVEL` | Service log level | `info` |
| `SMQ_PROVISION_ENV_CLIENTS_TLS` | SDK TLS verification | `false` |
| `SMQ_PROVISION_SERVER_CERT` | HTTPS server certificate | "" |
| `SMQ_PROVISION_SERVER_KEY` | HTTPS server key | "" |
| `SMQ_SEND_TELEMETRY` | Send telemetry to SuperMQ call-home server | `true` |
| `SMQ_MQTT_ADAPTER_INSTANCE_ID` | Instance ID used in health output | "" |
| `MG_PROVISION_HTTP_PORT` | Provision service listening port | `9016` |
| `MG_PROVISION_LOG_LEVEL` | Service log level | `info` |
| `MG_PROVISION_ENV_CLIENTS_TLS` | SDK TLS verification | `false` |
| `MG_PROVISION_SERVER_CERT` | HTTPS server certificate | "" |
| `MG_PROVISION_SERVER_KEY` | HTTPS server key | "" |
| `MG_SEND_TELEMETRY` | Send telemetry to Magistrala call-home server | `true` |
| `MG_MQTT_ADAPTER_INSTANCE_ID` | Instance ID used in health output | "" |
### SuperMQ endpoints and credentials
### Magistrala endpoints and credentials
| Variable | Description | Default |
| --- | --- | --- |
| `SMQ_PROVISION_USERS_LOCATION` | Users service URL | `http://localhost` |
| `SMQ_PROVISION_CLIENTS_LOCATION` | Clients service URL | `http://localhost` |
| `SMQ_PROVISION_CERTS_LOCATION` | Certs service URL (certs SDK) | `http://localhost` |
| `SMQ_PROVISION_BS_SVC_URL` | Bootstrap service URL | `http://localhost:9000` |
| `SMQ_PROVISION_CERTS_SVC_URL` | Certs service URL (Magistrala SDK) | `http://localhost:9019` |
| `SMQ_PROVISION_USERNAME` | SuperMQ username | `user` |
| `SMQ_PROVISION_PASS` | SuperMQ password | `test` |
| `SMQ_PROVISION_API_KEY` | SuperMQ authentication token | "" |
| `SMQ_PROVISION_EMAIL` | SuperMQ user email | `test@example.com` |
| `SMQ_PROVISION_DOMAIN_ID` | Default domain ID (unused by HTTP API) | "" |
| `MG_PROVISION_USERS_LOCATION` | Users service URL | `http://localhost` |
| `MG_PROVISION_CLIENTS_LOCATION` | Clients service URL | `http://localhost` |
| `MG_PROVISION_CERTS_LOCATION` | Certs service URL (certs SDK) | `http://localhost` |
| `MG_PROVISION_BS_SVC_URL` | Bootstrap service URL | `http://localhost:9000` |
| `MG_PROVISION_CERTS_SVC_URL` | Certs service URL (Magistrala SDK) | `http://localhost:9019` |
| `MG_PROVISION_USERNAME` | Magistrala username | `user` |
| `MG_PROVISION_PASS` | Magistrala password | `test` |
| `MG_PROVISION_API_KEY` | Magistrala authentication token | "" |
| `MG_PROVISION_EMAIL` | Magistrala user email | `test@example.com` |
| `MG_PROVISION_DOMAIN_ID` | Default domain ID (unused by HTTP API) | "" |
### Provisioning behavior
| Variable | Description | Default |
| --- | --- | --- |
| `SMQ_PROVISION_CONFIG_FILE` | Provision config file | `config.toml` |
| `SMQ_PROVISION_X509_PROVISIONING` | Issue client certificates during provisioning | `false` |
| `SMQ_PROVISION_BS_CONFIG_PROVISIONING` | Save client config in Bootstrap | `true` |
| `SMQ_PROVISION_BS_AUTO_WHITELIST` | Auto-whitelist client | `true` |
| `SMQ_PROVISION_BS_CONTENT` | Bootstrap config content (JSON string) | "" |
| `SMQ_PROVISION_CERTS_HOURS_VALID` | Client cert validity period | `2400h` |
| `MG_PROVISION_CONFIG_FILE` | Provision config file | `config.toml` |
| `MG_PROVISION_X509_PROVISIONING` | Issue client certificates during provisioning | `false` |
| `MG_PROVISION_BS_CONFIG_PROVISIONING` | Save client config in Bootstrap | `true` |
| `MG_PROVISION_BS_AUTO_WHITELIST` | Auto-whitelist client | `true` |
| `MG_PROVISION_BS_CONTENT` | Bootstrap config content (JSON string) | "" |
| `MG_PROVISION_CERTS_HOURS_VALID` | Client cert validity period | `2400h` |
## Features
@@ -66,7 +66,7 @@ Notes:
- At least one client must include `external_id` in metadata. This value is replaced with the `external_id` from the provisioning request and is used for bootstrap creation.
- Channel metadata `type` is reserved for `control`, `data`, and `export` and is used to enrich gateway metadata.
- Bootstrap content can be provided via `bootstrap.content` in the TOML file or as JSON through `SMQ_PROVISION_BS_CONTENT`.
- Bootstrap content can be provided via `bootstrap.content` in the TOML file or as JSON through `MG_PROVISION_BS_CONTENT`.
Example layout:
@@ -98,11 +98,11 @@ Example layout:
## Authentication
Provision uses SuperMQ APIs and requires a valid token. There are three ways to provide it:
Provision uses Magistrala APIs and requires a valid token. There are three ways to provide it:
- `Authorization: Bearer <token>` on each request.
- `SMQ_PROVISION_API_KEY` in env or TOML (used when no header token is provided).
- `SMQ_PROVISION_USERNAME` and `SMQ_PROVISION_PASS` in env or TOML (used to create an access token when no header token is provided).
- `MG_PROVISION_API_KEY` in env or TOML (used when no header token is provided).
- `MG_PROVISION_USERNAME` and `MG_PROVISION_PASS` in env or TOML (used to create an access token when no header token is provided).
`POST /{domainID}/mapping` can create its own token using API key or username/password if no `Authorization` header is provided. The `Authorization` header takes precedence when present. `GET /{domainID}/mapping` always requires a bearer token.
@@ -126,10 +126,10 @@ Standalone:
```bash
make provision
SMQ_PROVISION_BS_SVC_URL=http://localhost:9013 \
SMQ_PROVISION_CLIENTS_LOCATION=http://localhost:9006 \
SMQ_PROVISION_USERS_LOCATION=http://localhost:9002 \
SMQ_PROVISION_CONFIG_FILE=provision/configs/config.toml \
MG_PROVISION_BS_SVC_URL=http://localhost:9013 \
MG_PROVISION_CLIENTS_LOCATION=http://localhost:9006 \
MG_PROVISION_USERS_LOCATION=http://localhost:9002 \
MG_PROVISION_CONFIG_FILE=provision/configs/config.toml \
./build/provision
```
@@ -154,7 +154,7 @@ The Provision service exposes the following endpoints:
When credentials are available via env/config, you can omit the `Authorization` header. `Content-Type` must be exactly `application/json`.
```bash
curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
curl -s -S -X POST http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
-H 'Content-Type: application/json' \
-d '{"name": "gateway-a", "external_id": "33:52:77:99:43", "external_key": "223334fw2"}'
```
@@ -162,7 +162,7 @@ curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping
If you want to supply a token explicitly:
```bash
curl -s -S -X POST http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
curl -s -S -X POST http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
-H "Authorization: Bearer <token|api_key>" \
-H 'Content-Type: application/json' \
-d '{"name": "gateway-a", "external_id": "<external_id>", "external_key": "<external_key>"}'
@@ -207,14 +207,14 @@ Response contains created clients, channels, and optional certificate data:
### Example: Read bootstrap mapping
```bash
curl -s -S -X GET http://localhost:<SMQ_PROVISION_HTTP_PORT>/<domainID>/mapping \
curl -s -S -X GET http://localhost:<MG_PROVISION_HTTP_PORT>/<domainID>/mapping \
-H "Authorization: Bearer <token|api_key>" \
-H 'Content-Type: application/json'
```
## Certificates
When `SMQ_PROVISION_X509_PROVISIONING=true`, the provisioning flow issues certificates for each client and returns them in the response as `client_cert`, `client_key`, and `ca_cert`. The certificate TTL is controlled by `SMQ_PROVISION_CERTS_HOURS_VALID`.
When `MG_PROVISION_X509_PROVISIONING=true`, the provisioning flow issues certificates for each client and returns them in the response as `client_cert`, `client_key`, and `ca_cert`. The certificate TTL is controlled by `MG_PROVISION_CERTS_HOURS_VALID`.
## Testing
@@ -225,8 +225,8 @@ go test ./provision/...
For an in-depth explanation of our Provision Service, see the [official documentation][doc].
[doc]: https://docs.magistrala.absmach.eu/dev-guide/provision/
[supermq]: https://github.com/absmach/supermq
[bootstrap]: https://github.com/absmach/supermq/tree/main/bootstrap
[magistrala]: https://github.com/absmach/magistrala
[bootstrap]: https://github.com/absmach/magistrala/tree/main/bootstrap
[export]: https://github.com/absmach/export
[agent]: https://github.com/absmach/agent
[mgxui]: https://github.com/absmach/supermq/ui
[mgxui]: https://github.com/absmach/magistrala/ui
+31 -10
View File
@@ -8,18 +8,24 @@ import (
"github.com/absmach/magistrala/provision"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/go-kit/kit/endpoint"
)
func doProvision(svc provision.Service) endpoint.Endpoint {
return func(ctx context.Context, request any) (any, error) {
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(provisionReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
res, err := svc.Provision(ctx, req.domainID, req.token, req.Name, req.ExternalID, req.ExternalKey)
res, err := svc.Provision(ctx, session.DomainID, req.token, req.Name, req.ExternalID, req.ExternalKey)
if err != nil {
return nil, err
}
@@ -39,16 +45,31 @@ func doProvision(svc provision.Service) endpoint.Endpoint {
func getMapping(svc provision.Service) endpoint.Endpoint {
return func(ctx context.Context, request any) (any, error) {
req := request.(mappingReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
res, err := svc.Mapping(ctx, req.token)
if err != nil {
return nil, err
}
res := svc.Mapping()
return mappingRes{Data: res}, nil
}
}
func issueCert(svc provision.Service) endpoint.Endpoint {
return func(ctx context.Context, request any) (any, error) {
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(certReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
cert, key, err := svc.Cert(ctx, session.DomainID, req.token, req.ClientID, req.TTL)
if err != nil {
return nil, err
}
return certRes{
Certificate: cert,
Key: key,
}, nil
}
}
+129 -24
View File
@@ -16,7 +16,11 @@ import (
"github.com/absmach/magistrala/provision/api"
mocks "github.com/absmach/magistrala/provision/mocks"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/auth"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -26,6 +30,13 @@ var (
validToken = "valid"
validContenType = "application/json"
validID = testsutil.GenerateUUID(&testing.T{})
userID = testsutil.GenerateUUID(&testing.T{})
domainID = testsutil.GenerateUUID(&testing.T{})
validSession = smqauthn.Session{
DomainUserID: auth.EncodeDomainUserID(domainID, userID),
UserID: userID,
DomainID: domainID,
}
)
type testRequest struct {
@@ -54,16 +65,18 @@ func (tr testRequest) make() (*http.Response, error) {
return tr.client.Do(req)
}
func newProvisionServer() (*httptest.Server, *mocks.Service) {
func newProvisionServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
svc := new(mocks.Service)
logger := smqlog.NewMock()
mux := api.MakeHandler(svc, logger, "test")
return httptest.NewServer(mux), svc
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := api.MakeHandler(svc, am, logger, "test")
return httptest.NewServer(mux), svc, authn
}
func TestProvision(t *testing.T) {
is, svc := newProvisionServer()
is, svc, authn := newProvisionServer()
cases := []struct {
desc string
@@ -72,6 +85,8 @@ func TestProvision(t *testing.T) {
data string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcErr error
}{
{
@@ -81,6 +96,7 @@ func TestProvision(t *testing.T) {
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusCreated,
contentType: validContenType,
authnRes: validSession,
svcErr: nil,
},
{
@@ -90,7 +106,7 @@ func TestProvision(t *testing.T) {
data: fmt.Sprintf(`{"name": "test", "external_key": "%s"}`, validID),
status: http.StatusBadRequest,
contentType: validContenType,
svcErr: nil,
authnRes: validSession,
},
{
desc: "request with empty external key",
@@ -99,6 +115,7 @@ func TestProvision(t *testing.T) {
data: fmt.Sprintf(`{"name": "test", "external_id": "%s"}`, validID),
status: http.StatusUnauthorized,
contentType: validContenType,
authnRes: validSession,
svcErr: nil,
},
{
@@ -106,8 +123,10 @@ func TestProvision(t *testing.T) {
token: "",
domainID: validID,
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusCreated,
status: http.StatusUnauthorized,
contentType: validContenType,
authnRes: smqauthn.Session{},
authnErr: errors.ErrAuthentication,
svcErr: nil,
},
{
@@ -117,6 +136,7 @@ func TestProvision(t *testing.T) {
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusUnsupportedMediaType,
contentType: "text/plain",
authnRes: validSession,
svcErr: nil,
},
{
@@ -126,6 +146,7 @@ func TestProvision(t *testing.T) {
data: `data`,
status: http.StatusBadRequest,
contentType: validContenType,
authnRes: validSession,
svcErr: nil,
},
{
@@ -135,12 +156,14 @@ func TestProvision(t *testing.T) {
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusForbidden,
contentType: validContenType,
authnRes: validSession,
svcErr: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
repocall := svc.On("Provision", mock.Anything, validID, tc.token, "test", validID, validID).Return(provision.Result{}, tc.svcErr)
req := testRequest{
client: is.Client(),
@@ -154,13 +177,14 @@ func TestProvision(t *testing.T) {
resp, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
authCall.Unset()
repocall.Unset()
})
}
}
func TestMapping(t *testing.T) {
is, svc := newProvisionServer()
is, svc, authn := newProvisionServer()
cases := []struct {
desc string
@@ -168,6 +192,8 @@ func TestMapping(t *testing.T) {
domainID string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcErr error
}{
{
@@ -177,6 +203,8 @@ func TestMapping(t *testing.T) {
status: http.StatusOK,
contentType: validContenType,
svcErr: nil,
authnRes: validSession,
authnErr: nil,
},
{
desc: "empty token",
@@ -185,28 +213,15 @@ func TestMapping(t *testing.T) {
status: http.StatusUnauthorized,
contentType: validContenType,
svcErr: nil,
},
{
desc: "invalid content type",
token: validToken,
domainID: validID,
status: http.StatusUnsupportedMediaType,
contentType: "text/plain",
svcErr: nil,
},
{
desc: "service error",
token: validToken,
domainID: validID,
status: http.StatusForbidden,
contentType: validContenType,
svcErr: svcerr.ErrAuthorization,
authnRes: smqauthn.Session{},
authnErr: errors.ErrAuthentication,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repocall := svc.On("Mapping", mock.Anything, tc.token).Return(map[string]any{}, tc.svcErr)
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
repocall := svc.On("Mapping").Return(map[string]any{}, tc.svcErr)
req := testRequest{
client: is.Client(),
method: http.MethodGet,
@@ -218,6 +233,96 @@ func TestMapping(t *testing.T) {
resp, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
authCall.Unset()
repocall.Unset()
})
}
}
func TestCert(t *testing.T) {
is, svc, authn := newProvisionServer()
cases := []struct {
desc string
token string
domainID string
data string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcErr error
}{
{
desc: "valid request",
token: validToken,
domainID: validID,
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
status: http.StatusCreated,
contentType: validContenType,
authnRes: validSession,
svcErr: nil,
},
{
desc: "empty token",
token: "",
domainID: validID,
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
status: http.StatusUnauthorized,
contentType: validContenType,
authnRes: smqauthn.Session{},
authnErr: errors.ErrAuthentication,
svcErr: nil,
},
{
desc: "invalid content type",
token: validToken,
domainID: validID,
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
status: http.StatusUnsupportedMediaType,
contentType: "text/plain",
authnRes: validSession,
svcErr: nil,
},
{
desc: "invalid request",
token: validToken,
domainID: validID,
data: `data`,
status: http.StatusBadRequest,
contentType: validContenType,
authnRes: validSession,
svcErr: nil,
},
{
desc: "service error",
token: validToken,
domainID: validID,
data: fmt.Sprintf(`{"client_id": "%s", "ttl": "1h"}`, validID),
status: http.StatusForbidden,
contentType: validContenType,
authnRes: validSession,
svcErr: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
repocall := svc.On("Cert", mock.Anything, validID, tc.token, validID, "1h").Return("cert", "key", tc.svcErr)
req := testRequest{
client: is.Client(),
method: http.MethodPost,
url: is.URL + fmt.Sprintf("/%s/cert", tc.domainID),
token: tc.token,
contentType: tc.contentType,
body: strings.NewReader(tc.data),
}
resp, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
authCall.Unset()
repocall.Unset()
})
}
+7 -12
View File
@@ -9,7 +9,6 @@ import (
type provisionReq struct {
token string
domainID string
Name string `json:"name"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"`
@@ -19,9 +18,6 @@ func (req provisionReq) validate() error {
if req.ExternalID == "" {
return apiutil.ErrMissingID
}
if req.domainID == "" {
return apiutil.ErrMissingDomainID
}
if req.ExternalKey == "" {
return apiutil.ErrBearerKey
@@ -34,17 +30,16 @@ func (req provisionReq) validate() error {
return nil
}
type mappingReq struct {
type certReq struct {
token string
domainID string
ClientID string `json:"client_id"`
TTL string `json:"ttl,omitempty"`
}
func (req mappingReq) validate() error {
if req.token == "" {
return apiutil.ErrBearerToken
}
if req.domainID == "" {
return apiutil.ErrMissingDomainID
func (req certReq) validate() error {
if req.ClientID == "" {
return apiutil.ErrMissingID
}
return nil
}
-52
View File
@@ -23,7 +23,6 @@ func TestProvisioReq(t *testing.T) {
desc: "valid request",
req: provisionReq{
token: "token",
domainID: testsutil.GenerateUUID(t),
Name: "name",
ExternalID: testsutil.GenerateUUID(t),
ExternalKey: testsutil.GenerateUUID(t),
@@ -34,29 +33,16 @@ func TestProvisioReq(t *testing.T) {
desc: "empty external id",
req: provisionReq{
token: "token",
domainID: testsutil.GenerateUUID(t),
Name: "name",
ExternalID: "",
ExternalKey: testsutil.GenerateUUID(t),
},
err: apiutil.ErrMissingID,
},
{
desc: "empty domain id",
req: provisionReq{
token: "token",
domainID: "",
Name: "name",
ExternalID: testsutil.GenerateUUID(t),
ExternalKey: testsutil.GenerateUUID(t),
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "empty external key",
req: provisionReq{
token: "token",
domainID: testsutil.GenerateUUID(t),
Name: "name",
ExternalID: testsutil.GenerateUUID(t),
ExternalKey: "",
@@ -70,41 +56,3 @@ func TestProvisioReq(t *testing.T) {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
}
}
func TestMappingReq(t *testing.T) {
cases := []struct {
desc string
req mappingReq
err error
}{
{
desc: "valid request",
req: mappingReq{
token: "token",
domainID: testsutil.GenerateUUID(t),
},
err: nil,
},
{
desc: "empty token",
req: mappingReq{
token: "",
domainID: testsutil.GenerateUUID(t),
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty domain id",
req: mappingReq{
token: "token",
domainID: "",
},
err: apiutil.ErrMissingDomainID,
},
}
for _, tc := range cases {
err := tc.req.validate()
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
}
}
+17
View File
@@ -50,6 +50,23 @@ func (res mappingRes) Empty() bool {
return false
}
type certRes struct {
Certificate string `json:"certificate"`
Key string `json:"key"`
}
func (res certRes) Code() int {
return http.StatusCreated
}
func (res certRes) Headers() map[string]string {
return map[string]string{}
}
func (res certRes) Empty() bool {
return false
}
func (res mappingRes) MarshalJSON() ([]byte, error) {
return json.Marshal(res.Data)
}
+19 -6
View File
@@ -13,6 +13,7 @@ import (
"github.com/absmach/supermq"
api "github.com/absmach/supermq/api/http"
apiutil "github.com/absmach/supermq/api/http/util"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
"github.com/go-chi/chi/v5"
kithttp "github.com/go-kit/kit/transport/http"
@@ -24,7 +25,7 @@ const (
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler {
func MakeHandler(svc provision.Service, authn smqauthn.AuthNMiddleware, logger *slog.Logger, instanceID string) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
}
@@ -32,6 +33,7 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string)
r := chi.NewRouter()
r.Route("/{domainID}", func(r chi.Router) {
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
r.Route("/mapping", func(r chi.Router) {
r.Post("/", kithttp.NewServer(
doProvision(svc),
@@ -46,6 +48,12 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string)
opts...,
).ServeHTTP)
})
r.Post("/cert", kithttp.NewServer(
issueCert(svc),
decodeCertRequest,
api.EncodeResponse,
opts...,
).ServeHTTP)
})
r.Handle("/metrics", promhttp.Handler())
r.Get("/health", supermq.Health("provision", instanceID))
@@ -59,8 +67,7 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) {
}
req := provisionReq{
token: apiutil.ExtractBearerToken(r),
domainID: chi.URLParam(r, "domainID"),
token: apiutil.ExtractBearerToken(r),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
@@ -70,13 +77,19 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) {
}
func decodeMappingRequest(_ context.Context, r *http.Request) (any, error) {
return nil, nil
}
func decodeCertRequest(_ context.Context, r *http.Request) (any, error) {
if r.Header.Get("Content-Type") != contentType {
return nil, apiutil.ErrUnsupportedContentType
}
req := mappingReq{
token: apiutil.ExtractBearerToken(r),
domainID: chi.URLParam(r, "domainID"),
req := certReq{
token: apiutil.ExtractBearerToken(r),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
+17 -18
View File
@@ -17,22 +17,21 @@ var errFailedToReadConfig = errors.New("failed to read config file")
// ServiceConf represents service config.
type ServiceConf struct {
Port string `toml:"port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"`
LogLevel string `toml:"log_level" env:"SMQ_PROVISION_LOG_LEVEL" envDefault:"info"`
TLS bool `toml:"tls" env:"SMQ_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"`
ServerCert string `toml:"server_cert" env:"SMQ_PROVISION_SERVER_CERT" envDefault:""`
ServerKey string `toml:"server_key" env:"SMQ_PROVISION_SERVER_KEY" envDefault:""`
ClientsURL string `toml:"clients_url" env:"SMQ_PROVISION_CLIENTS_LOCATION" envDefault:"http://localhost"`
UsersURL string `toml:"users_url" env:"SMQ_PROVISION_USERS_LOCATION" envDefault:"http://localhost"`
CertsURL string `toml:"certs_url" env:"SMQ_PROVISION_CERTS_LOCATION" envDefault:"http://localhost"`
HTTPPort string `toml:"http_port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"`
MgEmail string `toml:"smq_email" env:"SMQ_PROVISION_EMAIL" envDefault:"test@example.com"`
MgUsername string `toml:"smq_username" env:"SMQ_PROVISION_USERNAME" envDefault:"user"`
MgPass string `toml:"smq_pass" env:"SMQ_PROVISION_PASS" envDefault:"test"`
MgDomainID string `toml:"smq_domain_id" env:"SMQ_PROVISION_DOMAIN_ID" envDefault:""`
MgAPIKey string `toml:"smq_api_key" env:"SMQ_PROVISION_API_KEY" envDefault:""`
MgBSURL string `toml:"smq_bs_url" env:"SMQ_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"`
MgCertsURL string `toml:"smq_certs_url" env:"SMQ_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"`
Port string `toml:"port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"`
LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"`
TLS bool `toml:"tls" env:"MG_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"`
ServerCert string `toml:"server_cert" env:"MG_PROVISION_SERVER_CERT" envDefault:""`
ServerKey string `toml:"server_key" env:"MG_PROVISION_SERVER_KEY" envDefault:""`
ClientsURL string `toml:"clients_url" env:"MG_PROVISION_CLIENTS_URL" envDefault:"http://localhost"`
ChannelsURL string `toml:"channels_url" env:"MG_PROVISION_CHANNELS_URL" envDefault:"http://localhost"`
UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_URL" envDefault:"http://localhost"`
CertsURL string `toml:"certs_url" env:"MG_PROVISION_CERTS_URL" envDefault:"http://localhost"`
MgEmail string `toml:"mg_email" env:"MG_PROVISION_EMAIL" envDefault:"test@example.com"`
MgUsername string `toml:"mg_username" env:"MG_PROVISION_USERNAME" envDefault:"user"`
MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"`
MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""`
MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""`
MgBSURL string `toml:"mg_bs_url" env:"MG_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"`
}
// Bootstrap represetns the Bootstrap config.
@@ -61,13 +60,13 @@ type Cert struct {
// Config struct of Provision.
type Config struct {
File string `toml:"file" env:"SMQ_PROVISION_CONFIG_FILE" envDefault:"config.toml"`
File string `toml:"file" env:"MG_PROVISION_CONFIG_FILE" envDefault:"config.toml"`
Server ServiceConf `toml:"server" mapstructure:"server"`
Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"`
Clients []clients.Client `toml:"clients" mapstructure:"clients"`
Channels []channels.Channel `toml:"channels" mapstructure:"channels"`
Cert Cert `toml:"cert" mapstructure:"cert"`
BSContent string `env:"SMQ_PROVISION_BS_CONTENT" envDefault:""`
BSContent string `env:"MG_PROVISION_BS_CONTENT" envDefault:""`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""`
}
+1
View File
@@ -40,6 +40,7 @@ var (
Metadata: map[string]any{
"test": "test",
},
PrivateMetadata: clients.Metadata{},
Actions: []string{},
AccessProviderRoleActions: []string{},
ConnectionTypes: []connections.ConnType{},
@@ -1,9 +1,7 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package api
package middleware
import (
"context"
@@ -20,8 +18,8 @@ type loggingMiddleware struct {
svc provision.Service
}
// NewLoggingMiddleware adds logging facilities to the core service.
func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service {
// NewLogging adds logging facilities to the core service.
func NewLogging(svc provision.Service, logger *slog.Logger) provision.Service {
return &loggingMiddleware{logger, svc}
}
@@ -33,7 +31,7 @@ func (lm *loggingMiddleware) Provision(ctx context.Context, domainID, token, nam
slog.String("external_id", externalID),
}
if err != nil {
args = append(args, slog.Any("error", err))
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Provision failed", args...)
return
}
@@ -51,8 +49,8 @@ func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID
slog.String("ttl", duration),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Client certificate failed to create successfully", args...)
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Client certificate creation failed", args...)
return
}
lm.logger.Info("Client certificate created successfully", args...)
@@ -61,18 +59,13 @@ func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID
return lm.svc.Cert(ctx, domainID, token, clientID, duration)
}
func (lm *loggingMiddleware) Mapping(ctx context.Context, token string) (res map[string]any, err error) {
func (lm *loggingMiddleware) Mapping() (res map[string]any) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Mapping failed", args...)
return
}
lm.logger.Info("Mapping completed successfully", args...)
}(time.Now())
return lm.svc.Mapping(ctx, token)
return lm.svc.Mapping()
}
+12 -34
View File
@@ -133,31 +133,22 @@ func (_c *Service_Cert_Call) RunAndReturn(run func(ctx context.Context, domainID
}
// Mapping provides a mock function for the type Service
func (_mock *Service) Mapping(ctx context.Context, token string) (map[string]any, error) {
ret := _mock.Called(ctx, token)
func (_mock *Service) Mapping() map[string]any {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Mapping")
}
var r0 map[string]any
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (map[string]any, error)); ok {
return returnFunc(ctx, token)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string) map[string]any); ok {
r0 = returnFunc(ctx, token)
if returnFunc, ok := ret.Get(0).(func() map[string]any); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]any)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = returnFunc(ctx, token)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// Service_Mapping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mapping'
@@ -166,36 +157,23 @@ type Service_Mapping_Call struct {
}
// Mapping is a helper method to define mock.On call
// - ctx context.Context
// - token string
func (_e *Service_Expecter) Mapping(ctx interface{}, token interface{}) *Service_Mapping_Call {
return &Service_Mapping_Call{Call: _e.mock.On("Mapping", ctx, token)}
func (_e *Service_Expecter) Mapping() *Service_Mapping_Call {
return &Service_Mapping_Call{Call: _e.mock.On("Mapping")}
}
func (_c *Service_Mapping_Call) Run(run func(ctx context.Context, token string)) *Service_Mapping_Call {
func (_c *Service_Mapping_Call) Run(run func()) *Service_Mapping_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 string
if args[1] != nil {
arg1 = args[1].(string)
}
run(
arg0,
arg1,
)
run()
})
return _c
}
func (_c *Service_Mapping_Call) Return(stringToV map[string]any, err error) *Service_Mapping_Call {
_c.Call.Return(stringToV, err)
func (_c *Service_Mapping_Call) Return(stringToV map[string]any) *Service_Mapping_Call {
_c.Call.Return(stringToV)
return _c
}
func (_c *Service_Mapping_Call) RunAndReturn(run func(ctx context.Context, token string) (map[string]any, error)) *Service_Mapping_Call {
func (_c *Service_Mapping_Call) RunAndReturn(run func() map[string]any) *Service_Mapping_Call {
_c.Call.Return(run)
return _c
}
+22 -34
View File
@@ -27,25 +27,22 @@ const (
)
var (
ErrUnauthorized = errors.New("unauthorized access")
ErrFailedToCreateToken = errors.New("failed to create access token")
ErrEmptyClientsList = errors.New("clients list in configuration empty")
ErrClientUpdate = errors.New("failed to update client")
ErrEmptyChannelsList = errors.New("channels list in configuration is empty")
ErrFailedChannelCreation = errors.New("failed to create channel")
ErrFailedChannelRetrieval = errors.New("failed to retrieve channel")
ErrFailedClientCreation = errors.New("failed to create client")
ErrFailedClientRetrieval = errors.New("failed to retrieve client")
ErrMissingCredentials = errors.New("missing credentials")
ErrFailedBootstrapRetrieval = errors.New("failed to retrieve bootstrap")
ErrFailedCertCreation = errors.New("failed to create certificates")
ErrFailedCertView = errors.New("failed to view certificate")
ErrFailedBootstrap = errors.New("failed to create bootstrap config")
ErrFailedBootstrapValidate = errors.New("failed to validate bootstrap config creation")
ErrGatewayUpdate = errors.New("failed to updated gateway metadata")
limit uint = 10
offset uint = 0
ErrUnauthorized = errors.NewAuthNError("unauthorized access")
ErrFailedToCreateToken = errors.NewAuthNError("failed to create access token")
ErrEmptyClientsList = errors.NewRequestError("clients list in configuration empty")
ErrClientUpdate = errors.NewRequestError("failed to update client")
ErrEmptyChannelsList = errors.NewRequestError("channels list in configuration is empty")
ErrFailedChannelCreation = errors.NewRequestError("failed to create channel")
ErrFailedChannelRetrieval = errors.NewRequestError("failed to retrieve channel")
ErrFailedClientCreation = errors.NewRequestError("failed to create client")
ErrFailedClientRetrieval = errors.NewRequestError("failed to retrieve client")
ErrMissingCredentials = errors.NewRequestError("missing credentials")
ErrFailedBootstrapRetrieval = errors.NewServiceError("failed to retrieve bootstrap")
ErrFailedCertCreation = errors.NewServiceError("failed to create certificates")
ErrFailedCertView = errors.NewServiceError("failed to view certificate")
ErrFailedBootstrap = errors.NewServiceError("failed to create bootstrap config")
ErrFailedBootstrapValidate = errors.NewServiceError("failed to validate bootstrap config creation")
ErrGatewayUpdate = errors.NewServiceError("failed to update gateway metadata")
)
var _ Service = (*provisionService)(nil)
@@ -63,7 +60,7 @@ type Service interface {
// Mapping returns current configuration used for provision
// useful for using in ui to create configuration that matches
// one created with Provision method.
Mapping(ctx context.Context, token string) (map[string]any, error)
Mapping() map[string]any
// Certs creates certificate for clients that communicate over mTLS
// A duration string is a possibly signed sequence of decimal numbers,
@@ -101,17 +98,8 @@ func New(cfg Config, mgsdk sdk.SDK, certsSdk csdk.SDK, logger *slog.Logger) Serv
}
// Mapping retrieves current configuration.
func (ps *provisionService) Mapping(ctx context.Context, token string) (map[string]any, error) {
pm := smqSDK.PageMetadata{
Offset: uint64(offset),
Limit: uint64(limit),
}
if _, err := ps.sdk.Users(ctx, pm, token); err != nil {
return map[string]any{}, errors.Wrap(ErrUnauthorized, err)
}
return ps.conf.Bootstrap.Content, nil
func (ps *provisionService) Mapping() map[string]any {
return ps.conf.Bootstrap.Content
}
// Provision is provision method for creating setup according to
@@ -119,7 +107,7 @@ func (ps *provisionService) Mapping(ctx context.Context, token string) (map[stri
func (ps *provisionService) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res Result, err error) {
var channels []smqSDK.Channel
var clients []smqSDK.Client
defer ps.recover(ctx, &err, &clients, &channels, &domainID, &token)
defer ps.recover(ctx, &err, &clients, &channels, domainID, token)
token, err = ps.createTokenIfEmpty(ctx, token)
if err != nil {
@@ -360,11 +348,11 @@ func clean(ctx context.Context, ps *provisionService, clients []smqSDK.Client, c
}
}
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, dm, tkn *string) {
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, domainID, token string) {
if e == nil {
return
}
clients, channels, domainID, token, err := *ths, *chs, *dm, *tkn, *e
clients, channels, err := *ths, *chs, *e
if errors.Contains(err, ErrFailedClientRetrieval) || errors.Contains(err, ErrFailedChannelCreation) {
for _, c := range clients {
+2 -15
View File
@@ -31,35 +31,22 @@ func TestMapping(t *testing.T) {
cases := []struct {
desc string
token string
content map[string]any
sdkerr error
err error
}{
{
desc: "valid token",
token: validToken,
desc: "valid request",
content: validConfig.Bootstrap.Content,
sdkerr: nil,
err: nil,
},
{
desc: "invalid token",
token: "invalid",
content: map[string]any{},
sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401),
err: provision.ErrUnauthorized,
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
pm := smqSDK.PageMetadata{Offset: uint64(0), Limit: uint64(10)}
repocall := mgsdk.On("Users", mock.Anything, pm, c.token).Return(smqSDK.UsersPage{}, c.sdkerr)
content, err := svc.Mapping(context.Background(), c.token)
assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected error %v, got %v", c.err, err))
content := svc.Mapping()
assert.Equal(t, c.content, content)
repocall.Unset()
})
}
}
+2 -2
View File
@@ -25,7 +25,7 @@ func addRuleEndpoint(s re.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return addRuleRes{}, err
}
rule, err := s.AddRule(ctx, session, req.Rule)
rule, _, err := s.AddRule(ctx, session, req.Rule)
if err != nil {
return addRuleRes{}, err
}
@@ -44,7 +44,7 @@ func viewRuleEndpoint(s re.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return viewRuleRes{}, err
}
rule, err := s.ViewRule(ctx, session, req.id)
rule, err := s.ViewRule(ctx, session, req.id, req.withRoles)
if err != nil {
return viewRuleRes{}, err
}
+3 -2
View File
@@ -26,6 +26,7 @@ import (
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/roles"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -236,7 +237,7 @@ func TestAddRuleEndpoint(t *testing.T) {
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("AddRule", mock.Anything, tc.authnRes, tc.rule).Return(tc.svcRes, tc.svcErr)
svcCall := svc.On("AddRule", mock.Anything, tc.authnRes, tc.rule).Return(tc.svcRes, []roles.RoleProvision{}, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
@@ -334,7 +335,7 @@ func TestViewRuleEndpoint(t *testing.T) {
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("ViewRule", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
svcCall := svc.On("ViewRule", mock.Anything, tc.authnRes, tc.id, false).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
+2 -1
View File
@@ -33,7 +33,8 @@ func (req addRuleReq) validate() error {
}
type viewRuleReq struct {
id string
id string
withRoles bool
}
func (req viewRuleReq) validate() error {
+12 -1
View File
@@ -16,6 +16,7 @@ import (
apiutil "github.com/absmach/supermq/api/http/util"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
roleManagerHttp "github.com/absmach/supermq/pkg/roles/rolemanager/api"
"github.com/go-chi/chi/v5"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -36,6 +37,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
r.Route("/{domainID}", func(r chi.Router) {
r.Route("/rules", func(r chi.Router) {
d := roleManagerHttp.NewDecoder("ruleID")
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
addRuleEndpoint(svc),
decodeAddRuleRequest,
@@ -50,6 +53,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
opts...,
), "list_rules").ServeHTTP)
r = roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts)
r.Route("/{ruleID}", func(r chi.Router) {
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
viewRuleEndpoint(svc),
@@ -99,6 +104,8 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l
api.EncodeResponse,
opts...,
), "disable_rule").ServeHTTP)
roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts)
})
})
})
@@ -123,7 +130,11 @@ func decodeAddRuleRequest(_ context.Context, r *http.Request) (any, error) {
func decodeViewRuleRequest(_ context.Context, r *http.Request) (any, error) {
id := chi.URLParam(r, ruleIdKey)
return viewRuleReq{id: id}, nil
withRoles, err := apiutil.ReadBoolQuery(r, api.RolesKey, false)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
return viewRuleReq{id: id, withRoles: withRoles}, nil
}
func decodeUpdateRuleRequest(_ context.Context, r *http.Request) (any, error) {
+8
View File
@@ -0,0 +1,8 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
import "github.com/absmach/supermq/pkg/roles"
const BuiltInRoleAdmin roles.BuiltInRoleName = "admin"
+4 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/events"
"github.com/absmach/supermq/pkg/roles"
)
const (
@@ -59,7 +60,8 @@ func (bre baseRuleEvent) Encode() map[string]any {
}
type createRuleEvent struct {
rule re.Rule
rule re.Rule
rolesProvisioned []roles.RoleProvision
baseRuleEvent
}
@@ -70,6 +72,7 @@ func (cre createRuleEvent) Encode() (map[string]any, error) {
}
maps.Copy(val, cre.baseRuleEvent.Encode())
val["operation"] = ruleCreate
val["roles_provisioned"] = cre.rolesProvisioned
return val, nil
}
+18 -11
View File
@@ -11,6 +11,8 @@ import (
"github.com/absmach/supermq/pkg/events"
"github.com/absmach/supermq/pkg/events/store"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/roles"
rmEvents "github.com/absmach/supermq/pkg/roles/rolemanager/events"
"github.com/go-chi/chi/v5/middleware"
)
@@ -32,6 +34,7 @@ var _ re.Service = (*eventStore)(nil)
type eventStore struct {
events.Publisher
svc re.Service
rmEvents.RoleManagerEventStore
}
// NewEventStoreMiddleware returns wrapper around rules service that sends
@@ -42,25 +45,29 @@ func NewEventStoreMiddleware(ctx context.Context, svc re.Service, url string) (r
return nil, err
}
res := rmEvents.NewRoleManagerEventStore("rules", rulePrefix, svc, publisher)
return &eventStore{
svc: svc,
Publisher: publisher,
svc: svc,
Publisher: publisher,
RoleManagerEventStore: res,
}, nil
}
func (es *eventStore) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
rule, err := es.svc.AddRule(ctx, session, r)
func (es *eventStore) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
rule, rps, err := es.svc.AddRule(ctx, session, r)
if err != nil {
return rule, err
return rule, rps, err
}
event := createRuleEvent{
rule: rule,
baseRuleEvent: newBaseRuleEvent(session, middleware.GetReqID(ctx)),
rule: rule,
rolesProvisioned: rps,
baseRuleEvent: newBaseRuleEvent(session, middleware.GetReqID(ctx)),
}
if err := es.Publish(ctx, CreateStream, event); err != nil {
return rule, err
return rule, rps, err
}
return rule, nil
return rule, rps, nil
}
func (es *eventStore) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
@@ -78,8 +85,8 @@ func (es *eventStore) ListRules(ctx context.Context, session authn.Session, pm r
return page, nil
}
func (es *eventStore) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
rule, err := es.svc.ViewRule(ctx, session, id)
func (es *eventStore) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
rule, err := es.svc.ViewRule(ctx, session, id, withRoles)
if err != nil {
return rule, err
}
+18 -2
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"log/slog"
"reflect"
"regexp"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/supermq/pkg/errors"
@@ -19,6 +20,11 @@ import (
const logicFunction = "main.logicFunction"
var (
goKeywordRegex = regexp.MustCompile(`\bgo\s+func\s*\(|^\s*go\s+\w+\(|[;\s{]go\s+func\s*\(|[;\s{]go\s+\w+\(`)
panicRegex = regexp.MustCompile(`\bpanic\s*\(`)
)
// Type message is an SMQ message with payload replaces by JSON deserialized payload.
type message struct {
Channel string `json:"channel,omitempty"`
@@ -30,7 +36,17 @@ type message struct {
Payload any `json:"payload,omitempty"`
}
func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *messaging.Message) pkglog.RunInfo {
func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *messaging.Message) (ret pkglog.RunInfo) {
defer func() {
if r := recover(); r != nil {
ret = pkglog.RunInfo{
Level: slog.LevelError,
Details: details,
Message: fmt.Sprintf("panic in Go script: %v", r),
}
}
}()
i := golang.New(golang.Options{})
if err := i.Use(stdlib.Symbols); err != nil {
return pkglog.RunInfo{Level: slog.LevelError, Details: details, Message: err.Error()}
@@ -77,7 +93,7 @@ func (re *re) processGo(ctx context.Context, details []slog.Attr, r Rule, msg *m
err = errors.Wrap(e, err)
}
}
ret := pkglog.RunInfo{Level: slog.LevelInfo, Details: details, Message: "rule processed successfully"}
ret = pkglog.RunInfo{Level: slog.LevelInfo, Details: details, Message: "rule processed successfully"}
if err != nil {
ret.Level = slog.LevelError
ret.Message = fmt.Sprintf("failed to handle rule output: %s", err)
+2 -2
View File
@@ -43,7 +43,7 @@ func (re *re) Handle(msg *messaging.Message) error {
Scheduled: &scheduledFalse,
}
ctx := context.Background()
page, err := re.repo.ListRules(ctx, pm)
page, err := re.repo.ListAllRules(ctx, pm)
if err != nil {
return err
}
@@ -130,7 +130,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
ScheduledBefore: &due,
}
page, err := re.repo.ListRules(ctx, pm)
page, err := re.repo.ListAllRules(ctx, pm)
if err != nil {
re.runInfo <- pkglog.RunInfo{
Level: slog.LevelError,
+77 -27
View File
@@ -7,12 +7,16 @@ import (
"context"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/operations"
"github.com/absmach/supermq/pkg/authn"
smqauthz "github.com/absmach/supermq/pkg/authz"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/roles"
rolemgr "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
)
var (
@@ -23,36 +27,47 @@ var (
)
type authorizationMiddleware struct {
svc re.Service
authz smqauthz.Authorization
svc re.Service
authz smqauthz.Authorization
entitiesOps permissions.EntitiesOperations[permissions.Operation]
rolemgr.RoleManagerAuthorizationMiddleware
}
// AuthorizationMiddleware adds authorization to the re service.
func AuthorizationMiddleware(svc re.Service, authz smqauthz.Authorization) (re.Service, error) {
func AuthorizationMiddleware(svc re.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (re.Service, error) {
if err := entitiesOps.Validate(); err != nil {
return nil, err
}
ram, err := rolemgr.NewAuthorization(operations.EntityType, svc, authz, roleOps)
if err != nil {
return nil, err
}
return &authorizationMiddleware{
svc: svc,
authz: authz,
svc: svc,
authz: authz,
entitiesOps: entitiesOps,
RoleManagerAuthorizationMiddleware: ram,
}, nil
}
func (am *authorizationMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
if err := am.authorize(ctx, re.OpAddRule, session); err != nil {
return re.Rule{}, errors.Wrap(errDomainCreateRules, err)
func (am *authorizationMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
if err := am.authorize(ctx, operations.OpAddRule, session, policies.DomainType, session.DomainID); err != nil {
return re.Rule{}, nil, errors.Wrap(errDomainCreateRules, err)
}
return am.svc.AddRule(ctx, session, r)
}
func (am *authorizationMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
if err := am.authorize(ctx, re.OpViewRule, session); err != nil {
func (am *authorizationMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
if err := am.authorize(ctx, operations.OpViewRule, session, operations.EntityType, id); err != nil {
return re.Rule{}, errors.Wrap(errDomainViewRules, err)
}
return am.svc.ViewRule(ctx, session, id)
return am.svc.ViewRule(ctx, session, id, withRoles)
}
func (am *authorizationMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
if err := am.authorize(ctx, re.OpUpdateRule, session); err != nil {
if err := am.authorize(ctx, operations.OpUpdateRule, session, operations.EntityType, r.ID); err != nil {
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
}
@@ -60,7 +75,7 @@ func (am *authorizationMiddleware) UpdateRule(ctx context.Context, session authn
}
func (am *authorizationMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
if err := am.authorize(ctx, re.OpUpdateRuleTags, session); err != nil {
if err := am.authorize(ctx, operations.OpUpdateRuleTags, session, operations.EntityType, r.ID); err != nil {
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
}
@@ -68,7 +83,7 @@ func (am *authorizationMiddleware) UpdateRuleTags(ctx context.Context, session a
}
func (am *authorizationMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
if err := am.authorize(ctx, re.OpUpdateRuleSchedule, session); err != nil {
if err := am.authorize(ctx, operations.OpUpdateRuleSchedule, session, operations.EntityType, r.ID); err != nil {
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
}
@@ -76,15 +91,19 @@ func (am *authorizationMiddleware) UpdateRuleSchedule(ctx context.Context, sessi
}
func (am *authorizationMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
if err := am.authorize(ctx, re.OpListRules, session); err != nil {
return re.Page{}, errors.Wrap(errDomainViewRules, err)
switch err := am.checkSuperAdmin(ctx, session); {
case err == nil:
session.SuperAdmin = true
case errors.Contains(err, svcerr.ErrSuperAdminAction):
default:
return re.Page{}, err
}
return am.svc.ListRules(ctx, session, pm)
}
func (am *authorizationMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, re.OpRemoveRule, session); err != nil {
if err := am.authorize(ctx, operations.OpRemoveRule, session, operations.EntityType, id); err != nil {
return errors.Wrap(errDomainDeleteRules, err)
}
@@ -92,7 +111,7 @@ func (am *authorizationMiddleware) RemoveRule(ctx context.Context, session authn
}
func (am *authorizationMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
if err := am.authorize(ctx, re.OpEnableRule, session); err != nil {
if err := am.authorize(ctx, operations.OpEnableRule, session, operations.EntityType, id); err != nil {
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
}
@@ -100,7 +119,7 @@ func (am *authorizationMiddleware) EnableRule(ctx context.Context, session authn
}
func (am *authorizationMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
if err := am.authorize(ctx, re.OpDisableRule, session); err != nil {
if err := am.authorize(ctx, operations.OpDisableRule, session, operations.EntityType, id); err != nil {
return re.Rule{}, errors.Wrap(errDomainUpdateRules, err)
}
@@ -119,23 +138,54 @@ func (am *authorizationMiddleware) Cancel() error {
return am.svc.Cancel()
}
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session) error {
perm, err := re.GetPermission(op)
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
if err != nil {
return err
}
pr := smqauthz.PolicyReq{
TokenType: session.Type,
UserID: session.UserID,
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: perm,
Object: obj,
ObjectType: objType,
Permission: perm.String(),
}
return am.authz.Authorize(ctx, pr)
var pat *smqauthz.PATReq
if session.PatID != "" {
opName := am.entitiesOps.OperationName(operations.EntityType, op)
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: session.DomainID,
EntityType: operations.EntityType,
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
return err
}
return nil
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if session.Role != authn.SuperAdminRole {
return svcerr.ErrSuperAdminAction
}
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
SubjectType: policies.UserType,
Subject: session.UserID,
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}, nil); err != nil {
return err
}
return nil
}
+38 -20
View File
@@ -7,52 +7,70 @@ import (
"context"
"time"
mgPolicies "github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/operations"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/callout"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/roles"
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
)
var _ re.Service = (*calloutMiddleware)(nil)
type calloutMiddleware struct {
svc re.Service
callout callout.Callout
svc re.Service
callout callout.Callout
entitiesOps permissions.EntitiesOperations[permissions.Operation]
rolemw.RoleManagerCalloutMiddleware
}
const entityType = "rule"
func NewCallout(svc re.Service, callout callout.Callout) (re.Service, error) {
func NewCallout(svc re.Service, callout callout.Callout, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (re.Service, error) {
call, err := rolemw.NewCallout(mgPolicies.RulesType, svc, callout, roleOps)
if err != nil {
return nil, err
}
if err := entitiesOps.Validate(); err != nil {
return nil, err
}
return &calloutMiddleware{
svc: svc,
callout: callout,
svc: svc,
callout: callout,
entitiesOps: entitiesOps,
RoleManagerCalloutMiddleware: call,
}, nil
}
func (cm *calloutMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
func (cm *calloutMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
params := map[string]any{
"entities": r,
"count": 1,
}
if err := cm.callOut(ctx, session, re.OpAddRuleStr, params); err != nil {
return re.Rule{}, err
if err := cm.callOut(ctx, session, operations.OpAddRule, params); err != nil {
return re.Rule{}, nil, err
}
return cm.svc.AddRule(ctx, session, r)
}
func (cm *calloutMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
func (cm *calloutMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
params := map[string]any{
"entity_id": id,
}
if err := cm.callOut(ctx, session, re.OpViewRuleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpViewRule, params); err != nil {
return re.Rule{}, err
}
return cm.svc.ViewRule(ctx, session, id)
return cm.svc.ViewRule(ctx, session, id, withRoles)
}
func (cm *calloutMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
@@ -60,7 +78,7 @@ func (cm *calloutMiddleware) UpdateRule(ctx context.Context, session authn.Sessi
"entity_id": r.ID,
}
if err := cm.callOut(ctx, session, re.OpUpdateRuleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpUpdateRule, params); err != nil {
return re.Rule{}, err
}
@@ -72,7 +90,7 @@ func (cm *calloutMiddleware) UpdateRuleTags(ctx context.Context, session authn.S
"entity_id": r.ID,
}
if err := cm.callOut(ctx, session, re.OpUpdateRuleTagsStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpUpdateRuleTags, params); err != nil {
return re.Rule{}, err
}
@@ -84,7 +102,7 @@ func (cm *calloutMiddleware) UpdateRuleSchedule(ctx context.Context, session aut
"entity_id": r.ID,
}
if err := cm.callOut(ctx, session, re.OpUpdateRuleScheduleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpUpdateRuleSchedule, params); err != nil {
return re.Rule{}, err
}
@@ -96,7 +114,7 @@ func (cm *calloutMiddleware) ListRules(ctx context.Context, session authn.Sessio
"pagemeta": pm,
}
if err := cm.callOut(ctx, session, re.OpListRulesStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpListRules, params); err != nil {
return re.Page{}, err
}
@@ -108,7 +126,7 @@ func (cm *calloutMiddleware) RemoveRule(ctx context.Context, session authn.Sessi
"entity_id": id,
}
if err := cm.callOut(ctx, session, re.OpRemoveRuleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpRemoveRule, params); err != nil {
return err
}
@@ -120,7 +138,7 @@ func (cm *calloutMiddleware) EnableRule(ctx context.Context, session authn.Sessi
"entity_id": id,
}
if err := cm.callOut(ctx, session, re.OpEnableRuleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpEnableRule, params); err != nil {
return re.Rule{}, err
}
@@ -132,7 +150,7 @@ func (cm *calloutMiddleware) DisableRule(ctx context.Context, session authn.Sess
"entity_id": id,
}
if err := cm.callOut(ctx, session, re.OpDisableRuleStr, params); err != nil {
if err := cm.callOut(ctx, session, operations.OpDisableRule, params); err != nil {
return re.Rule{}, err
}
@@ -151,7 +169,7 @@ func (cm *calloutMiddleware) Cancel() error {
return cm.svc.Cancel()
}
func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session, op string, pld map[string]any) error {
func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session, op permissions.Operation, pld map[string]any) error {
var entityID string
if id, ok := pld["entity_id"].(string); ok {
entityID = id
@@ -159,7 +177,7 @@ func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session,
req := callout.Request{
BaseRequest: callout.BaseRequest{
Operation: op,
Operation: cm.entitiesOps.OperationName(entityType, op),
EntityType: entityType,
EntityID: entityID,
CallerID: session.UserID,
+15 -5
View File
@@ -1,6 +1,8 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package middleware
import (
@@ -12,6 +14,8 @@ import (
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/roles"
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
)
var _ re.Service = (*loggingMiddleware)(nil)
@@ -19,13 +23,18 @@ var _ re.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger *slog.Logger
svc re.Service
rolemw.RoleManagerLoggingMiddleware
}
func LoggingMiddleware(svc re.Service, logger *slog.Logger) re.Service {
return &loggingMiddleware{logger, svc}
return &loggingMiddleware{
logger: logger,
svc: svc,
RoleManagerLoggingMiddleware: rolemw.NewLogging("re", svc, logger),
}
}
func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, err error) {
func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, rps []roles.RoleProvision, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -39,10 +48,11 @@ func (lm *loggingMiddleware) AddRule(ctx context.Context, session authn.Session,
}
lm.logger.Info("Add rule completed successfully", args...)
}(time.Now())
return lm.svc.AddRule(ctx, session, r)
res, rps, err = lm.svc.AddRule(ctx, session, r)
return
}
func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string) (res re.Rule, err error) {
func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (res re.Rule, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
@@ -59,7 +69,7 @@ func (lm *loggingMiddleware) ViewRule(ctx context.Context, session authn.Session
}
lm.logger.Info("View rule completed successfully", args...)
}(time.Now())
return lm.svc.ViewRule(ctx, session, id)
return lm.svc.ViewRule(ctx, session, id, withRoles)
}
func (lm *loggingMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (res re.Rule, err error) {
+139
View File
@@ -0,0 +1,139 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package middleware
import (
"context"
"time"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/roles"
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
"github.com/go-kit/kit/metrics"
)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
service re.Service
rolemw.RoleManagerMetricsMiddleware
}
var _ re.Service = (*metricsMiddleware)(nil)
func NewMetricsMiddleware(counter metrics.Counter, latency metrics.Histogram, service re.Service) re.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
service: service,
RoleManagerMetricsMiddleware: rolemw.NewMetrics("re", service, counter, latency),
}
}
func (mm *metricsMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
defer func(begin time.Time) {
mm.counter.With("method", "add_rule").Add(1)
mm.latency.With("method", "add_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.AddRule(ctx, session, r)
}
func (mm *metricsMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "view_rule").Add(1)
mm.latency.With("method", "view_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.ViewRule(ctx, session, id, withRoles)
}
func (mm *metricsMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_rule").Add(1)
mm.latency.With("method", "update_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.UpdateRule(ctx, session, r)
}
func (mm *metricsMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_rule_tags").Add(1)
mm.latency.With("method", "update_rule_tags").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.UpdateRuleTags(ctx, session, r)
}
func (mm *metricsMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_rule_schedule").Add(1)
mm.latency.With("method", "update_rule_schedule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.UpdateRuleSchedule(ctx, session, r)
}
func (mm *metricsMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
defer func(begin time.Time) {
mm.counter.With("method", "list_rules").Add(1)
mm.latency.With("method", "list_rules").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.ListRules(ctx, session, pm)
}
func (mm *metricsMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
defer func(begin time.Time) {
mm.counter.With("method", "remove_rule").Add(1)
mm.latency.With("method", "remove_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.RemoveRule(ctx, session, id)
}
func (mm *metricsMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "enable_rule").Add(1)
mm.latency.With("method", "enable_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.EnableRule(ctx, session, id)
}
func (mm *metricsMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
defer func(begin time.Time) {
mm.counter.With("method", "disable_rule").Add(1)
mm.latency.With("method", "disable_rule").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.DisableRule(ctx, session, id)
}
func (mm *metricsMiddleware) Handle(msg *messaging.Message) error {
defer func(begin time.Time) {
mm.counter.With("method", "handle").Add(1)
mm.latency.With("method", "handle").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.Handle(msg)
}
func (mm *metricsMiddleware) StartScheduler(ctx context.Context) error {
defer func(begin time.Time) {
mm.counter.With("method", "start_scheduler").Add(1)
mm.latency.With("method", "start_scheduler").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.service.StartScheduler(ctx)
}
func (mm *metricsMiddleware) Cancel() error {
return mm.service.Cancel()
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package middleware
import (
"context"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/roles"
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
smqTracing "github.com/absmach/supermq/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
type tracingMiddleware struct {
tracer trace.Tracer
svc re.Service
rolemw.RoleManagerTracing
}
var _ re.Service = (*tracingMiddleware)(nil)
func NewTracingMiddleware(tracer trace.Tracer, svc re.Service) re.Service {
return &tracingMiddleware{
tracer: tracer,
svc: svc,
RoleManagerTracing: rolemw.NewTracing("re", svc, tracer),
}
}
func (tm *tracingMiddleware) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, []roles.RoleProvision, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "add_rule", trace.WithAttributes(
attribute.String("name", r.Name),
attribute.String("domain_id", r.DomainID),
))
defer span.End()
return tm.svc.AddRule(ctx, session, r)
}
func (tm *tracingMiddleware) ViewRule(ctx context.Context, session authn.Session, id string, withRoles bool) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "view_rule", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
return tm.svc.ViewRule(ctx, session, id, withRoles)
}
func (tm *tracingMiddleware) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule", trace.WithAttributes(
attribute.String("id", r.ID),
))
defer span.End()
return tm.svc.UpdateRule(ctx, session, r)
}
func (tm *tracingMiddleware) UpdateRuleTags(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule_tags", trace.WithAttributes(
attribute.String("id", r.ID),
))
defer span.End()
return tm.svc.UpdateRuleTags(ctx, session, r)
}
func (tm *tracingMiddleware) UpdateRuleSchedule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_rule_schedule", trace.WithAttributes(
attribute.String("id", r.ID),
))
defer span.End()
return tm.svc.UpdateRuleSchedule(ctx, session, r)
}
func (tm *tracingMiddleware) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "list_rules", trace.WithAttributes(
attribute.Int("offset", int(pm.Offset)),
attribute.Int("limit", int(pm.Limit)),
))
defer span.End()
return tm.svc.ListRules(ctx, session, pm)
}
func (tm *tracingMiddleware) RemoveRule(ctx context.Context, session authn.Session, id string) error {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "remove_rule", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
return tm.svc.RemoveRule(ctx, session, id)
}
func (tm *tracingMiddleware) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "enable_rule", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
return tm.svc.EnableRule(ctx, session, id)
}
func (tm *tracingMiddleware) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "disable_rule", trace.WithAttributes(
attribute.String("id", id),
))
defer span.End()
return tm.svc.DisableRule(ctx, session, id)
}
func (tm *tracingMiddleware) Handle(msg *messaging.Message) error {
_, span := smqTracing.StartSpan(context.Background(), tm.tracer, "handle", trace.WithAttributes(
attribute.String("channel", msg.Channel),
attribute.String("subtopic", msg.Subtopic),
))
defer span.End()
return tm.svc.Handle(msg)
}
func (tm *tracingMiddleware) StartScheduler(ctx context.Context) error {
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "start_scheduler")
defer span.End()
return tm.svc.StartScheduler(ctx)
}
func (tm *tracingMiddleware) Cancel() error {
return tm.svc.Cancel()
}
+1509 -11
View File
File diff suppressed because it is too large Load Diff
+1517 -21
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
import (
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
)
const (
OpAddRule permissions.Operation = iota
OpViewRule
OpUpdateRule
OpUpdateRuleTags
OpUpdateRuleSchedule
OpListRules
OpRemoveRule
OpEnableRule
OpDisableRule
)
const (
OpAddRuleStr = "OpAddRule"
OpViewRuleStr = "OpViewRule"
OpUpdateRuleStr = "OpUpdateRule"
OpUpdateRuleTagsStr = "OpUpdateRuleTags"
OpUpdateRuleScheduleStr = "OpUpdateRuleSchedule"
OpListRulesStr = "OpListRules"
OpRemoveRuleStr = "OpRemoveRule"
OpEnableRuleStr = "OpEnableRule"
OpDisableRuleStr = "OpDisableRule"
)
func GetPermission(op permissions.Operation) (string, error) {
if op < OpAddRule || op > OpDisableRule {
return "", errors.New("invalid operation")
}
return policies.MembershipPermission, nil
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package operations
import "github.com/absmach/supermq/pkg/permissions"
const EntityType = "rule"
// Rule Operations.
const (
OpAddRule permissions.Operation = iota
OpViewRule
OpUpdateRule
OpUpdateRuleTags
OpUpdateRuleSchedule
OpRemoveRule
OpListRules
OpEnableRule
OpDisableRule
)
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
return map[permissions.Operation]permissions.OperationDetails{
OpAddRule: {
Name: "add",
PermissionRequired: true,
},
OpViewRule: {
Name: "view",
PermissionRequired: true,
},
OpUpdateRule: {
Name: "update",
PermissionRequired: true,
},
OpUpdateRuleTags: {
Name: "update_tags",
PermissionRequired: true,
},
OpUpdateRuleSchedule: {
Name: "update_schedule",
PermissionRequired: true,
},
OpRemoveRule: {
Name: "delete",
PermissionRequired: true,
},
OpListRules: {
Name: "list",
PermissionRequired: true,
},
OpEnableRule: {
Name: "enable",
PermissionRequired: true,
},
OpDisableRule: {
Name: "disable",
PermissionRequired: true,
},
}
}
+33 -2
View File
@@ -4,12 +4,20 @@
package postgres
import (
dpostgres "github.com/absmach/supermq/domains/postgres"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
func Migration() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
func Migration() (*migrate.MemoryMigrationSource, error) {
rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName)
if err != nil {
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
}
rulesMigration := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "rules_01",
@@ -50,6 +58,29 @@ func Migration() *migrate.MemoryMigrationSource {
`ALTER TABLE rules DROP COLUMN tags;`,
},
},
{
Id: "rules_03",
Up: []string{
`UPDATE rules
SET metadata = (COALESCE(metadata, '{}'::jsonb) - 'ui') || jsonb_build_object('flow', metadata->'ui')
WHERE metadata ? 'ui' AND jsonb_typeof(metadata->'ui') = 'string'`,
},
Down: []string{
`UPDATE rules
SET metadata = (COALESCE(metadata, '{}'::jsonb) - 'flow') || jsonb_build_object('ui', metadata->'flow')
WHERE metadata ? 'flow' AND jsonb_typeof(metadata->'flow') = 'string'`,
},
},
},
}
rulesMigration.Migrations = append(rulesMigration.Migrations, rolesMigration.Migrations...)
domainsMigration, err := dpostgres.Migration()
if err != nil {
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
}
rulesMigration.Migrations = append(rulesMigration.Migrations, domainsMigration.Migrations...)
return rulesMigration, nil
}
+251 -27
View File
@@ -10,19 +10,32 @@ import (
"strings"
"time"
mgPolicies "github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/re"
api "github.com/absmach/supermq/api/http"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
"github.com/absmach/supermq/pkg/postgres"
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
)
const (
rolesTableNamePrefix = "rules"
entityTableName = "rules"
entityIDColumnName = "id"
)
type PostgresRepository struct {
DB postgres.Database
rolesPostgres.Repository
}
func NewRepository(db postgres.Database) re.Repository {
return &PostgresRepository{DB: db}
rolesRepo := rolesPostgres.NewRepository(db, mgPolicies.RulesType, rolesTableNamePrefix, entityTableName, entityIDColumnName)
return &PostgresRepository{
DB: db,
Repository: rolesRepo,
}
}
func (repo *PostgresRepository) AddRule(ctx context.Context, r re.Rule) (re.Rule, error) {
@@ -82,6 +95,157 @@ func (repo *PostgresRepository) ViewRule(ctx context.Context, id string) (re.Rul
return ret, nil
}
func (repo *PostgresRepository) RetrieveByIDWithRoles(ctx context.Context, id, memberID string) (re.Rule, error) {
query := `
WITH selected_rule AS (
SELECT
r.id,
r.domain_id
FROM
rules r
WHERE
r.id = :id
LIMIT 1
),
selected_rule_roles AS (
SELECT
rr.entity_id AS rule_id,
rrm.member_id AS member_id,
rr.id AS role_id,
rr."name" AS role_name,
jsonb_agg(DISTINCT rra."action") AS actions,
'direct' AS access_type,
'' AS access_provider_id
FROM
rules_roles rr
JOIN
rules_role_members rrm ON rr.id = rrm.role_id
JOIN
rules_role_actions rra ON rr.id = rra.role_id
JOIN
selected_rule sr ON sr.id = rr.entity_id
AND rrm.member_id = :member_id
GROUP BY
rr.entity_id, rr.id, rr.name, rrm.member_id
),
selected_domain_roles AS (
SELECT
sr.id AS rule_id,
drm.member_id AS member_id,
dr.id AS role_id,
dr."name" AS role_name,
jsonb_agg(DISTINCT all_actions."action") AS actions,
'domain' AS access_type,
dr.entity_id AS access_provider_id
FROM
domains d
JOIN
selected_rule sr ON sr.domain_id = d.id
JOIN
domains_roles dr ON dr.entity_id = d.id
JOIN
domains_role_members drm ON dr.id = drm.role_id
JOIN
domains_role_actions dra ON dr.id = dra.role_id
JOIN
domains_role_actions all_actions ON dr.id = all_actions.role_id
WHERE
drm.member_id = :member_id
AND dra."action" LIKE 'rule%'
GROUP BY
sr.id, dr.entity_id, dr.id, dr."name", drm.member_id
),
all_roles AS (
SELECT
srr.rule_id,
srr.member_id,
srr.role_id,
srr.role_name,
srr.actions,
srr.access_type,
srr.access_provider_id
FROM
selected_rule_roles srr
UNION
SELECT
sdr.rule_id,
sdr.member_id,
sdr.role_id,
sdr.role_name,
sdr.actions,
sdr.access_type,
sdr.access_provider_id
FROM
selected_domain_roles sdr
),
final_roles AS (
SELECT
ar.rule_id,
ar.member_id,
jsonb_agg(
jsonb_build_object(
'role_id', ar.role_id,
'role_name', ar.role_name,
'actions', ar.actions,
'access_type', ar.access_type,
'access_provider_id', ar.access_provider_id
)
) AS roles
FROM all_roles ar
GROUP BY
ar.rule_id, ar.member_id
)
SELECT
r2.id,
r2."name",
r2.domain_id,
r2.tags,
r2.metadata,
r2.input_channel,
r2.input_topic,
r2.outputs,
r2.status,
r2.logic_type,
r2.logic_value,
r2.time,
r2.recurring,
r2.recurring_period,
r2.start_datetime,
r2.created_at,
r2.created_by,
r2.updated_at,
r2.updated_by,
fr.member_id,
fr.roles
FROM rules r2
JOIN final_roles fr ON fr.rule_id = r2.id
`
parameters := map[string]any{
"id": id,
"member_id": memberID,
}
row, err := repo.DB.NamedQueryContext(ctx, query, parameters)
if err != nil {
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
defer row.Close()
dbrule := dbRule{}
if !row.Next() {
return re.Rule{}, repoerr.ErrNotFound
}
if err := row.StructScan(&dbrule); err != nil {
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
r, err := dbToRule(dbrule)
if err != nil {
return re.Rule{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
return r, nil
}
func (repo *PostgresRepository) UpdateRuleStatus(ctx context.Context, r re.Rule) (re.Rule, error) {
q := `UPDATE rules
SET status = :status, updated_at = :updated_at, updated_by = :updated_by
@@ -193,33 +357,10 @@ func (repo *PostgresRepository) RemoveRule(ctx context.Context, id string) error
return nil
}
func (repo *PostgresRepository) ListRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
pgData := ""
if pm.Limit != 0 {
pgData = "LIMIT :limit"
}
if pm.Offset != 0 {
pgData += " OFFSET :offset"
}
func (repo *PostgresRepository) ListAllRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
pq := pageRulesQuery(pm)
dir := api.DescDir
if pm.Dir == api.AscDir {
dir = api.AscDir
}
orderClause := ""
switch pm.Order {
case api.NameKey:
orderClause = fmt.Sprintf("ORDER BY name %s, id %s", dir, dir)
case api.CreatedAtOrder:
orderClause = fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
case api.UpdatedAtOrder:
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
default:
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
}
orderClause := rulesOrderClause(pm)
pgData := rulesPageData(pm)
q := fmt.Sprintf(`
SELECT id, name, domain_id, tags, input_channel, input_topic, logic_type, logic_value, outputs,
@@ -261,6 +402,62 @@ func (repo *PostgresRepository) ListRules(ctx context.Context, pm re.PageMeta) (
return ret, nil
}
func (repo *PostgresRepository) ListUserRules(ctx context.Context, userID string, pm re.PageMeta) (re.Page, error) {
pm.UserID = userID
pq := pageRulesQuery(pm)
orderClause := rulesOrderClause(pm)
pgData := rulesPageData(pm)
userJoin := `
INNER JOIN rules_roles rr ON rr.entity_id = r.id
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
`
innerQ := fmt.Sprintf(`
SELECT DISTINCT r.id, r.name, r.domain_id, r.tags, r.input_channel, r.input_topic, r.logic_type, r.logic_value, r.outputs,
r.start_datetime, r.time, r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status
FROM rules r
%s
%s
`, userJoin, pq)
q := fmt.Sprintf(`
SELECT * FROM (%s) AS sub %s %s;
`, innerQ, orderClause, pgData)
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
if err != nil {
return re.Page{}, err
}
defer rows.Close()
var rules []re.Rule
for rows.Next() {
var r dbRule
if err := rows.StructScan(&r); err != nil {
return re.Page{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
ret, err := dbToRule(r)
if err != nil {
return re.Page{}, err
}
rules = append(rules, ret)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM (%s) AS count_sub;`, innerQ)
total, err := postgres.Total(ctx, repo.DB, cq, pm)
if err != nil {
return re.Page{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
return re.Page{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
Rules: rules,
}, nil
}
func (repo *PostgresRepository) UpdateRuleDue(ctx context.Context, id string, due time.Time) (re.Rule, error) {
q := `
UPDATE rules
@@ -296,6 +493,33 @@ func (repo *PostgresRepository) UpdateRuleDue(ctx context.Context, id string, du
return rule, nil
}
func rulesOrderClause(pm re.PageMeta) string {
dir := api.DescDir
if pm.Dir == api.AscDir {
dir = api.AscDir
}
switch pm.Order {
case api.NameKey:
return fmt.Sprintf("ORDER BY name %s, id %s", dir, dir)
case api.CreatedAtOrder:
return fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
default:
return fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
}
}
func rulesPageData(pm re.PageMeta) string {
pgData := ""
if pm.Limit != 0 {
pgData = "LIMIT :limit"
}
if pm.Offset != 0 {
pgData += " OFFSET :offset"
}
return pgData
}
func pageRulesQuery(pm re.PageMeta) string {
var query []string
if pm.InputChannel != "" {
+187 -1
View File
@@ -890,7 +890,7 @@ func TestListRules(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
page, err := repo.ListRules(context.Background(), tc.pm)
page, err := repo.ListAllRules(context.Background(), tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
@@ -935,6 +935,192 @@ func TestListRules(t *testing.T) {
}
}
func TestListUserRules(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM rules")
assert.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
})
repo := postgres.NewRepository(database)
domainID := generateUUID(t)
userID := generateUUID(t)
otherUserID := generateUUID(t)
channelID := generateUUID(t)
// Create 10 rules; assign the first 4 to userID via a role.
var allRules []re.Rule
for i := range 10 {
r := re.Rule{
ID: generateUUID(t),
Name: namegen.Generate(),
DomainID: domainID,
InputChannel: channelID,
Logic: re.Script{Type: re.LuaType, Value: "return true"},
Status: re.EnabledStatus,
CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute).Truncate(time.Microsecond),
CreatedBy: generateUUID(t),
UpdatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute).Truncate(time.Microsecond),
UpdatedBy: generateUUID(t),
}
rule, err := repo.AddRule(context.Background(), r)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
allRules = append(allRules, rule)
}
// Assign userID to the first 4 rules via direct role INSERT.
for i := range 4 {
roleID := generateUUID(t)
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", allRules[i].ID)
assert.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
_, err = db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, allRules[i].ID)
assert.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
}
cases := []struct {
desc string
userID string
pm re.PageMeta
count int
err error
}{
{
desc: "list user rules returns only accessible rules",
userID: userID,
pm: re.PageMeta{
Offset: 0,
Limit: 100,
Status: re.AllStatus,
},
count: 4,
err: nil,
},
{
desc: "list user rules with offset",
userID: userID,
pm: re.PageMeta{
Offset: 2,
Limit: 100,
Status: re.AllStatus,
},
count: 2,
err: nil,
},
{
desc: "list user rules with limit",
userID: userID,
pm: re.PageMeta{
Offset: 0,
Limit: 2,
Status: re.AllStatus,
},
count: 2,
err: nil,
},
{
desc: "list user rules with domain filter",
userID: userID,
pm: re.PageMeta{
Domain: domainID,
Offset: 0,
Limit: 100,
Status: re.AllStatus,
},
count: 4,
err: nil,
},
{
desc: "list user rules with channel filter",
userID: userID,
pm: re.PageMeta{
InputChannel: channelID,
Offset: 0,
Limit: 100,
Status: re.AllStatus,
},
count: 4,
err: nil,
},
{
desc: "list user rules with non-existing domain returns 0",
userID: userID,
pm: re.PageMeta{
Domain: generateUUID(t),
Offset: 0,
Limit: 100,
Status: re.AllStatus,
},
count: 0,
err: nil,
},
{
desc: "list rules for user with no role assignments returns 0",
userID: otherUserID,
pm: re.PageMeta{
Offset: 0,
Limit: 100,
Status: re.AllStatus,
},
count: 0,
err: nil,
},
{
desc: "list user rules ordered by name ascending",
userID: userID,
pm: re.PageMeta{
Offset: 0,
Limit: 100,
Status: re.AllStatus,
Order: nameOrder,
Dir: ascDir,
},
count: 4,
err: nil,
},
{
desc: "list user rules ordered by created_at descending",
userID: userID,
pm: re.PageMeta{
Offset: 0,
Limit: 100,
Status: re.AllStatus,
Order: createdAtOrder,
Dir: descDir,
},
count: 4,
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
page, err := repo.ListUserRules(context.Background(), tc.userID, tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.Equal(t, tc.count, len(page.Rules), fmt.Sprintf("%s: expected %d rules, got %d", tc.desc, tc.count, len(page.Rules)))
if len(page.Rules) > 1 {
switch tc.pm.Order {
case nameOrder:
if tc.pm.Dir == ascDir {
assert.True(t, sort.SliceIsSorted(page.Rules, func(i, j int) bool {
return page.Rules[i].Name <= page.Rules[j].Name
}), "Expected names to be sorted ascending")
}
case createdAtOrder:
if tc.pm.Dir == descDir {
assert.True(t, sort.SliceIsSorted(page.Rules, func(i, j int) bool {
return page.Rules[i].CreatedAt.After(page.Rules[j].CreatedAt)
}), "Expected created_at to be sorted descending")
}
}
}
})
}
}
func TestRemoveRule(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM rules")
+11
View File
@@ -11,6 +11,7 @@ import (
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/roles"
"github.com/jackc/pgtype"
)
@@ -35,6 +36,8 @@ type dbRule struct {
CreatedBy string `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy string `db:"updated_by"`
MemberID string `db:"member_id,omitempty"`
Roles json.RawMessage `db:"roles,omitempty"`
}
func ruleToDb(r re.Rule) (dbRule, error) {
@@ -108,6 +111,13 @@ func dbToRule(dto dbRule) (re.Rule, error) {
}
}
var roles []roles.MemberRoleActions
if dto.Roles != nil {
if err := json.Unmarshal(dto.Roles, &roles); err != nil {
return re.Rule{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
return re.Rule{
ID: dto.ID,
Name: dto.Name,
@@ -132,6 +142,7 @@ func dbToRule(dto dbRule) (re.Rule, error) {
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
Roles: roles,
}, nil
}
+5 -1
View File
@@ -75,7 +75,11 @@ func TestMain(m *testing.M) {
SSLRootCert: "",
}
if db, err = postgres.Setup(dbConfig, *repostgres.Migration()); err != nil {
migration, err := repostgres.Migration()
if err != nil {
log.Fatalf("Could not get migration: %s", err)
}
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}

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