├── .dockerignore
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ ├── config.yaml
│ └── feature_request.yaml
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yaml
└── workflows
│ ├── build.yaml
│ ├── check-license.yaml
│ ├── swagger-ui.yaml
│ └── tests.yaml
├── .gitignore
├── ADOPTERS.md
├── CONTRIBUTING.md
├── LICENSE
├── MAINTAINERS
├── Makefile
├── README.md
├── alarms
├── alarms.go
├── alarms_test.go
├── api
│ ├── doc.go
│ ├── endpoint.go
│ ├── requests.go
│ ├── responses.go
│ └── transport.go
├── brokers
│ ├── brokers_nats.go
│ └── brokers_rabbitmq.go
├── consumer
│ └── consumer.go
├── doc.go
├── middleware
│ ├── authorization.go
│ ├── doc.go
│ ├── logging.go
│ ├── metrics.go
│ └── tracing.go
├── mocks
│ ├── repository.go
│ └── service.go
├── postgres
│ ├── alarms.go
│ ├── alarms_test.go
│ ├── init.go
│ └── setup_test.go
├── service.go
├── service_test.go
└── status.go
├── api
├── common.go
└── grpc
│ └── readers
│ └── v1
│ ├── readers.pb.go
│ └── readers_grpc.pb.go
├── apidocs
├── asyncapi
│ ├── mqtt.yaml
│ └── websocket.yaml
└── openapi
│ ├── README.md
│ ├── bootstrap.yaml
│ ├── notifiers.yaml
│ ├── readers.yaml
│ ├── rules.yaml
│ └── schemas
│ └── health_info.yaml
├── bootstrap
├── README.md
├── api
│ ├── doc.go
│ ├── endpoint.go
│ ├── endpoint_test.go
│ ├── requests.go
│ ├── requests_test.go
│ ├── responses.go
│ └── transport.go
├── configs.go
├── doc.go
├── events
│ ├── consumer
│ │ ├── doc.go
│ │ ├── events.go
│ │ └── streams.go
│ ├── doc.go
│ └── producer
│ │ ├── doc.go
│ │ ├── events.go
│ │ ├── setup_test.go
│ │ ├── streams.go
│ │ └── streams_test.go
├── middleware
│ ├── authorization.go
│ ├── logging.go
│ └── metrics.go
├── mocks
│ ├── config_reader.go
│ ├── config_repository.go
│ └── service.go
├── postgres
│ ├── configs.go
│ ├── configs_test.go
│ ├── doc.go
│ ├── init.go
│ └── setup_test.go
├── reader.go
├── reader_test.go
├── service.go
├── service_test.go
├── state.go
└── tracing
│ ├── doc.go
│ └── tracing.go
├── cli
├── bootstrap.go
├── bootstrap_test.go
├── commands_test.go
├── config.go
├── consumers.go
├── consumers_test.go
├── provision.go
├── sdk.go
├── setup_test.go
└── utils.go
├── cmd
├── alarms
│ └── main.go
├── bootstrap
│ └── main.go
├── cli
│ └── main.go
├── postgres-reader
│ └── main.go
├── postgres-writer
│ └── main.go
├── provision
│ └── main.go
├── re
│ └── main.go
├── timescale-reader
│ └── main.go
└── timescale-writer
│ └── main.go
├── config.toml
├── consumers
├── README.md
├── doc.go
├── notifiers
│ ├── README.md
│ ├── api
│ │ ├── doc.go
│ │ ├── endpoint.go
│ │ ├── endpoint_test.go
│ │ ├── logging.go
│ │ ├── metrics.go
│ │ ├── requests.go
│ │ ├── responses.go
│ │ └── transport.go
│ ├── doc.go
│ ├── mocks
│ │ ├── service.go
│ │ └── subscriptions_repository.go
│ ├── postgres
│ │ ├── database.go
│ │ ├── doc.go
│ │ ├── init.go
│ │ ├── setup_test.go
│ │ ├── subscriptions.go
│ │ └── subscriptions_test.go
│ ├── service.go
│ ├── service_test.go
│ ├── smpp
│ │ ├── README.md
│ │ ├── config.go
│ │ ├── doc.go
│ │ └── notifier.go
│ ├── smtp
│ │ └── notifier.go
│ ├── subscriptions.go
│ └── tracing
│ │ ├── doc.go
│ │ └── subscriptions.go
├── tracing
│ └── consumers.go
└── writers
│ ├── README.md
│ ├── api
│ ├── doc.go
│ ├── logging.go
│ ├── metrics.go
│ └── transport.go
│ ├── brokers
│ ├── brokers_nats.go
│ └── brokers_rabbitmq.go
│ ├── doc.go
│ ├── postgres
│ ├── README.md
│ ├── consumer.go
│ ├── consumer_test.go
│ ├── doc.go
│ ├── init.go
│ └── setup_test.go
│ └── timescale
│ ├── README.md
│ ├── consumer.go
│ ├── consumer_test.go
│ ├── doc.go
│ ├── init.go
│ └── setup_test.go
├── doc.go
├── docker
├── .env
├── Dockerfile
├── Dockerfile.dev
├── README.md
├── addons
│ ├── bootstrap
│ │ └── docker-compose.yaml
│ ├── postgres-reader
│ │ └── docker-compose.yaml
│ ├── postgres-writer
│ │ ├── config.toml
│ │ └── docker-compose.yaml
│ ├── prometheus
│ │ ├── docker-compose.yaml
│ │ ├── grafana
│ │ │ ├── dashboard.yaml
│ │ │ ├── datasource.yaml
│ │ │ └── example-dashboard.json
│ │ └── metrics
│ │ │ └── prometheus.yaml
│ ├── provision
│ │ ├── configs
│ │ │ └── config.toml
│ │ └── docker-compose.yaml
│ ├── timescale-reader
│ │ └── docker-compose.yaml
│ ├── timescale-writer
│ │ ├── config.toml
│ │ └── docker-compose.yaml
│ └── vault
│ │ ├── README.md
│ │ ├── config.hcl
│ │ ├── docker-compose.yaml
│ │ ├── entrypoint.sh
│ │ └── scripts
│ │ ├── .gitignore
│ │ ├── magistrala_things_certs_issue.template.hcl
│ │ ├── vault_cmd.sh
│ │ ├── vault_copy_certs.sh
│ │ ├── vault_copy_env.sh
│ │ ├── vault_create_approle.sh
│ │ ├── vault_init.sh
│ │ ├── vault_set_pki.sh
│ │ └── vault_unseal.sh
├── config.toml
├── docker-compose.yaml
├── nginx
│ ├── .gitignore
│ ├── entrypoint.sh
│ ├── nginx-key.conf
│ └── nginx-x509.conf
├── ssl
│ ├── .gitignore
│ ├── Makefile
│ ├── certs
│ │ ├── ca.crt
│ │ ├── ca.key
│ │ ├── magistrala-server.crt
│ │ └── magistrala-server.key
│ └── dhparam.pem
├── supermq-docker-compose.override.yaml
├── supermq-docker
│ ├── .env
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── README.md
│ ├── addons
│ │ ├── certs
│ │ │ ├── config.yaml
│ │ │ └── docker-compose.yaml
│ │ ├── journal
│ │ │ └── docker-compose.yaml
│ │ ├── prometheus
│ │ │ ├── docker-compose.yaml
│ │ │ ├── grafana
│ │ │ │ ├── dashboard.yaml
│ │ │ │ ├── datasource.yaml
│ │ │ │ └── example-dashboard.json
│ │ │ └── metrics
│ │ │ │ └── prometheus.yaml
│ │ └── vault
│ │ │ ├── README.md
│ │ │ ├── config.hcl
│ │ │ ├── docker-compose.yaml
│ │ │ ├── entrypoint.sh
│ │ │ └── scripts
│ │ │ ├── .gitignore
│ │ │ ├── supermq_clients_certs_issue.template.hcl
│ │ │ ├── vault_cmd.sh
│ │ │ ├── vault_copy_certs.sh
│ │ │ ├── vault_copy_env.sh
│ │ │ ├── vault_create_approle.sh
│ │ │ ├── vault_init.sh
│ │ │ ├── vault_set_pki.sh
│ │ │ └── vault_unseal.sh
│ ├── docker-compose.yaml
│ ├── nats
│ │ └── nats.conf
│ ├── nginx
│ │ ├── .gitignore
│ │ ├── entrypoint.sh
│ │ ├── nginx-key.conf
│ │ ├── nginx-x509.conf
│ │ └── snippets
│ │ │ ├── http_access_log.conf
│ │ │ ├── mqtt-upstream-cluster.conf
│ │ │ ├── mqtt-upstream-single.conf
│ │ │ ├── mqtt-ws-upstream-cluster.conf
│ │ │ ├── mqtt-ws-upstream-single.conf
│ │ │ ├── proxy-headers.conf
│ │ │ ├── ssl-client.conf
│ │ │ ├── ssl.conf
│ │ │ ├── stream_access_log.conf
│ │ │ ├── verify-ssl-client.conf
│ │ │ └── ws-upgrade.conf
│ ├── rabbitmq
│ │ ├── enabled_plugins
│ │ └── rabbitmq.conf
│ ├── spicedb
│ │ └── schema.zed
│ ├── ssl
│ │ ├── .gitignore
│ │ ├── Makefile
│ │ ├── authorization.js
│ │ ├── certs
│ │ │ ├── ca.crt
│ │ │ ├── ca.key
│ │ │ ├── supermq-server.crt
│ │ │ └── supermq-server.key
│ │ └── dhparam.pem
│ └── templates
│ │ ├── smtp-notifier.tmpl
│ │ └── users.tmpl
└── templates
│ └── re.tmpl
├── go.mod
├── go.sum
├── internal
├── clients
│ ├── doc.go
│ └── redis
│ │ ├── doc.go
│ │ └── redis.go
├── email
│ ├── README.md
│ ├── doc.go
│ └── email.go
├── proto
│ └── readers
│ │ └── v1
│ │ └── readers.proto
└── testsutil
│ └── common.go
├── pkg
├── README.md
├── doc.go
├── errors
│ ├── README.md
│ ├── doc.go
│ ├── errors.go
│ ├── errors_test.go
│ ├── repository
│ │ └── types.go
│ ├── sdk_errors.go
│ ├── sdk_errors_test.go
│ ├── service
│ │ └── types.go
│ └── types.go
├── postgres
│ └── client.go
├── prometheus
│ ├── doc.go
│ └── metrics.go
├── reltime
│ ├── reltime.go
│ └── reltime_test.go
└── sdk
│ ├── bootstrap.go
│ ├── bootstrap_test.go
│ ├── consumers.go
│ ├── consumers_test.go
│ ├── doc.go
│ ├── messages.go
│ ├── messages_test.go
│ ├── mocks
│ └── sdk.go
│ ├── responses.go
│ ├── sdk.go
│ └── setup_test.go
├── provision
├── README.md
├── api
│ ├── doc.go
│ ├── endpoint.go
│ ├── endpoint_test.go
│ ├── logging.go
│ ├── requests.go
│ ├── requests_test.go
│ ├── responses.go
│ └── transport.go
├── config.go
├── config_test.go
├── configs
│ └── config.toml
├── doc.go
├── mocks
│ └── service.go
├── service.go
└── service_test.go
├── re
├── README.md
├── aes.go
├── aes_test.go
├── api
│ ├── doc.go
│ ├── endpoints.go
│ ├── endpoints_test.go
│ ├── requests.go
│ ├── responses.go
│ └── transport.go
├── bindings.go
├── bindings_test.go
├── doc.go
├── emailer.go
├── emailer
│ ├── doc.go
│ └── emailer.go
├── generator.go
├── handlers.go
├── lua.go
├── middleware
│ ├── authorization.go
│ └── logging.go
├── mocks
│ ├── emailer.go
│ ├── repository.go
│ ├── service.go
│ └── ticker.go
├── postgres
│ ├── init.go
│ ├── repository.go
│ └── rule.go
├── reports.go
├── rule.go
├── schedule.go
├── service.go
├── service_test.go
├── status.go
└── ticker.go
├── readers
├── README.md
├── api
│ ├── doc.go
│ ├── grpc
│ │ ├── client.go
│ │ ├── doc.go
│ │ ├── endpoint.go
│ │ ├── endpoint_test.go
│ │ ├── request.go
│ │ ├── responses.go
│ │ ├── server.go
│ │ └── setup_test.go
│ └── http
│ │ ├── endpoint.go
│ │ ├── endpoint_test.go
│ │ ├── requests.go
│ │ ├── responses.go
│ │ └── transport.go
├── doc.go
├── middleware
│ ├── doc.go
│ ├── logging.go
│ └── metrics.go
├── mocks
│ └── readers_client.go
├── postgres
│ ├── README.md
│ ├── doc.go
│ ├── init.go
│ ├── messages.go
│ ├── messages_test.go
│ └── setup_test.go
└── timescale
│ ├── README.md
│ ├── doc.go
│ ├── messages.go
│ ├── messages_test.go
│ └── setup_test.go
├── scripts
├── ci.sh
├── csv
│ ├── channels.csv
│ └── clients.csv
├── gen-ts-data
│ ├── .gitignore
│ └── gen-messages.py
├── provision-dev.sh
├── run.sh
└── supermq.sh
└── tools
├── config
├── .codecov.yaml
├── .golangci.yaml
├── .mockery.yaml
└── boilerplate.txt
├── doc.go
├── e2e
├── Makefile
├── README.md
├── cmd
│ └── main.go
├── doc.go
└── e2e.go
├── mqtt-bench
├── Makefile
├── README.md
├── bench.go
├── client.go
├── cmd
│ └── main.go
├── config.go
├── doc.go
├── results.go
├── scripts
│ └── mqtt-bench.sh
└── templates
│ └── reference.toml
└── provision
├── Makefile
├── README.md
├── cmd
└── main.go
├── doc.go
└── provision.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | .git
5 | .github
6 | build
7 | docker
8 | metrics
9 | scripts
10 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @absmach/magistrala
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Bug Report
5 | description: File a bug/issue report. Make sure to search to see if an issue already exists for the bug you encountered.
6 | title: "Bug:
"
7 | labels: ["bug", "needs-review", "help wanted"]
8 | body:
9 | - type: textarea
10 | attributes:
11 | label: What were you trying to achieve?
12 | description: A clear and concise description of what the bug is.
13 | validations:
14 | required: true
15 | - type: textarea
16 | attributes:
17 | label: What are the expected results?
18 | description: A concise description of what you expected to happen.
19 | validations:
20 | required: true
21 | - type: textarea
22 | attributes:
23 | label: What are the received results?
24 | description: A concise description of what you received.
25 | validations:
26 | required: true
27 | - type: textarea
28 | attributes:
29 | label: Steps To Reproduce
30 | description: What are the steps to reproduce the issue?
31 | placeholder: |
32 | 1. In this environment...
33 | 2. With this config...
34 | 3. Run '...'
35 | 4. See error...
36 | validations:
37 | required: false
38 | - type: textarea
39 | attributes:
40 | label: In what environment did you encounter the issue?
41 | description: A concise description of the environment you encountered the issue in.
42 | validations:
43 | required: true
44 | - type: textarea
45 | attributes:
46 | label: Additional information you deem important
47 | description: |
48 | Links? References? Anything that will give us more context about the issue you are encountering!
49 |
50 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
51 | validations:
52 | required: false
53 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | blank_issues_enabled: false
5 | contact_links:
6 | - name: Google group
7 | url: https://groups.google.com/forum/#!forum/mainflux
8 | about: Join the Magistrala community on Google group.
9 | - name: Gitter
10 | url: https://gitter.im/mainflux/mainflux
11 | about: Join the Magistrala community on Gitter.
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Feature Request
5 | description: File a feature request. Make sure to search to see if a request already exists for the feature you are requesting.
6 | title: "Feature: "
7 | labels: ["enchancement", "needs-review"]
8 | body:
9 | - type: textarea
10 | attributes:
11 | label: Is your feature request related to a problem? Please describe.
12 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
13 | validations:
14 | required: true
15 | - type: textarea
16 | attributes:
17 | label: Describe the feature you are requesting, as well as the possible use case(s) for it.
18 | description: A clear and concise description of what you want to happen.
19 | validations:
20 | required: true
21 | - type: dropdown
22 | attributes:
23 | label: Indicate the importance of this feature to you.
24 | description: This will help us prioritize the feature request.
25 | options:
26 | - Must-have
27 | - Should-have
28 | - Nice-to-have
29 | validations:
30 | required: true
31 | - type: textarea
32 | attributes:
33 | label: Anything else?
34 | description: |
35 | Links? References? Anything that will give us more context about the feature that you are requesting.
36 |
37 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
38 | validations:
39 | required: false
40 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: "github-actions"
7 | directory: "./.github/workflows"
8 | schedule:
9 | interval: "monthly"
10 | day: "monday"
11 | timezone: "Europe/Paris"
12 | groups:
13 | gh-dependency:
14 | patterns:
15 | - "*"
16 |
17 | - package-ecosystem: "gomod"
18 | directory: "/"
19 | schedule:
20 | interval: "weekly"
21 | day: "monday"
22 | timezone: "Europe/Paris"
23 |
24 | - package-ecosystem: "docker"
25 | directory: "./docker"
26 | schedule:
27 | interval: "monthly"
28 | day: "monday"
29 | timezone: "Europe/Paris"
30 | groups:
31 | docker-dependency:
32 | patterns:
33 | - "*"
34 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Continuous Delivery
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build-and-push:
13 | name: Build and Push
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v4
19 |
20 | - name: Fetch tags for the build
21 | run: |
22 | git fetch --prune --unshallow --tags
23 |
24 | - name: Install Go
25 | uses: actions/setup-go@v5
26 | with:
27 | go-version: 1.24.x
28 | cache-dependency-path: "go.sum"
29 |
30 | - name: Run tests
31 | run: |
32 | make test
33 |
34 | - name: Upload coverage
35 | uses: codecov/codecov-action@v5
36 | with:
37 | token: ${{ secrets.CODECOV }}
38 | files: ./coverage/*.out
39 | codecov_yml_path: tools/.codecov.yaml
40 | verbose: true
41 |
42 | - name: Set up Docker Build
43 | uses: docker/setup-buildx-action@v3
44 |
45 | - name: Login to DockerHub
46 | uses: docker/login-action@v3
47 | with:
48 | registry: ghcr.io
49 | username: ${{ github.actor }}
50 | password: ${{ secrets.GITHUB_TOKEN }}
51 |
52 | - name: Build and push Dockers
53 | run: |
54 | make latest -j $(nproc)
55 |
--------------------------------------------------------------------------------
/.github/workflows/check-license.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Check License Header
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 | pull_request:
11 | branches:
12 | - main
13 |
14 | jobs:
15 | check-license:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v4
20 |
21 | - name: Check License Header
22 | run: |
23 | CHECK=""
24 | for file in $(grep -rl --exclude-dir={.git,build,**vernemq**,coverage} \
25 | --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \
26 | --exclude={CODEOWNERS,LICENSE,MAINTAINERS,enabled_plugins,rabbitmq.conf} \
27 | .); do
28 |
29 | if ! head -n 5 "$file" | grep -q "Copyright (c) Abstract Machines"; then
30 | CHECK="$CHECK $file"
31 | fi
32 | done
33 |
34 | if [ "$CHECK" ]; then
35 | echo "License header check failed. Fix the following files:"
36 | echo "$CHECK"
37 | exit 1
38 | else
39 | echo "All files have the correct license header!"
40 | fi
41 |
--------------------------------------------------------------------------------
/.github/workflows/swagger-ui.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Deploy GitHub Pages
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | swagger-ui:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v4
17 |
18 | - name: Swagger UI action
19 | id: swagger-ui-action
20 | uses: blokovi/swagger-ui-action@main
21 | with:
22 | dir: "./apidocs/openapi"
23 | pattern: "*.yaml"
24 | debug: "true"
25 |
26 | - name: Deploy to GitHub Pages
27 | uses: peaceiris/actions-gh-pages@v4
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: swagger-ui
31 | cname: docs.api.magistrala.abstractmachines.fr
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # build dirs
5 | build
6 |
7 | # tools
8 | tools/e2e/e2e
9 | tools/mqtt-bench/mqtt-bench
10 | tools/provision/provision
11 | tools/provision/mgconn.toml
12 |
13 | # coverage files
14 | coverage
15 |
16 | # Schemathesis
17 | .hypothesis
18 |
19 | # Ignore Vault data directory as it contains runtime-generated data
20 | docker/addons/vault/data/
21 |
--------------------------------------------------------------------------------
/ADOPTERS.md:
--------------------------------------------------------------------------------
1 | # Adopters
2 |
3 | As Magistrala Community grows, we'd like to keep track of Magistrala adopters to grow the community, contact other users, share experiences and best practices.
4 |
5 | To accomplish this, we created a public ledger. The list of organizations and users who consider themselves as Magistrala adopters and that **publicly/officially** shared information and/or details of their adoption journey(optional).
6 | Where users themselves directly maintain the list.
7 |
8 | ## Adding yourself as an adopter
9 | If you are using Magistrala, please consider adding yourself as an adopter with a brief description of your use case by opening a pull request to this file and adding a section describing your adoption of Magistrala technology.
10 |
11 | **Please send PRs to add or remove organizations/users**
12 |
13 | ### Format
14 |
15 | ```
16 | N: Name of user (company or individual)
17 | D: Short Use Case Description (optional)
18 | L: Link with further information (optional)
19 | T: Type of adaptation: Evaluation, Core Technology, Production Usage (optional)
20 | ```
21 |
22 | ## Requirements
23 | * You must represent the user or organization listed. Do NOT add entries on behalf of other organizations or individuals.
24 | Pull request commit must be [signed](https://docs.github.com/en/github/authenticating-to-github/signing-commits) and auto-checked with [ Developer Certificate of Origin (DCO)](https://probot.github.io/apps/dco/)
25 | * There is no minimum requirement or adaptation size, but we request to list permanent deployments only, i.e., no demo or trial deployments. Commercial or production use is not required. A well-done home lab setup can be equally impressive as a large-scale commercial deployment.
26 |
27 |
28 | **The list of organizations/users that have publicly shared the usage of Magistrala:**
29 |
30 | **Note**: Several other organizations/users couldn't publicly share their usage details but are active project contributors and Magistrala Community members.
31 |
32 |
33 | ## Adopters list (alphabetical)
34 |
35 |
36 | **Note:** The list is maintained by the users themselves. If you find yourself on this list, and you think it's inappropriate. Please contact [project maintainers](https://github.com/absmach/magistrala/blob/main/MAINTAINERS) and you will be permanently removed from the list.
37 |
--------------------------------------------------------------------------------
/MAINTAINERS:
--------------------------------------------------------------------------------
1 | # Magistrala follows the timeless, highly efficient and totally unfair system
2 | # known as [Benevolent dictator for
3 | # life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with
4 | # Drasko DRASKOVIC in the role of BDFL.
5 |
6 | [bdfl]
7 |
8 | [[drasko]]
9 | Name = "Drasko Draskovic"
10 | Email = "draasko.draskovic@abstractmachines.fr"
11 | GitHub = "drasko"
12 |
13 | # However, this role serves only in dead-lock events, or in a special and very rare cases
14 | # when BDFL completely disagrees with the decisions made.
15 | # In the normal flow of events, decisions on the project design are made through discussions,
16 | # most often on the Pull Requests.
17 | #
18 | # Maintainers have the special role in the project in managing and accepting PRs,
19 | # overall leading the project and making design decisions on the maintained subsystems.
20 | #
21 | # A reference list of all maintainers of the Magistrala project.
22 |
23 | # ADD YOURSELF HERE IN ALPHABETICAL ORDER
24 |
25 | [maintainers]
26 |
27 | [[dusan]]
28 | Name = "Dusan Borovcanin"
29 | Email = "dusan.borovcanin@abstractmachines.fr"
30 | GitHub = "dborovcanin"
31 |
--------------------------------------------------------------------------------
/alarms/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/alarms/api/requests.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "errors"
8 |
9 | "github.com/absmach/magistrala/alarms"
10 | api "github.com/absmach/supermq/api/http"
11 | apiutil "github.com/absmach/supermq/api/http/util"
12 | )
13 |
14 | type alarmReq struct {
15 | alarms.Alarm `json:",inline"`
16 | }
17 |
18 | func (req alarmReq) validate() error {
19 | if req.Alarm.ID == "" {
20 | return errors.New("missing alarm id")
21 | }
22 |
23 | return nil
24 | }
25 |
26 | type listAlarmsReq struct {
27 | alarms.PageMetadata
28 | }
29 |
30 | func (req listAlarmsReq) validate() error {
31 | if req.Limit > api.MaxLimitSize || req.Limit < 1 {
32 | return apiutil.ErrLimitSize
33 | }
34 |
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/alarms/api/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "fmt"
8 | "net/http"
9 |
10 | "github.com/absmach/magistrala/alarms"
11 | "github.com/absmach/supermq"
12 | )
13 |
14 | var (
15 | _ supermq.Response = (*alarmRes)(nil)
16 | _ supermq.Response = (*alarmsPageRes)(nil)
17 | )
18 |
19 | type alarmRes struct {
20 | alarms.Alarm `json:",inline"`
21 | created bool
22 | deleted bool
23 | }
24 |
25 | func (res alarmRes) Headers() map[string]string {
26 | switch {
27 | case res.created:
28 | return map[string]string{
29 | "Location": fmt.Sprintf("/%s/alarms/%s", res.DomainID, res.ID),
30 | }
31 | default:
32 | return map[string]string{}
33 | }
34 | }
35 |
36 | func (res alarmRes) Code() int {
37 | switch {
38 | case res.created:
39 | return http.StatusCreated
40 | case res.deleted:
41 | return http.StatusNoContent
42 | default:
43 | return http.StatusOK
44 | }
45 | }
46 |
47 | func (res alarmRes) Empty() bool {
48 | switch {
49 | case res.deleted:
50 | return true
51 | default:
52 | return false
53 | }
54 | }
55 |
56 | type alarmsPageRes struct {
57 | alarms.AlarmsPage `json:",inline"`
58 | }
59 |
60 | func (res alarmsPageRes) Headers() map[string]string {
61 | return map[string]string{}
62 | }
63 |
64 | func (res alarmsPageRes) Code() int {
65 | return http.StatusOK
66 | }
67 |
68 | func (res alarmsPageRes) Empty() bool {
69 | return false
70 | }
71 |
--------------------------------------------------------------------------------
/alarms/brokers/brokers_nats.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !rabbitmq
5 | // +build !rabbitmq
6 |
7 | package brokers
8 |
9 | import (
10 | "context"
11 | "log/slog"
12 | "time"
13 |
14 | "github.com/absmach/supermq/pkg/messaging"
15 | broker "github.com/absmach/supermq/pkg/messaging/nats"
16 | "github.com/nats-io/nats.go/jetstream"
17 | )
18 |
19 | const (
20 | AllTopic = "alarms.>"
21 |
22 | prefix = "alarms"
23 | )
24 |
25 | var cfg = jetstream.StreamConfig{
26 | Name: "alarms",
27 | Description: "SuperMQ stream alarms",
28 | Subjects: []string{"alarms.>"},
29 | Retention: jetstream.LimitsPolicy,
30 | MaxMsgsPerSubject: 1e6,
31 | MaxAge: time.Hour * 24,
32 | MaxMsgSize: 1024 * 1024,
33 | Discard: jetstream.DiscardOld,
34 | Storage: jetstream.FileStorage,
35 | }
36 |
37 | func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
38 | pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | return pb, nil
44 | }
45 |
46 | func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) {
47 | pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | return pb, nil
53 | }
54 |
--------------------------------------------------------------------------------
/alarms/brokers/brokers_rabbitmq.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build rabbitmq
5 | // +build rabbitmq
6 |
7 | package brokers
8 |
9 | import (
10 | "context"
11 | "log/slog"
12 |
13 | "github.com/absmach/supermq/pkg/messaging"
14 | broker "github.com/absmach/supermq/pkg/messaging/rabbitmq"
15 | )
16 |
17 | const (
18 | AllTopic = "alarms.#"
19 |
20 | exchangeName = "alarms"
21 | prefix = "alarms"
22 | )
23 |
24 | func NewPubSub(_ context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
25 | pb, err := broker.NewPubSub(url, logger, broker.Prefix(prefix), broker.Exchange(exchangeName))
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | return pb, nil
31 | }
32 |
33 | func NewPublisher(_ context.Context, url string) (messaging.Publisher, error) {
34 | pb, err := broker.NewPublisher(url, broker.Prefix(prefix), broker.Exchange(exchangeName))
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | return pb, nil
40 | }
41 |
--------------------------------------------------------------------------------
/alarms/consumer/consumer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package consumer
5 |
6 | import (
7 | "bytes"
8 | "context"
9 | "encoding/gob"
10 | "log/slog"
11 | "time"
12 |
13 | "github.com/absmach/magistrala/alarms"
14 | "github.com/absmach/supermq/pkg/errors"
15 | "github.com/absmach/supermq/pkg/messaging"
16 | )
17 |
18 | type handler struct {
19 | svc alarms.Service
20 | logger *slog.Logger
21 | }
22 |
23 | func Newhandler(svc alarms.Service, logger *slog.Logger) messaging.MessageHandler {
24 | return &handler{svc: svc, logger: logger}
25 | }
26 |
27 | func (h handler) Handle(msg *messaging.Message) (err error) {
28 | if msg == nil {
29 | return errors.New("message is empty")
30 | }
31 | if msg.GetPayload() == nil {
32 | return errors.New("message payload is empty")
33 | }
34 |
35 | var alarm alarms.Alarm
36 | if err := gob.NewDecoder(bytes.NewReader(msg.GetPayload())).Decode(&alarm); err != nil {
37 | return err
38 | }
39 | alarm.DomainID = msg.GetDomain()
40 | alarm.ChannelID = msg.GetChannel()
41 | alarm.ClientID = msg.GetPublisher()
42 | alarm.Subtopic = msg.GetSubtopic()
43 | alarm.CreatedAt = time.Unix(0, int64(msg.GetCreated()))
44 |
45 | if err := alarm.Validate(); err != nil {
46 | return err
47 | }
48 |
49 | return h.svc.CreateAlarm(context.Background(), alarm)
50 | }
51 |
52 | func (h handler) Cancel() error {
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/alarms/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package alarms contains domain concept definitions needed to support
5 | // Alarms service feature, i.e. create, read, update, and delete alarms.
6 | package alarms
7 |
--------------------------------------------------------------------------------
/alarms/middleware/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package middleware provides middleware for the alarms service.
5 | // This is logging, metrics, and tracing middleware.
6 | package middleware
7 |
--------------------------------------------------------------------------------
/alarms/postgres/init.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import (
7 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
8 | migrate "github.com/rubenv/sql-migrate"
9 | )
10 |
11 | // Migration of Users service.
12 | func Migration() *migrate.MemoryMigrationSource {
13 | return &migrate.MemoryMigrationSource{
14 | Migrations: []*migrate.Migration{
15 | {
16 | Id: "alarms_01",
17 | // VARCHAR(36) for columns with IDs as UUIDS have a maximum of 36 characters
18 | Up: []string{
19 | `CREATE TABLE IF NOT EXISTS alarms (
20 | id VARCHAR(36) PRIMARY KEY,
21 | rule_id VARCHAR(36) NOT NULL CHECK (length(rule_id) > 0),
22 | domain_id VARCHAR(36) NOT NULL,
23 | channel_id VARCHAR(36) NOT NULL,
24 | subtopic TEXT NOT NULL,
25 | client_id VARCHAR(36) NOT NULL,
26 | measurement TEXT NOT NULL,
27 | value TEXT NOT NULL,
28 | unit TEXT NOT NULL,
29 | threshold TEXT NOT NULL,
30 | cause TEXT NOT NULL,
31 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
32 | severity SMALLINT NOT NULL DEFAULT 0 CHECK (severity >= 0),
33 | assignee_id VARCHAR(36),
34 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
35 | updated_at TIMESTAMPTZ NULL,
36 | updated_by VARCHAR(36) NULL,
37 | assigned_at TIMESTAMPTZ NULL,
38 | assigned_by VARCHAR(36) NULL,
39 | acknowledged_at TIMESTAMPTZ NULL,
40 | acknowledged_by VARCHAR(36) NULL,
41 | resolved_at TIMESTAMPTZ NULL,
42 | resolved_by VARCHAR(36) NULL,
43 | metadata JSONB
44 | );`,
45 | "CREATE INDEX IF NOT EXISTS idx_alarms_state ON alarms (domain_id, rule_id, channel_id, subtopic, client_id, measurement, created_at DESC);",
46 | },
47 | Down: []string{
48 | `DROP TABLE IF EXISTS alarms`,
49 | },
50 | },
51 | },
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/alarms/postgres/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres_test
5 |
6 | import (
7 | "database/sql"
8 | "fmt"
9 | "log"
10 | "os"
11 | "testing"
12 | "time"
13 |
14 | apostgres "github.com/absmach/magistrala/alarms/postgres"
15 | "github.com/absmach/supermq/pkg/postgres"
16 | "github.com/jmoiron/sqlx"
17 | dockertest "github.com/ory/dockertest/v3"
18 | "github.com/ory/dockertest/v3/docker"
19 | "go.opentelemetry.io/otel"
20 | )
21 |
22 | var (
23 | db *sqlx.DB
24 | database postgres.Database
25 | tracer = otel.Tracer("repo_tests")
26 | )
27 |
28 | func TestMain(m *testing.M) {
29 | pool, err := dockertest.NewPool("")
30 | if err != nil {
31 | log.Fatalf("Could not connect to docker: %s", err)
32 | }
33 |
34 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
35 | Repository: "postgres",
36 | Tag: "16.2-alpine",
37 | Env: []string{
38 | "POSTGRES_USER=test",
39 | "POSTGRES_PASSWORD=test",
40 | "POSTGRES_DB=test",
41 | "listen_addresses = '*'",
42 | },
43 | }, func(config *docker.HostConfig) {
44 | config.AutoRemove = true
45 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
46 | })
47 | if err != nil {
48 | log.Fatalf("Could not start container: %s", err)
49 | }
50 |
51 | port := container.GetPort("5432/tcp")
52 |
53 | // exponential backoff-retry, because the application in the container might not be ready to accept connections yet
54 | pool.MaxWait = 120 * time.Second
55 | if err := pool.Retry(func() error {
56 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
57 | db, err := sql.Open("pgx", url)
58 | if err != nil {
59 | return err
60 | }
61 | return db.Ping()
62 | }); err != nil {
63 | log.Fatalf("Could not connect to docker: %s", err)
64 | }
65 |
66 | dbConfig := postgres.Config{
67 | Host: "localhost",
68 | Port: port,
69 | User: "test",
70 | Pass: "test",
71 | Name: "test",
72 | SSLMode: "disable",
73 | SSLCert: "",
74 | SSLKey: "",
75 | SSLRootCert: "",
76 | }
77 |
78 | if db, err = postgres.Setup(dbConfig, *apostgres.Migration()); err != nil {
79 | log.Fatalf("Could not setup test DB connection: %s", err)
80 | }
81 |
82 | database = postgres.NewDatabase(db, dbConfig, tracer)
83 |
84 | code := m.Run()
85 |
86 | // Defers will not be run when using os.Exit
87 | db.Close()
88 | if err := pool.Purge(container); err != nil {
89 | log.Fatalf("Could not purge container: %s", err)
90 | }
91 |
92 | os.Exit(code)
93 | }
94 |
--------------------------------------------------------------------------------
/alarms/service.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package alarms
5 |
6 | import (
7 | "context"
8 | "time"
9 |
10 | "github.com/absmach/supermq"
11 | "github.com/absmach/supermq/pkg/authn"
12 | repoerr "github.com/absmach/supermq/pkg/errors/repository"
13 | )
14 |
15 | type service struct {
16 | idp supermq.IDProvider
17 | repo Repository
18 | }
19 |
20 | var _ Service = (*service)(nil)
21 |
22 | func NewService(idp supermq.IDProvider, repo Repository) Service {
23 | return &service{
24 | idp: idp,
25 | repo: repo,
26 | }
27 | }
28 |
29 | func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) error {
30 | id, err := s.idp.ID()
31 | if err != nil {
32 | return err
33 | }
34 | alarm.ID = id
35 | if alarm.CreatedAt.IsZero() {
36 | alarm.CreatedAt = time.Now()
37 | }
38 |
39 | if err := alarm.Validate(); err != nil {
40 | return err
41 | }
42 |
43 | if _, err = s.repo.CreateAlarm(ctx, alarm); err != nil && err != repoerr.ErrNotFound {
44 | return err
45 | }
46 | return nil
47 | }
48 |
49 | func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID string) (Alarm, error) {
50 | return s.repo.ViewAlarm(ctx, alarmID, session.DomainID)
51 | }
52 |
53 | func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) {
54 | return s.repo.ListAlarms(ctx, pm)
55 | }
56 |
57 | func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error {
58 | return s.repo.DeleteAlarm(ctx, alarmID)
59 | }
60 |
61 | func (s *service) UpdateAlarm(ctx context.Context, session authn.Session, alarm Alarm) (Alarm, error) {
62 | alarm.UpdatedAt = time.Now()
63 | alarm.UpdatedBy = session.UserID
64 |
65 | return s.repo.UpdateAlarm(ctx, alarm)
66 | }
67 |
--------------------------------------------------------------------------------
/alarms/status.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package alarms
5 |
6 | import (
7 | "encoding/json"
8 | "strings"
9 |
10 | svcerr "github.com/absmach/supermq/pkg/errors/service"
11 | )
12 |
13 | type Status uint8
14 |
15 | const (
16 | ActiveStatus Status = iota
17 | ClearedStatus
18 |
19 | // AllStatus is used for querying purposes to list alarms irrespective
20 | // of their status. It is never stored in the database as the actual
21 | // Alarm status and should always be the largest value in this enumeration.
22 | AllStatus
23 | )
24 |
25 | const (
26 | Active = "active"
27 | Cleared = "cleared"
28 | Unknown = "unknown"
29 | All = "all"
30 | )
31 |
32 | // String converts alarm status to string literal.
33 | func (s Status) String() string {
34 | switch s {
35 | case ActiveStatus:
36 | return Active
37 | case ClearedStatus:
38 | return Cleared
39 | default:
40 | return Unknown
41 | }
42 | }
43 |
44 | // ToStatus converts string value to a valid Alarm status.
45 | func ToStatus(status string) (Status, error) {
46 | switch strings.ToLower(status) {
47 | case Active:
48 | return ActiveStatus, nil
49 | case Cleared:
50 | return ClearedStatus, nil
51 | case All:
52 | return AllStatus, nil
53 | default:
54 | return Status(0), svcerr.ErrInvalidStatus
55 | }
56 | }
57 |
58 | // Custom Marshaller for Alarm.
59 | func (s Status) MarshalJSON() ([]byte, error) {
60 | return json.Marshal(s.String())
61 | }
62 |
63 | // Custom Unmarshaler for Alarm.
64 | func (s *Status) UnmarshalJSON(data []byte) error {
65 | str := strings.Trim(string(data), "\"")
66 | val, err := ToStatus(str)
67 | *s = val
68 |
69 | return err
70 | }
71 |
--------------------------------------------------------------------------------
/api/common.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "context"
8 | "encoding/json"
9 | "net/http"
10 |
11 | "github.com/absmach/magistrala/bootstrap"
12 | api "github.com/absmach/supermq/api/http"
13 | apiutil "github.com/absmach/supermq/api/http/util"
14 | "github.com/absmach/supermq/pkg/errors"
15 | )
16 |
17 | // EncodeError encodes an error response.
18 | func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
19 | var wrapper error
20 | if errors.Contains(err, apiutil.ErrValidation) {
21 | wrapper, err = errors.Unwrap(err)
22 | }
23 |
24 | w.Header().Set("Content-Type", api.ContentType)
25 |
26 | status, nerr := toStatus(err)
27 | if nerr != nil {
28 | err = unwrap(err)
29 | w.WriteHeader(status)
30 | encodeErrorMessage(err, wrapper, w)
31 | return
32 | }
33 |
34 | if wrapper != nil {
35 | err = errors.Wrap(wrapper, err)
36 | }
37 | api.EncodeError(ctx, err, w)
38 | }
39 |
40 | func toStatus(err error) (int, error) {
41 | switch {
42 | case errors.Contains(err, bootstrap.ErrExternalKey),
43 | errors.Contains(err, bootstrap.ErrExternalKeySecure):
44 | return http.StatusForbidden, err
45 |
46 | case errors.Contains(err, bootstrap.ErrBootstrapState),
47 | errors.Contains(err, bootstrap.ErrAddBootstrap):
48 | return http.StatusBadRequest, err
49 |
50 | case errors.Contains(err, bootstrap.ErrBootstrap):
51 | return http.StatusNotFound, err
52 |
53 | default:
54 | return 0, nil
55 | }
56 | }
57 |
58 | func encodeErrorMessage(err, wrapper error, w http.ResponseWriter) {
59 | if wrapper != nil {
60 | err = errors.Wrap(wrapper, err)
61 | }
62 | if errorVal, ok := err.(errors.Error); ok {
63 | if err := json.NewEncoder(w).Encode(errorVal); err != nil {
64 | w.WriteHeader(http.StatusInternalServerError)
65 | }
66 | }
67 | }
68 |
69 | func unwrap(err error) error {
70 | wrapper, err := errors.Unwrap(err)
71 | if wrapper != nil {
72 | return wrapper
73 | }
74 | return err
75 | }
76 |
--------------------------------------------------------------------------------
/apidocs/openapi/README.md:
--------------------------------------------------------------------------------
1 | # Magistrala OpenAPI Specification
2 |
3 | This folder contains an OpenAPI specifications for Magistrala API.
4 |
5 | View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.magistrala.abstractmachines.fr)
--------------------------------------------------------------------------------
/apidocs/openapi/schemas/health_info.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | type: object
5 | properties:
6 | status:
7 | type: string
8 | description: Service status.
9 | enum:
10 | - pass
11 | version:
12 | type: string
13 | description: Service version.
14 | example: v0.14.0
15 | commit:
16 | type: string
17 | description: Service commit hash.
18 | example: 73362210dd2e04e389eaddb802cab3fe03976593
19 | description:
20 | type: string
21 | description: Service description.
22 | example: service
23 | build_time:
24 | type: string
25 | description: Service build time.
26 | example: 2024-02-01_12:18:15
27 | instance_id:
28 | type: string
29 | description: Service instance ID.
30 | example: 8edbf8af-7db7-4218-bb4f-a8a929ff5266
31 |
--------------------------------------------------------------------------------
/bootstrap/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains implementation of bootstrap service HTTP API.
5 | package api
6 |
--------------------------------------------------------------------------------
/bootstrap/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package bootstrap contains the domain concept definitions needed to support
5 | // SuperMQ bootstrap service functionality.
6 | package bootstrap
7 |
--------------------------------------------------------------------------------
/bootstrap/events/consumer/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package consumer contains events consumer for events
5 | // published by Bootstrap service.
6 | package consumer
7 |
--------------------------------------------------------------------------------
/bootstrap/events/consumer/events.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package consumer
5 |
6 | import "time"
7 |
8 | type removeEvent struct {
9 | id string
10 | }
11 |
12 | type updateChannelEvent struct {
13 | id string
14 | name string
15 | metadata map[string]interface{}
16 | updatedAt time.Time
17 | updatedBy string
18 | }
19 |
20 | // Connection event is either connect or disconnect event.
21 | type connectionEvent struct {
22 | clientIDs []string
23 | channelID string
24 | }
25 |
--------------------------------------------------------------------------------
/bootstrap/events/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package events provides the domain concept definitions needed to support
5 | // bootstrap events functionality.
6 | package events
7 |
--------------------------------------------------------------------------------
/bootstrap/events/producer/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package producer contains the domain events needed to support
5 | // event sourcing of Bootstrap service actions.
6 | package producer
7 |
--------------------------------------------------------------------------------
/bootstrap/events/producer/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package producer_test
5 |
6 | import (
7 | "context"
8 | "fmt"
9 | "log"
10 | "os"
11 | "testing"
12 |
13 | "github.com/ory/dockertest/v3"
14 | "github.com/ory/dockertest/v3/docker"
15 | "github.com/redis/go-redis/v9"
16 | )
17 |
18 | var (
19 | redisClient *redis.Client
20 | redisURL string
21 | )
22 |
23 | func TestMain(m *testing.M) {
24 | pool, err := dockertest.NewPool("")
25 | if err != nil {
26 | log.Fatalf("Could not connect to docker: %s", err)
27 | }
28 |
29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
30 | Repository: "redis",
31 | Tag: "7.2.4-alpine",
32 | }, func(config *docker.HostConfig) {
33 | config.AutoRemove = true
34 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
35 | })
36 | if err != nil {
37 | log.Fatalf("Could not start container: %s", err)
38 | }
39 |
40 | redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp"))
41 | opts, err := redis.ParseURL(redisURL)
42 | if err != nil {
43 | log.Fatalf("Could not parse redis URL: %s", err)
44 | }
45 |
46 | if err := pool.Retry(func() error {
47 | redisClient = redis.NewClient(opts)
48 |
49 | return redisClient.Ping(context.Background()).Err()
50 | }); err != nil {
51 | log.Fatalf("Could not connect to docker: %s", err)
52 | }
53 |
54 | code := m.Run()
55 |
56 | if err := pool.Purge(container); err != nil {
57 | log.Fatalf("Could not purge container: %s", err)
58 | }
59 |
60 | os.Exit(code)
61 | }
62 |
--------------------------------------------------------------------------------
/bootstrap/postgres/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres contains repository implementations using PostgreSQL as
5 | // the underlying database.
6 | package postgres
7 |
--------------------------------------------------------------------------------
/bootstrap/postgres/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres_test
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "os"
10 | "testing"
11 |
12 | "github.com/absmach/magistrala/bootstrap/postgres"
13 | smqlog "github.com/absmach/supermq/logger"
14 | pgclient "github.com/absmach/supermq/pkg/postgres"
15 | "github.com/jmoiron/sqlx"
16 | "github.com/ory/dockertest/v3"
17 | "github.com/ory/dockertest/v3/docker"
18 | )
19 |
20 | var (
21 | testLog, _ = smqlog.New(os.Stdout, "info")
22 | db *sqlx.DB
23 | )
24 |
25 | func TestMain(m *testing.M) {
26 | pool, err := dockertest.NewPool("")
27 | if err != nil {
28 | testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
29 | }
30 |
31 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
32 | Repository: "postgres",
33 | Tag: "16.2-alpine",
34 | Env: []string{
35 | "POSTGRES_USER=test",
36 | "POSTGRES_PASSWORD=test",
37 | "POSTGRES_DB=test",
38 | "listen_addresses = '*'",
39 | },
40 | }, func(config *docker.HostConfig) {
41 | config.AutoRemove = true
42 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
43 | })
44 | if err != nil {
45 | log.Fatalf("Could not start container: %s", err)
46 | }
47 |
48 | port := container.GetPort("5432/tcp")
49 |
50 | if err := pool.Retry(func() error {
51 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
52 | db, err = sqlx.Open("pgx", url)
53 | if err != nil {
54 | return err
55 | }
56 | return db.Ping()
57 | }); err != nil {
58 | testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
59 | }
60 |
61 | dbConfig := pgclient.Config{
62 | Host: "localhost",
63 | Port: port,
64 | User: "test",
65 | Pass: "test",
66 | Name: "test",
67 | SSLMode: "disable",
68 | SSLCert: "",
69 | SSLKey: "",
70 | SSLRootCert: "",
71 | }
72 |
73 | if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil {
74 | testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err))
75 | }
76 |
77 | code := m.Run()
78 |
79 | // Defers will not be run when using os.Exit
80 | db.Close()
81 | if err := pool.Purge(container); err != nil {
82 | testLog.Error(fmt.Sprintf("Could not purge container: %s", err))
83 | }
84 |
85 | os.Exit(code)
86 | }
87 |
--------------------------------------------------------------------------------
/bootstrap/state.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package bootstrap
5 |
6 | import "strconv"
7 |
8 | const (
9 | // Inactive Client is created, but not able to exchange messages using SuperMQ.
10 | Inactive State = iota
11 | // Active Client is created, configured, and whitelisted.
12 | Active
13 | )
14 |
15 | // State represents corresponding SuperMQ Client state. The possible Config States
16 | // as well as description of what that State represents are given in the table:
17 | // | State | What it means |
18 | // |----------+--------------------------------------------------------------------------------|
19 | // | Inactive | Client is created, but isn't able to communicate over SuperMQ |
20 | // | Active | Client is able to communicate using SuperMQ |.
21 | type State int
22 |
23 | // String returns string representation of State.
24 | func (s State) String() string {
25 | return strconv.Itoa(int(s))
26 | }
27 |
--------------------------------------------------------------------------------
/bootstrap/tracing/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package tracing provides tracing instrumentation for SuperMQ Users service.
5 | //
6 | // This package provides tracing middleware for SuperMQ Users service.
7 | // It can be used to trace incoming requests and add tracing capabilities to
8 | // SuperMQ Users service.
9 | //
10 | // For more details about tracing instrumentation for SuperMQ messaging refer
11 | // to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
12 | package tracing
13 |
--------------------------------------------------------------------------------
/cli/commands_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package cli_test
5 |
6 | // CRUD and common commands
7 | const (
8 | createCmd = "create"
9 | updateCmd = "update"
10 | getCmd = "get"
11 | enableCmd = "enable"
12 | disableCmd = "disable"
13 | updCmd = "update"
14 | delCmd = "delete"
15 | rmCmd = "remove"
16 | whitelistCmd = "whitelist"
17 | bootStrapCmd = "bootstrap"
18 | )
19 |
--------------------------------------------------------------------------------
/cli/sdk.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package cli
5 |
6 | import mgsdk "github.com/absmach/magistrala/pkg/sdk"
7 |
8 | // Keep SDK handle in global var.
9 | var sdk mgsdk.SDK
10 |
11 | // SetSDK sets supermq SDK instance.
12 | func SetSDK(s mgsdk.SDK) {
13 | sdk = s
14 | }
15 |
--------------------------------------------------------------------------------
/cli/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package cli_test
5 |
6 | import (
7 | "bytes"
8 | "testing"
9 |
10 | "github.com/absmach/supermq/cli"
11 | "github.com/spf13/cobra"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | type outputLog uint8
16 |
17 | const (
18 | usageLog outputLog = iota
19 | errLog
20 | entityLog
21 | okLog
22 | createLog
23 | revokeLog
24 | )
25 |
26 | func executeCommand(t *testing.T, root *cobra.Command, args ...string) string {
27 | buffer := new(bytes.Buffer)
28 | root.SetOut(buffer)
29 | root.SetErr(buffer)
30 | root.SetArgs(args)
31 | err := root.Execute()
32 | assert.NoError(t, err, "Error executing command")
33 | return buffer.String()
34 | }
35 |
36 | func setFlags(rootCmd *cobra.Command) *cobra.Command {
37 | // Root Flags
38 | rootCmd.PersistentFlags().BoolVarP(
39 | &cli.RawOutput,
40 | "raw",
41 | "r",
42 | cli.RawOutput,
43 | "Enables raw output mode for easier parsing of output",
44 | )
45 |
46 | // Client and Channels Flags
47 | rootCmd.PersistentFlags().Uint64VarP(
48 | &cli.Limit,
49 | "limit",
50 | "l",
51 | 10,
52 | "Limit query parameter",
53 | )
54 |
55 | rootCmd.PersistentFlags().Uint64VarP(
56 | &cli.Offset,
57 | "offset",
58 | "o",
59 | 0,
60 | "Offset query parameter",
61 | )
62 |
63 | rootCmd.PersistentFlags().StringVarP(
64 | &cli.Name,
65 | "name",
66 | "n",
67 | "",
68 | "Name query parameter",
69 | )
70 |
71 | rootCmd.PersistentFlags().StringVarP(
72 | &cli.Identity,
73 | "identity",
74 | "I",
75 | "",
76 | "User identity query parameter",
77 | )
78 |
79 | rootCmd.PersistentFlags().StringVarP(
80 | &cli.Metadata,
81 | "metadata",
82 | "m",
83 | "",
84 | "Metadata query parameter",
85 | )
86 |
87 | rootCmd.PersistentFlags().StringVarP(
88 | &cli.Status,
89 | "status",
90 | "S",
91 | "",
92 | "User status query parameter",
93 | )
94 |
95 | rootCmd.PersistentFlags().StringVarP(
96 | &cli.State,
97 | "state",
98 | "z",
99 | "",
100 | "Bootstrap state query parameter",
101 | )
102 |
103 | rootCmd.PersistentFlags().StringVarP(
104 | &cli.Topic,
105 | "topic",
106 | "T",
107 | "",
108 | "Subscription topic query parameter",
109 | )
110 |
111 | rootCmd.PersistentFlags().StringVarP(
112 | &cli.Contact,
113 | "contact",
114 | "C",
115 | "",
116 | "Subscription contact query parameter",
117 | )
118 |
119 | return rootCmd
120 | }
121 |
--------------------------------------------------------------------------------
/cli/utils.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package cli
5 |
6 | import (
7 | "encoding/json"
8 | "fmt"
9 |
10 | "github.com/fatih/color"
11 | "github.com/hokaccha/go-prettyjson"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | var (
16 | // Limit query parameter.
17 | Limit uint64 = 10
18 | // Offset query parameter.
19 | Offset uint64 = 0
20 | // Name query parameter.
21 | Name string = ""
22 | // Identity query parameter.
23 | Identity string = ""
24 | // Metadata query parameter.
25 | Metadata string = ""
26 | // Status query parameter.
27 | Status string = ""
28 | // ConfigPath config path parameter.
29 | ConfigPath string = ""
30 | // State query parameter.
31 | State string = ""
32 | // Topic query parameter.
33 | Topic string = ""
34 | // Contact query parameter.
35 | Contact string = ""
36 | // RawOutput raw output mode.
37 | RawOutput bool = false
38 | // Username query parameter.
39 | Username string = ""
40 | // FirstName query parameter.
41 | FirstName string = ""
42 | // LastName query parameter.
43 | LastName string = ""
44 | )
45 |
46 | func logJSONCmd(cmd cobra.Command, iList ...interface{}) {
47 | for _, i := range iList {
48 | m, err := json.Marshal(i)
49 | if err != nil {
50 | logErrorCmd(cmd, err)
51 | return
52 | }
53 |
54 | pj, err := prettyjson.Format(m)
55 | if err != nil {
56 | logErrorCmd(cmd, err)
57 | return
58 | }
59 |
60 | fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", string(pj))
61 | }
62 | }
63 |
64 | func logUsageCmd(cmd cobra.Command, u string) {
65 | fmt.Fprintf(cmd.OutOrStdout(), color.YellowString("\nusage: %s\n\n"), u)
66 | }
67 |
68 | func logErrorCmd(cmd cobra.Command, err error) {
69 | boldRed := color.New(color.FgRed, color.Bold)
70 | boldRed.Fprintf(cmd.ErrOrStderr(), "\nerror: ")
71 |
72 | fmt.Fprintf(cmd.ErrOrStderr(), "%s\n\n", color.RedString(err.Error()))
73 | }
74 |
75 | func logOKCmd(cmd cobra.Command) {
76 | fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", color.BlueString("ok"))
77 | }
78 |
79 | func logCreatedCmd(cmd cobra.Command, e string) {
80 | if RawOutput {
81 | fmt.Fprintln(cmd.OutOrStdout(), e)
82 | } else {
83 | fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\ncreated: %s\n\n"), e)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | raw_output = "false"
5 | user_token = ""
6 |
7 | [filter]
8 | limit = "10"
9 | offset = "0"
10 | topic = ""
11 |
12 | [remotes]
13 | journal_url = "http://localhost:9021"
14 | bootstrap_url = "http://localhost:9013"
15 | certs_url = "http://localhost:9019"
16 | domains_url = "http://localhost:9003"
17 | host_url = "http://localhost"
18 | http_adapter_url = "http://localhost:8008"
19 | reader_url = "http://localhost:9011"
20 | clients_url = "http://localhost:9006"
21 | channels_url = "http://localhost:9005"
22 | groups_url = "http://localhost:9004"
23 | users_url = "http://localhost:9002"
24 | tls_verification = false
25 |
--------------------------------------------------------------------------------
/consumers/README.md:
--------------------------------------------------------------------------------
1 | # Consumers
2 |
3 | Consumers provide an abstraction of various `SuperMQ consumers`.
4 | SuperMQ consumer is a generic service that can handle received messages - consume them.
5 | The message is not necessarily a SuperMQ message - before consuming, SuperMQ message can
6 | be transformed into any valid format that specific consumer can understand. For example,
7 | writers are consumers that can take a SenML or JSON message and store it.
8 |
9 | Consumers are optional services and are treated as plugins. In order to
10 | run consumer services, core services must be up and running.
11 |
12 | For an in-depth explanation of the usage of `consumers`, as well as thorough
13 | understanding of SuperMQ, please check out the [official documentation][doc].
14 |
15 | For more information about service capabilities and its usage, please check out
16 | the [API documentation](https://docs.api.supermq.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yaml).
17 |
18 | [doc]: https://docs.supermq.abstractmachines.fr
19 |
--------------------------------------------------------------------------------
/consumers/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package consumers contain the domain concept definitions needed to
5 | // support SuperMQ consumer services functionality.
6 | package consumers
7 |
--------------------------------------------------------------------------------
/consumers/notifiers/README.md:
--------------------------------------------------------------------------------
1 | # Notifiers service
2 |
3 | Notifiers service provides a service for sending notifications using Notifiers.
4 | Notifiers service can be configured to use different types of Notifiers to send
5 | different types of notifications such as SMS messages, emails, or push notifications.
6 | Service is extensible so that new implementations of Notifiers can be easily added.
7 | Notifiers **are not standalone services** but rather dependencies used by Notifiers service
8 | for sending notifications over specific protocols.
9 |
10 | ## Configuration
11 |
12 | The service is configured using the environment variables.
13 | The environment variables needed for service configuration depend on the underlying Notifier.
14 | An example of the service configuration for SMTP Notifier can be found [in SMTP Notifier documentation](smtp/README.md).
15 | Note that any unset variables will be replaced with their
16 | default values.
17 |
18 |
19 | ## Usage
20 |
21 | Subscriptions service will start consuming messages and sending notifications when a message is received.
22 |
23 | [doc]: https://docs.supermq.abstractmachines.fr
24 |
--------------------------------------------------------------------------------
/consumers/notifiers/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/consumers/notifiers/api/requests.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import apiutil "github.com/absmach/supermq/api/http/util"
7 |
8 | type createSubReq struct {
9 | token string
10 | Topic string `json:"topic,omitempty"`
11 | Contact string `json:"contact,omitempty"`
12 | }
13 |
14 | func (req createSubReq) validate() error {
15 | if req.token == "" {
16 | return apiutil.ErrBearerToken
17 | }
18 | if req.Topic == "" {
19 | return apiutil.ErrInvalidTopic
20 | }
21 | if req.Contact == "" {
22 | return apiutil.ErrInvalidContact
23 | }
24 | return nil
25 | }
26 |
27 | type subReq struct {
28 | token string
29 | id string
30 | }
31 |
32 | func (req subReq) validate() error {
33 | if req.token == "" {
34 | return apiutil.ErrBearerToken
35 | }
36 | if req.id == "" {
37 | return apiutil.ErrMissingID
38 | }
39 | return nil
40 | }
41 |
42 | type listSubsReq struct {
43 | token string
44 | topic string
45 | contact string
46 | offset uint
47 | limit uint
48 | }
49 |
50 | func (req listSubsReq) validate() error {
51 | if req.token == "" {
52 | return apiutil.ErrBearerToken
53 | }
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/consumers/notifiers/api/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "fmt"
8 | "net/http"
9 |
10 | "github.com/absmach/supermq"
11 | )
12 |
13 | var (
14 | _ supermq.Response = (*createSubRes)(nil)
15 | _ supermq.Response = (*viewSubRes)(nil)
16 | _ supermq.Response = (*listSubsRes)(nil)
17 | _ supermq.Response = (*removeSubRes)(nil)
18 | )
19 |
20 | type createSubRes struct {
21 | ID string
22 | }
23 |
24 | func (res createSubRes) Code() int {
25 | return http.StatusCreated
26 | }
27 |
28 | func (res createSubRes) Headers() map[string]string {
29 | return map[string]string{
30 | "Location": fmt.Sprintf("/subscriptions/%s", res.ID),
31 | }
32 | }
33 |
34 | func (res createSubRes) Empty() bool {
35 | return true
36 | }
37 |
38 | type viewSubRes struct {
39 | ID string `json:"id"`
40 | OwnerID string `json:"owner_id"`
41 | Contact string `json:"contact"`
42 | Topic string `json:"topic"`
43 | }
44 |
45 | func (res viewSubRes) Code() int {
46 | return http.StatusOK
47 | }
48 |
49 | func (res viewSubRes) Headers() map[string]string {
50 | return map[string]string{}
51 | }
52 |
53 | func (res viewSubRes) Empty() bool {
54 | return false
55 | }
56 |
57 | type listSubsRes struct {
58 | Offset uint `json:"offset"`
59 | Limit int `json:"limit"`
60 | Total uint `json:"total,omitempty"`
61 | Subscriptions []viewSubRes `json:"subscriptions,omitempty"`
62 | }
63 |
64 | func (res listSubsRes) Code() int {
65 | return http.StatusOK
66 | }
67 |
68 | func (res listSubsRes) Headers() map[string]string {
69 | return map[string]string{}
70 | }
71 |
72 | func (res listSubsRes) Empty() bool {
73 | return false
74 | }
75 |
76 | type removeSubRes struct{}
77 |
78 | func (res removeSubRes) Code() int {
79 | return http.StatusNoContent
80 | }
81 |
82 | func (res removeSubRes) Headers() map[string]string {
83 | return map[string]string{}
84 | }
85 |
86 | func (res removeSubRes) Empty() bool {
87 | return true
88 | }
89 |
--------------------------------------------------------------------------------
/consumers/notifiers/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package notifiers contain the domain concept definitions needed to
5 | // support SuperMQ notifications functionality.
6 | package notifiers
7 |
--------------------------------------------------------------------------------
/consumers/notifiers/postgres/database.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import (
7 | "context"
8 | "database/sql"
9 | "fmt"
10 |
11 | "github.com/jmoiron/sqlx"
12 | "go.opentelemetry.io/otel/attribute"
13 | "go.opentelemetry.io/otel/trace"
14 | )
15 |
16 | var _ Database = (*database)(nil)
17 |
18 | type database struct {
19 | db *sqlx.DB
20 | tracer trace.Tracer
21 | }
22 |
23 | // Database provides a database interface.
24 | type Database interface {
25 | NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
26 | QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row
27 | NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error)
28 | GetContext(context.Context, interface{}, string, ...interface{}) error
29 | }
30 |
31 | // NewDatabase creates a SubscriptionsDatabase instance.
32 | func NewDatabase(db *sqlx.DB, tracer trace.Tracer) Database {
33 | return &database{
34 | db: db,
35 | tracer: tracer,
36 | }
37 | }
38 |
39 | func (dm database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) {
40 | ctx, span := dm.addSpanTags(ctx, "NamedExecContext", query)
41 | defer span.End()
42 | return dm.db.NamedExecContext(ctx, query, args)
43 | }
44 |
45 | func (dm database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
46 | ctx, span := dm.addSpanTags(ctx, "QueryRowxContext", query)
47 | defer span.End()
48 | return dm.db.QueryRowxContext(ctx, query, args...)
49 | }
50 |
51 | func (dm database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) {
52 | ctx, span := dm.addSpanTags(ctx, "NamedQueryContext", query)
53 | defer span.End()
54 | return dm.db.NamedQueryContext(ctx, query, args)
55 | }
56 |
57 | func (dm database) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
58 | ctx, span := dm.addSpanTags(ctx, "GetContext", query)
59 | defer span.End()
60 | return dm.db.GetContext(ctx, dest, query, args...)
61 | }
62 |
63 | func (dm database) addSpanTags(ctx context.Context, method, query string) (context.Context, trace.Span) {
64 | ctx, span := dm.tracer.Start(ctx,
65 | fmt.Sprintf("sql_%s", method),
66 | trace.WithAttributes(
67 | attribute.String("sql.statement", query),
68 | attribute.String("span.kind", "client"),
69 | attribute.String("peer.service", "postgres"),
70 | attribute.String("db.type", "sql"),
71 | ),
72 | )
73 | return ctx, span
74 | }
75 |
--------------------------------------------------------------------------------
/consumers/notifiers/postgres/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres contains repository implementations using PostgreSQL as
5 | // the underlying database.
6 | package postgres
7 |
--------------------------------------------------------------------------------
/consumers/notifiers/postgres/init.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import migrate "github.com/rubenv/sql-migrate"
7 |
8 | func Migration() *migrate.MemoryMigrationSource {
9 | return &migrate.MemoryMigrationSource{
10 | Migrations: []*migrate.Migration{
11 | {
12 | Id: "subscriptions_1",
13 | Up: []string{
14 | `CREATE TABLE IF NOT EXISTS subscriptions (
15 | id VARCHAR(254) PRIMARY KEY,
16 | owner_id VARCHAR(254) NOT NULL,
17 | contact VARCHAR(254),
18 | topic TEXT,
19 | UNIQUE(topic, contact)
20 | )`,
21 | },
22 | Down: []string{
23 | "DROP TABLE IF EXISTS subscriptions",
24 | },
25 | },
26 | },
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/consumers/notifiers/postgres/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres_test contains tests for PostgreSQL repository
5 | // implementations.
6 | package postgres_test
7 |
8 | import (
9 | "fmt"
10 | "log"
11 | "os"
12 | "testing"
13 |
14 | "github.com/absmach/magistrala/consumers/notifiers/postgres"
15 | pgclient "github.com/absmach/supermq/pkg/postgres"
16 | "github.com/absmach/supermq/pkg/ulid"
17 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
18 | "github.com/jmoiron/sqlx"
19 | "github.com/ory/dockertest/v3"
20 | "github.com/ory/dockertest/v3/docker"
21 | )
22 |
23 | var (
24 | idProvider = ulid.New()
25 | db *sqlx.DB
26 | )
27 |
28 | func TestMain(m *testing.M) {
29 | pool, err := dockertest.NewPool("")
30 | if err != nil {
31 | log.Fatalf("Could not connect to docker: %s", err)
32 | }
33 |
34 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
35 | Repository: "postgres",
36 | Tag: "16.2-alpine",
37 | Env: []string{
38 | "POSTGRES_USER=test",
39 | "POSTGRES_PASSWORD=test",
40 | "POSTGRES_DB=test",
41 | "listen_addresses = '*'",
42 | },
43 | }, func(config *docker.HostConfig) {
44 | config.AutoRemove = true
45 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
46 | })
47 | if err != nil {
48 | log.Fatalf("Could not start container: %s", err)
49 | }
50 |
51 | port := container.GetPort("5432/tcp")
52 |
53 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
54 | if err := pool.Retry(func() error {
55 | db, err = sqlx.Open("pgx", url)
56 | if err != nil {
57 | return err
58 | }
59 | return db.Ping()
60 | }); err != nil {
61 | log.Fatalf("Could not connect to docker: %s", err)
62 | }
63 |
64 | dbConfig := pgclient.Config{
65 | Host: "localhost",
66 | Port: port,
67 | User: "test",
68 | Pass: "test",
69 | Name: "test",
70 | SSLMode: "disable",
71 | SSLCert: "",
72 | SSLKey: "",
73 | SSLRootCert: "",
74 | }
75 |
76 | if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil {
77 | log.Fatalf("Could not setup test DB connection: %s", err)
78 | }
79 |
80 | code := m.Run()
81 |
82 | // Defers will not be run when using os.Exit
83 | db.Close()
84 | if err := pool.Purge(container); err != nil {
85 | log.Fatalf("Could not purge container: %s", err)
86 | }
87 |
88 | os.Exit(code)
89 | }
90 |
--------------------------------------------------------------------------------
/consumers/notifiers/smpp/config.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package smpp
5 |
6 | import (
7 | "crypto/tls"
8 | )
9 |
10 | // Config represents SMPP transmitter configuration.
11 | type Config struct {
12 | Address string `env:"MG_SMPP_ADDRESS" envDefault:""`
13 | Username string `env:"MG_SMPP_USERNAME" envDefault:""`
14 | Password string `env:"MG_SMPP_PASSWORD" envDefault:""`
15 | SystemType string `env:"MG_SMPP_SYSTEM_TYPE" envDefault:""`
16 | SourceAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_TON" envDefault:"0"`
17 | SourceAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_TON" envDefault:"0"`
18 | DestAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_NPI" envDefault:"0"`
19 | DestAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_NPI" envDefault:"0"`
20 | TLS *tls.Config
21 | }
22 |
--------------------------------------------------------------------------------
/consumers/notifiers/smpp/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package smpp contains the domain concept definitions needed to
5 | // support Magistrala SMS notifications.
6 | package smpp
7 |
--------------------------------------------------------------------------------
/consumers/notifiers/smpp/notifier.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package smpp
5 |
6 | import (
7 | "time"
8 |
9 | "github.com/absmach/supermq/consumers"
10 | "github.com/absmach/supermq/pkg/messaging"
11 | "github.com/absmach/supermq/pkg/transformers"
12 | "github.com/absmach/supermq/pkg/transformers/json"
13 | "github.com/fiorix/go-smpp/smpp"
14 | "github.com/fiorix/go-smpp/smpp/pdu/pdufield"
15 | "github.com/fiorix/go-smpp/smpp/pdu/pdutext"
16 | )
17 |
18 | var _ consumers.Notifier = (*notifier)(nil)
19 |
20 | type notifier struct {
21 | transmitter *smpp.Transmitter
22 | transformer transformers.Transformer
23 | sourceAddrTON uint8
24 | sourceAddrNPI uint8
25 | destAddrTON uint8
26 | destAddrNPI uint8
27 | }
28 |
29 | // New instantiates SMTP message notifier.
30 | func New(cfg Config) consumers.Notifier {
31 | t := &smpp.Transmitter{
32 | Addr: cfg.Address,
33 | User: cfg.Username,
34 | Passwd: cfg.Password,
35 | SystemType: cfg.SystemType,
36 | RespTimeout: 3 * time.Second,
37 | }
38 | t.Bind()
39 | ret := ¬ifier{
40 | transmitter: t,
41 | transformer: json.New([]json.TimeField{}),
42 | sourceAddrTON: cfg.SourceAddrTON,
43 | destAddrTON: cfg.DestAddrTON,
44 | sourceAddrNPI: cfg.SourceAddrNPI,
45 | destAddrNPI: cfg.DestAddrNPI,
46 | }
47 | return ret
48 | }
49 |
50 | func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error {
51 | send := &smpp.ShortMessage{
52 | Src: from,
53 | DstList: to,
54 | Validity: 10 * time.Minute,
55 | SourceAddrTON: n.sourceAddrTON,
56 | DestAddrTON: n.destAddrTON,
57 | SourceAddrNPI: n.sourceAddrNPI,
58 | DestAddrNPI: n.destAddrNPI,
59 | Text: pdutext.Raw(msg.GetPayload()),
60 | Register: pdufield.NoDeliveryReceipt,
61 | }
62 | _, err := n.transmitter.Submit(send)
63 | if err != nil {
64 | return err
65 | }
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/consumers/notifiers/smtp/notifier.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package smtp
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/absmach/magistrala/internal/email"
10 | "github.com/absmach/supermq/consumers"
11 | "github.com/absmach/supermq/pkg/messaging"
12 | )
13 |
14 | const (
15 | footer = "Sent by SuperMQ SMTP Notification"
16 | contentTemplate = "A publisher with an id %s sent the message over %s with the following values \n %s"
17 | )
18 |
19 | var _ consumers.Notifier = (*notifier)(nil)
20 |
21 | type notifier struct {
22 | agent *email.Agent
23 | }
24 |
25 | // New instantiates SMTP message notifier.
26 | func New(agent *email.Agent) consumers.Notifier {
27 | return ¬ifier{agent: agent}
28 | }
29 |
30 | func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error {
31 | subject := fmt.Sprintf(`Notification for Channel %s`, msg.GetChannel())
32 | if msg.GetSubtopic() != "" {
33 | subject = fmt.Sprintf("%s and subtopic %s", subject, msg.GetSubtopic())
34 | }
35 |
36 | values := string(msg.GetPayload())
37 | content := fmt.Sprintf(contentTemplate, msg.GetPublisher(), msg.GetProtocol(), values)
38 |
39 | return n.agent.Send(to, from, subject, "", "", content, footer, map[string][]byte{})
40 | }
41 |
--------------------------------------------------------------------------------
/consumers/notifiers/subscriptions.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package notifiers
5 |
6 | import "context"
7 |
8 | // Subscription represents a user Subscription.
9 | type Subscription struct {
10 | ID string
11 | OwnerID string
12 | Contact string
13 | Topic string
14 | }
15 |
16 | // Page represents page metadata with content.
17 | type Page struct {
18 | PageMetadata
19 | Total uint
20 | Subscriptions []Subscription
21 | }
22 |
23 | // PageMetadata contains page metadata that helps navigation.
24 | type PageMetadata struct {
25 | Offset uint
26 | // Limit values less than 0 indicate no limit.
27 | Limit int
28 | Topic string
29 | Contact string
30 | }
31 |
32 | // SubscriptionsRepository specifies a Subscription persistence API.
33 | type SubscriptionsRepository interface {
34 | // Save persists a subscription. Successful operation is indicated by non-nil
35 | // error response.
36 | Save(ctx context.Context, sub Subscription) (string, error)
37 |
38 | // Retrieve retrieves the subscription for the given id.
39 | Retrieve(ctx context.Context, id string) (Subscription, error)
40 |
41 | // RetrieveAll retrieves all the subscriptions for the given page metadata.
42 | RetrieveAll(ctx context.Context, pm PageMetadata) (Page, error)
43 |
44 | // Remove removes the subscription for the given ID.
45 | Remove(ctx context.Context, id string) error
46 | }
47 |
--------------------------------------------------------------------------------
/consumers/notifiers/tracing/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package tracing provides tracing instrumentation for SuperMQ WebSocket adapter service.
5 | //
6 | // This package provides tracing middleware for SuperMQ WebSocket adapter service.
7 | // It can be used to trace incoming requests and add tracing capabilities to
8 | // SuperMQ WebSocket adapter service.
9 | //
10 | // For more details about tracing instrumentation for SuperMQ messaging refer
11 | // to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
12 | package tracing
13 |
--------------------------------------------------------------------------------
/consumers/writers/README.md:
--------------------------------------------------------------------------------
1 | # Writers
2 |
3 | Writers provide an implementation of various `message writers`.
4 | Message writers are services that normalize (in `SenML` format)
5 | SuperMQ messages and store them in specific data store.
6 |
7 | Writers are optional services and are treated as plugins. In order to
8 | run writer services, core services must be up and running. For more info
9 | on the platform core services with its dependencies, please check out
10 | the [Docker Compose][compose] file.
11 |
12 | For an in-depth explanation of the usage of `writers`, as well as thorough
13 | understanding of SuperMQ, please check out the [official documentation][doc].
14 |
15 | [doc]: https://docs.supermq.abstractmachines.fr
16 | [compose]: ../docker/docker-compose.yaml
17 |
--------------------------------------------------------------------------------
/consumers/writers/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/consumers/writers/api/logging.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !test
5 |
6 | package api
7 |
8 | import (
9 | "context"
10 | "log/slog"
11 | "time"
12 |
13 | "github.com/absmach/supermq/consumers"
14 | )
15 |
16 | var _ consumers.BlockingConsumer = (*loggingMiddleware)(nil)
17 |
18 | type loggingMiddleware struct {
19 | logger *slog.Logger
20 | consumer consumers.BlockingConsumer
21 | }
22 |
23 | // LoggingMiddleware adds logging facilities to the adapter.
24 | func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger) consumers.BlockingConsumer {
25 | return &loggingMiddleware{
26 | logger: logger,
27 | consumer: consumer,
28 | }
29 | }
30 |
31 | // ConsumeBlocking logs the consume request. It logs the time it took to complete the request.
32 | // If the request fails, it logs the error.
33 | func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) (err error) {
34 | defer func(begin time.Time) {
35 | args := []any{
36 | slog.String("duration", time.Since(begin).String()),
37 | }
38 | if err != nil {
39 | args = append(args, slog.Any("error", err))
40 | lm.logger.Warn("Blocking consumer failed to consume messages successfully", args...)
41 | return
42 | }
43 | lm.logger.Info("Blocking consumer consumed messages successfully", args...)
44 | }(time.Now())
45 |
46 | return lm.consumer.ConsumeBlocking(ctx, msgs)
47 | }
48 |
--------------------------------------------------------------------------------
/consumers/writers/api/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !test
5 |
6 | package api
7 |
8 | import (
9 | "context"
10 | "time"
11 |
12 | "github.com/absmach/supermq/consumers"
13 | "github.com/go-kit/kit/metrics"
14 | )
15 |
16 | var _ consumers.BlockingConsumer = (*metricsMiddleware)(nil)
17 |
18 | type metricsMiddleware struct {
19 | counter metrics.Counter
20 | latency metrics.Histogram
21 | consumer consumers.BlockingConsumer
22 | }
23 |
24 | // MetricsMiddleware returns new message repository
25 | // with Save method wrapped to expose metrics.
26 | func MetricsMiddleware(consumer consumers.BlockingConsumer, counter metrics.Counter, latency metrics.Histogram) consumers.BlockingConsumer {
27 | return &metricsMiddleware{
28 | counter: counter,
29 | latency: latency,
30 | consumer: consumer,
31 | }
32 | }
33 |
34 | // ConsumeBlocking instruments ConsumeBlocking method with metrics.
35 | func (mm *metricsMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) error {
36 | defer func(begin time.Time) {
37 | mm.counter.With("method", "consume").Add(1)
38 | mm.latency.With("method", "consume").Observe(time.Since(begin).Seconds())
39 | }(time.Now())
40 | return mm.consumer.ConsumeBlocking(ctx, msgs)
41 | }
42 |
--------------------------------------------------------------------------------
/consumers/writers/api/transport.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "net/http"
8 |
9 | "github.com/absmach/supermq"
10 | "github.com/go-chi/chi/v5"
11 | "github.com/prometheus/client_golang/prometheus/promhttp"
12 | )
13 |
14 | // MakeHandler returns a HTTP API handler with health check and metrics.
15 | func MakeHandler(svcName, instanceID string) http.Handler {
16 | r := chi.NewRouter()
17 | r.Get("/health", supermq.Health(svcName, instanceID))
18 | r.Handle("/metrics", promhttp.Handler())
19 |
20 | return r
21 | }
22 |
--------------------------------------------------------------------------------
/consumers/writers/brokers/brokers_nats.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !rabbitmq
5 | // +build !rabbitmq
6 |
7 | package brokers
8 |
9 | import (
10 | "context"
11 | "log/slog"
12 | "time"
13 |
14 | "github.com/absmach/supermq/pkg/messaging"
15 | broker "github.com/absmach/supermq/pkg/messaging/nats"
16 | "github.com/nats-io/nats.go/jetstream"
17 | )
18 |
19 | const (
20 | AllTopic = "writers.>"
21 |
22 | prefix = "writers"
23 | )
24 |
25 | var cfg = jetstream.StreamConfig{
26 | Name: "writers",
27 | Description: "SuperMQ Rules Engine stream for handling internal messages",
28 | Subjects: []string{"writers.>"},
29 | Retention: jetstream.LimitsPolicy,
30 | MaxMsgsPerSubject: 1e6,
31 | MaxAge: time.Hour * 24,
32 | MaxMsgSize: 1024 * 1024,
33 | Discard: jetstream.DiscardOld,
34 | Storage: jetstream.FileStorage,
35 | }
36 |
37 | func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
38 | pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | return pb, nil
44 | }
45 |
46 | func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) {
47 | pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg))
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | return pb, nil
53 | }
54 |
--------------------------------------------------------------------------------
/consumers/writers/brokers/brokers_rabbitmq.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build rabbitmq
5 | // +build rabbitmq
6 |
7 | package brokers
8 |
9 | import (
10 | "context"
11 | "log/slog"
12 |
13 | "github.com/absmach/supermq/pkg/messaging"
14 | broker "github.com/absmach/supermq/pkg/messaging/rabbitmq"
15 | )
16 |
17 | const (
18 | AllTopic = "writers.#"
19 |
20 | exchangeName = "writers"
21 | prefix = "writers"
22 | )
23 |
24 | func NewPubSub(_ context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) {
25 | pb, err := broker.NewPubSub(url, logger, broker.Prefix(prefix), broker.Exchange(exchangeName))
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | return pb, nil
31 | }
32 |
33 | func NewPublisher(_ context.Context, url string) (messaging.Publisher, error) {
34 | pb, err := broker.NewPublisher(url, broker.Prefix(prefix), broker.Exchange(exchangeName))
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | return pb, nil
40 | }
41 |
--------------------------------------------------------------------------------
/consumers/writers/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package writers contain the domain concept definitions needed to
5 | // support SuperMQ writer services functionality.
6 | package writers
7 |
--------------------------------------------------------------------------------
/consumers/writers/postgres/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres contains repository implementations using Postgres as
5 | // the underlying database.
6 | package postgres
7 |
--------------------------------------------------------------------------------
/consumers/writers/postgres/init.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import migrate "github.com/rubenv/sql-migrate"
7 |
8 | // Migration of postgres-writer.
9 | func Migration() *migrate.MemoryMigrationSource {
10 | return &migrate.MemoryMigrationSource{
11 | Migrations: []*migrate.Migration{
12 | {
13 | Id: "messages_1",
14 | Up: []string{
15 | `CREATE TABLE IF NOT EXISTS messages (
16 | id UUID,
17 | channel UUID,
18 | subtopic VARCHAR(254),
19 | publisher UUID,
20 | protocol TEXT,
21 | name TEXT,
22 | unit TEXT,
23 | value FLOAT,
24 | string_value TEXT,
25 | bool_value BOOL,
26 | data_value BYTEA,
27 | sum FLOAT,
28 | time FLOAT,
29 | update_time FLOAT,
30 | PRIMARY KEY (id)
31 | )`,
32 | },
33 | Down: []string{
34 | "DROP TABLE messages",
35 | },
36 | },
37 | {
38 | Id: "messages_2",
39 | Up: []string{
40 | `ALTER TABLE messages DROP CONSTRAINT messages_pkey`,
41 | `ALTER TABLE messages ADD PRIMARY KEY (time, publisher, subtopic, name)`,
42 | },
43 | },
44 | },
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/consumers/writers/postgres/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres_test contains tests for PostgreSQL repository
5 | // implementations.
6 | package postgres_test
7 |
8 | import (
9 | "fmt"
10 | "log"
11 | "os"
12 | "testing"
13 |
14 | "github.com/absmach/magistrala/consumers/writers/postgres"
15 | pgclient "github.com/absmach/supermq/pkg/postgres"
16 | "github.com/jmoiron/sqlx"
17 | "github.com/ory/dockertest/v3"
18 | "github.com/ory/dockertest/v3/docker"
19 | )
20 |
21 | var db *sqlx.DB
22 |
23 | func TestMain(m *testing.M) {
24 | pool, err := dockertest.NewPool("")
25 | if err != nil {
26 | log.Fatalf("Could not connect to docker: %s", err)
27 | }
28 |
29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
30 | Repository: "postgres",
31 | Tag: "16.2-alpine",
32 | Env: []string{
33 | "POSTGRES_USER=test",
34 | "POSTGRES_PASSWORD=test",
35 | "POSTGRES_DB=test",
36 | "listen_addresses = '*'",
37 | },
38 | }, func(config *docker.HostConfig) {
39 | config.AutoRemove = true
40 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
41 | })
42 | if err != nil {
43 | log.Fatalf("Could not start container: %s", err)
44 | }
45 |
46 | port := container.GetPort("5432/tcp")
47 |
48 | if err := pool.Retry(func() error {
49 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
50 | db, err = sqlx.Open("pgx", url)
51 | if err != nil {
52 | return err
53 | }
54 | return db.Ping()
55 | }); err != nil {
56 | log.Fatalf("Could not connect to docker: %s", err)
57 | }
58 |
59 | dbConfig := pgclient.Config{
60 | Host: "localhost",
61 | Port: port,
62 | User: "test",
63 | Pass: "test",
64 | Name: "test",
65 | SSLMode: "disable",
66 | SSLCert: "",
67 | SSLKey: "",
68 | SSLRootCert: "",
69 | }
70 |
71 | db, err = pgclient.Setup(dbConfig, *postgres.Migration())
72 | if err != nil {
73 | log.Fatalf("Could not setup test DB connection: %s", err)
74 | }
75 |
76 | code := m.Run()
77 |
78 | // Defers will not be run when using os.Exit
79 | db.Close()
80 | if err := pool.Purge(container); err != nil {
81 | log.Fatalf("Could not purge container: %s", err)
82 | }
83 |
84 | os.Exit(code)
85 | }
86 |
--------------------------------------------------------------------------------
/consumers/writers/timescale/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package timescale contains repository implementations using Timescale as
5 | // the underlying database.
6 | package timescale
7 |
--------------------------------------------------------------------------------
/consumers/writers/timescale/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package timescale_test contains tests for TimescaleSQL repository
5 | // implementations.
6 | package timescale_test
7 |
8 | import (
9 | "fmt"
10 | "log"
11 | "os"
12 | "testing"
13 |
14 | "github.com/absmach/magistrala/consumers/writers/timescale"
15 | pgclient "github.com/absmach/supermq/pkg/postgres"
16 | "github.com/jmoiron/sqlx"
17 | "github.com/ory/dockertest/v3"
18 | "github.com/ory/dockertest/v3/docker"
19 | )
20 |
21 | var db *sqlx.DB
22 |
23 | func TestMain(m *testing.M) {
24 | pool, err := dockertest.NewPool("")
25 | if err != nil {
26 | log.Fatalf("Could not connect to docker: %s", err)
27 | }
28 |
29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
30 | Repository: "timescale/timescaledb",
31 | Tag: "2.13.1-pg16",
32 | Env: []string{
33 | "POSTGRES_USER=test",
34 | "POSTGRES_PASSWORD=test",
35 | "POSTGRES_DB=test",
36 | "listen_addresses = '*'",
37 | },
38 | }, func(config *docker.HostConfig) {
39 | config.AutoRemove = true
40 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
41 | })
42 | if err != nil {
43 | log.Fatalf("Could not start container: %s", err)
44 | }
45 |
46 | port := container.GetPort("5432/tcp")
47 |
48 | if err := pool.Retry(func() error {
49 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
50 | db, err = sqlx.Open("pgx", url)
51 | if err != nil {
52 | return err
53 | }
54 | return db.Ping()
55 | }); err != nil {
56 | log.Fatalf("Could not connect to docker: %s", err)
57 | }
58 |
59 | dbConfig := pgclient.Config{
60 | Host: "localhost",
61 | Port: port,
62 | User: "test",
63 | Pass: "test",
64 | Name: "test",
65 | SSLMode: "disable",
66 | SSLCert: "",
67 | SSLKey: "",
68 | SSLRootCert: "",
69 | }
70 |
71 | db, err = pgclient.Setup(dbConfig, *timescale.Migration())
72 | if err != nil {
73 | log.Fatalf("Could not setup test DB connection: %s", err)
74 | }
75 |
76 | code := m.Run()
77 |
78 | // Defers will not be run when using os.Exit
79 | db.Close()
80 | if err := pool.Purge(container); err != nil {
81 | log.Fatalf("Could not purge container: %s", err)
82 | }
83 |
84 | os.Exit(code)
85 | }
86 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // package magistrala acts as an umbrella package containing multiple different
5 | // microservices and defines all shared domain concepts.
6 | package magistrala
7 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | FROM golang:1.24.3-alpine AS builder
5 | ARG SVC
6 | ARG GOARCH
7 | ARG GOARM
8 | ARG VERSION
9 | ARG COMMIT
10 | ARG TIME
11 |
12 | WORKDIR /go/src/github.com/absmach/magistrala
13 | COPY . .
14 | RUN apk update \
15 | && apk add make upx\
16 | && make $SVC \
17 | && upx build/$SVC \
18 | && mv build/$SVC /exe
19 |
20 | FROM scratch
21 | # Certificates are needed so that mailing util can work.
22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
23 | COPY --from=builder /exe /
24 | ENTRYPOINT ["/exe"]
25 |
--------------------------------------------------------------------------------
/docker/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | FROM scratch
5 | ARG SVC
6 | COPY $SVC /exe
7 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
8 | ENTRYPOINT ["/exe"]
9 |
--------------------------------------------------------------------------------
/docker/addons/postgres-writer/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # To listen all messsage broker subjects use default value "writers.>".
5 | # To subscribe to specific subjects use values starting by "writers." and
6 | # followed by a subtopic (e.g ["writers..sub.topic.x", ...]).
7 | ["subscriber"]
8 | subjects = ["writers.>"]
9 |
10 | [transformer]
11 | # SenML or JSON
12 | format = "senml"
13 | # Used if format is SenML
14 | content_type = "application/senml+json"
15 | # Used as timestamp fields if format is JSON
16 | time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"},
17 | { field_name = "millis_key", field_format = "unix_ms", location = "UTC"},
18 | { field_name = "micros_key", field_format = "unix_us", location = "UTC"},
19 | { field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}]
20 |
--------------------------------------------------------------------------------
/docker/addons/prometheus/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # This docker-compose file contains optional Prometheus and Grafana service for Magistrala platform.
5 | # Since this service is optional, this file is dependent of docker-compose.yaml file
6 | # from /docker. In order to run this service, execute command:
7 | # docker compose -f docker/addons/prometheus/docker-compose.yaml up
8 | # from project root.
9 |
10 | networks:
11 | magistrala-base-net:
12 |
13 | volumes:
14 | magistrala-prometheus-volume:
15 |
16 | services:
17 | promethues:
18 | image: prom/prometheus:v2.49.1
19 | container_name: magistrala-prometheus
20 | restart: on-failure
21 | ports:
22 | - ${MG_PROMETHEUS_PORT}:${MG_PROMETHEUS_PORT}
23 | networks:
24 | - magistrala-base-net
25 | volumes:
26 | - type: bind
27 | source: ./metrics/prometheus.yaml
28 | target: /etc/prometheus/prometheus.yaml
29 | - magistrala-prometheus-volume:/prometheus
30 |
31 | grafana:
32 | image: grafana/grafana:10.2.3
33 | container_name: magistrala-grafana
34 | depends_on:
35 | - promethues
36 | restart: on-failure
37 | ports:
38 | - ${MG_GRAFANA_PORT}:${MG_GRAFANA_PORT}
39 | environment:
40 | - GF_SECURITY_ADMIN_USER=${MG_GRAFANA_ADMIN_USER}
41 | - GF_SECURITY_ADMIN_PASSWORD=${MG_GRAFANA_ADMIN_PASSWORD}
42 | networks:
43 | - magistrala-base-net
44 | volumes:
45 | - type: bind
46 | source: ./grafana/datasource.yaml
47 | target: /etc/grafana/provisioning/datasources/datasource.yaml
48 | - type: bind
49 | source: ./grafana/dashboard.yaml
50 | target: /etc/grafana/provisioning/dashboards/main.yaml
51 | - type: bind
52 | source: ./grafana/example-dashboard.json
53 | target: /var/lib/grafana/dashboards/example-dashboard.json
54 |
--------------------------------------------------------------------------------
/docker/addons/prometheus/grafana/dashboard.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | apiVersion: 1
5 |
6 | providers:
7 | - name: "Dashboard provider"
8 | orgId: 1
9 | type: file
10 | disableDeletion: false
11 | updateIntervalSeconds: 10
12 | allowUiUpdates: false
13 | options:
14 | path: /var/lib/grafana/dashboards
15 | foldersFromFilesStructure: true
16 |
--------------------------------------------------------------------------------
/docker/addons/prometheus/grafana/datasource.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | apiVersion: 1
5 |
6 | datasources:
7 | - name: Prometheus
8 | type: prometheus
9 | url: http://magistrala-prometheus:9090
10 | isDefault: true
11 | access: proxy
12 | editable: true
13 |
--------------------------------------------------------------------------------
/docker/addons/prometheus/metrics/prometheus.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | global:
5 | scrape_interval: 15s
6 | evaluation_interval: 15s
7 |
8 | scrape_configs:
9 | - job_name: 'magistrala'
10 | honor_timestamps: true
11 | scrape_interval: 15s
12 | scrape_timeout: 10s
13 | metrics_path: /metrics
14 | follow_redirects: true
15 | enable_http2: true
16 | static_configs:
17 | - targets:
18 | - magistrala-things:9000
19 | - magistrala-users:9002
20 | - magistrala-http:8008
21 | - magistrala-ws:8186
22 | - magistrala-coap:5683
23 |
--------------------------------------------------------------------------------
/docker/addons/provision/configs/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | [bootstrap]
5 | [bootstrap.content]
6 | [bootstrap.content.agent.edgex]
7 | url = "http://localhost:48090/api/v1/"
8 |
9 | [bootstrap.content.agent.log]
10 | level = "info"
11 |
12 | [bootstrap.content.agent.mqtt]
13 | mtls = false
14 | qos = 0
15 | retain = false
16 | skip_tls_ver = true
17 | url = "localhost:1883"
18 |
19 | [bootstrap.content.agent.server]
20 | nats_url = "localhost:4222"
21 | port = "9000"
22 |
23 | [bootstrap.content.agent.heartbeat]
24 | interval = "30s"
25 |
26 | [bootstrap.content.agent.terminal]
27 | session_timeout = "30s"
28 |
29 |
30 | [bootstrap.content.export.exp]
31 | log_level = "debug"
32 | nats = "nats://localhost:4222"
33 | port = "8172"
34 | cache_url = "localhost:6379"
35 | cache_pass = ""
36 | cache_db = "0"
37 |
38 | [bootstrap.content.export.mqtt]
39 | ca_path = "ca.crt"
40 | cert_path = "thing.crt"
41 | channel = ""
42 | host = "tcp://localhost:1883"
43 | mtls = false
44 | password = ""
45 | priv_key_path = "thing.key"
46 | qos = 0
47 | retain = false
48 | skip_tls_ver = false
49 | username = ""
50 |
51 | [[bootstrap.content.export.routes]]
52 | mqtt_topic = ""
53 | nats_topic = ">"
54 | subtopic = ""
55 | type = "plain"
56 | workers = 10
57 |
58 | [[things]]
59 | name = "thing"
60 |
61 | [things.metadata]
62 | external_id = "xxxxxx"
63 |
64 | [[channels]]
65 | name = "control-channel"
66 |
67 | [channels.metadata]
68 | type = "control"
69 |
70 | [[channels]]
71 | name = "data-channel"
72 |
73 | [channels.metadata]
74 | type = "data"
75 |
--------------------------------------------------------------------------------
/docker/addons/provision/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # This docker-compose file contains optional provision services. Since it's optional, this file is
5 | # dependent of docker-compose file from /docker. In order to run this services, execute command:
6 | # docker compose -f docker/docker-compose.yaml -f docker/addons/provision/docker-compose.yaml up
7 | # from project root.
8 |
9 | networks:
10 | magistrala-base-net:
11 |
12 | services:
13 | provision:
14 | image: ghcr.io/absmach/magistrala/provision:${MG_RELEASE_TAG}
15 | container_name: magistrala-provision
16 | restart: on-failure
17 | networks:
18 | - magistrala-base-net
19 | ports:
20 | - ${MG_PROVISION_HTTP_PORT}:${MG_PROVISION_HTTP_PORT}
21 | environment:
22 | MG_PROVISION_LOG_LEVEL: ${MG_PROVISION_LOG_LEVEL}
23 | MG_PROVISION_HTTP_PORT: ${MG_PROVISION_HTTP_PORT}
24 | MG_PROVISION_CONFIG_FILE: ${MG_PROVISION_CONFIG_FILE}
25 | MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS}
26 | MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT}
27 | MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY}
28 | MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION}
29 | MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION}
30 | MG_PROVISION_USER: ${MG_PROVISION_USER}
31 | MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME}
32 | MG_PROVISION_PASS: ${MG_PROVISION_PASS}
33 | MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY}
34 | MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL}
35 | MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING}
36 | MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL}
37 | MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING}
38 | MG_PROVISION_BS_AUTO_WHITELIST: ${MG_PROVISION_BS_AUTO_WHITELIST}
39 | MG_PROVISION_BS_CONTENT: ${MG_PROVISION_BS_CONTENT}
40 | MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID}
41 | SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
42 | MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID}
43 | volumes:
44 | - ./configs:/configs
45 | - ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
46 | - ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt
47 |
--------------------------------------------------------------------------------
/docker/addons/timescale-writer/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # To listen all messsage broker subjects use default value "writers.>".
5 | # To subscribe to specific subjects use values starting by "writers." and
6 | # followed by a subtopic (e.g ["writers..sub.topic.x", ...]).
7 | ["subscriber"]
8 | subjects = ["writers.>"]
9 |
--------------------------------------------------------------------------------
/docker/addons/vault/config.hcl:
--------------------------------------------------------------------------------
1 | storage "file" {
2 | path = "/vault/file"
3 | }
4 |
5 | listener "tcp" {
6 | address = "0.0.0.0:8200"
7 | tls_disable = 1
8 | }
9 |
10 | ui = true
11 |
--------------------------------------------------------------------------------
/docker/addons/vault/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # This docker-compose file contains optional Vault service for Magistrala platform.
5 | # Since this is optional, this file is dependent of docker-compose file
6 | # from /docker. In order to run these services, execute command:
7 | # docker compose -f docker/docker-compose.yaml -f docker/addons/vault/docker-compose.yaml up
8 | # from project root. Vault default port (8200) is exposed, so you can use Vault CLI tool for
9 | # vault inspection and administration, as well as access the UI.
10 |
11 | networks:
12 | magistrala-base-net:
13 |
14 | volumes:
15 | magistrala-vault-volume:
16 |
17 | services:
18 | vault:
19 | image: hashicorp/vault:1.15.4
20 | container_name: magistrala-vault
21 | ports:
22 | - ${MG_VAULT_PORT}:8200
23 | networks:
24 | - magistrala-base-net
25 | volumes:
26 | - magistrala-vault-volume:/vault/file
27 | - magistrala-vault-volume:/vault/logs
28 | - ./config.hcl:/vault/config/config.hcl
29 | - ./entrypoint.sh:/entrypoint.sh
30 | environment:
31 | VAULT_ADDR: http://127.0.0.1:${MG_VAULT_PORT}
32 | MG_VAULT_PORT: ${MG_VAULT_PORT}
33 | MG_VAULT_UNSEAL_KEY_1: ${MG_VAULT_UNSEAL_KEY_1}
34 | MG_VAULT_UNSEAL_KEY_2: ${MG_VAULT_UNSEAL_KEY_2}
35 | MG_VAULT_UNSEAL_KEY_3: ${MG_VAULT_UNSEAL_KEY_3}
36 | entrypoint: /bin/sh
37 | command: /entrypoint.sh
38 | cap_add:
39 | - IPC_LOCK
40 |
--------------------------------------------------------------------------------
/docker/addons/vault/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/dumb-init /bin/sh
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | VAULT_CONFIG_DIR=/vault/config
6 |
7 | docker-entrypoint.sh server &
8 | VAULT_PID=$!
9 |
10 | sleep 2
11 |
12 | echo $MG_VAULT_UNSEAL_KEY_1
13 | echo $MG_VAULT_UNSEAL_KEY_2
14 | echo $MG_VAULT_UNSEAL_KEY_3
15 |
16 | if [[ ! -z "${MG_VAULT_UNSEAL_KEY_1}" ]] &&
17 | [[ ! -z "${MG_VAULT_UNSEAL_KEY_2}" ]] &&
18 | [[ ! -z "${MG_VAULT_UNSEAL_KEY_3}" ]]; then
19 | echo "Unsealing Vault"
20 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_1}
21 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_2}
22 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_3}
23 | fi
24 |
25 | wait $VAULT_PID
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | data
5 | magistrala_things_certs_issue.hcl
6 |
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl:
--------------------------------------------------------------------------------
1 |
2 | # Allow issue certificate with role with default issuer from Intermediate PKI
3 | path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" {
4 | capabilities = ["create", "update"]
5 | }
6 |
7 | ## Revole certificate from Intermediate PKI
8 | path "${MG_VAULT_PKI_INT_PATH}/revoke" {
9 | capabilities = ["create", "update"]
10 | }
11 |
12 | ## List Revoked Certificates from Intermediate PKI
13 | path "${MG_VAULT_PKI_INT_PATH}/certs/revoked" {
14 | capabilities = ["list"]
15 | }
16 |
17 |
18 | ## List Certificates from Intermediate PKI
19 | path "${MG_VAULT_PKI_INT_PATH}/certs" {
20 | capabilities = ["list"]
21 | }
22 |
23 | ## Read Certificate from Intermediate PKI
24 | path "${MG_VAULT_PKI_INT_PATH}/cert/+" {
25 | capabilities = ["read"]
26 | }
27 | path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw" {
28 | capabilities = ["read"]
29 | }
30 | path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw/pem" {
31 | capabilities = ["read"]
32 | }
33 |
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/vault_cmd.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | vault() {
6 | if is_container_running "magistrala-vault"; then
7 | docker exec -it magistrala-vault vault "$@"
8 | else
9 | if which vault &> /dev/null; then
10 | $(which vault) "$@"
11 | else
12 | echo "magistrala-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md"
13 | fi
14 | fi
15 | }
16 |
17 | is_container_running() {
18 | local container_name="$1"
19 | if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then
20 | return 0
21 | else
22 | return 1
23 | fi
24 | }
25 |
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/vault_copy_env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | write_env() {
35 | if [ -e "$scriptdir/data/secrets" ]; then
36 | sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' $scriptdir/data/secrets)," "$env_file"
37 | sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' $scriptdir/data/secrets)," "$env_file"
38 | sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' $scriptdir/data/secrets)," "$env_file"
39 | sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' $scriptdir/data/secrets)," "$env_file"
40 | echo "Vault environment variables are set successfully in $env_file"
41 | else
42 | echo "Error: Source file '$scriptdir/data/secrets' not found."
43 | fi
44 | }
45 |
46 | write_env
47 |
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/vault_init.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | readDotEnv() {
35 | set -o allexport
36 | source "$env_file"
37 | set +o allexport
38 | }
39 |
40 | source "$scriptdir/vault_cmd.sh"
41 |
42 | readDotEnv
43 |
44 | mkdir -p "$scriptdir/data"
45 |
46 | vault operator init -address="$MG_VAULT_ADDR" 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > "$scriptdir/data/secrets")
47 |
--------------------------------------------------------------------------------
/docker/addons/vault/scripts/vault_unseal.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | readDotEnv() {
35 | set -o allexport
36 | source "$env_file"
37 | set +o allexport
38 | }
39 |
40 | source "$scriptdir/vault_cmd.sh"
41 |
42 | readDotEnv
43 |
44 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_1}
45 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_2}
46 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_3}
47 |
--------------------------------------------------------------------------------
/docker/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # To listen all messsage broker subjects use default value "channels.>".
5 | # To subscribe to specific subjects use values starting by "channels." and
6 | # followed by a subtopic (e.g ["channels..sub.topic.x", ...]).
7 | [subscriber]
8 | subjects = ["channels.>"]
9 |
--------------------------------------------------------------------------------
/docker/nginx/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | snippets/mqtt-upstream.conf
5 | snippets/mqtt-ws-upstream.conf
6 |
--------------------------------------------------------------------------------
/docker/nginx/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/ash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | if [ -z "$SMQ_MQTT_CLUSTER" ]; then
6 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' /etc/nginx/snippets/mqtt-upstream.conf
7 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' /etc/nginx/snippets/mqtt-ws-upstream.conf
8 | else
9 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' /etc/nginx/snippets/mqtt-upstream.conf
10 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' /etc/nginx/snippets/mqtt-ws-upstream.conf
11 | fi
12 |
13 | envsubst '
14 | ${SMQ_NGINX_SERVER_NAME}
15 | ${SMQ_DOMAINS_HTTP_PORT}
16 | ${SMQ_GROUPS_HTTP_PORT}
17 | ${SMQ_USERS_HTTP_PORT}
18 | ${SMQ_CLIENTS_HTTP_PORT}
19 | ${SMQ_CLIENTS_HTTP_PORT}
20 | ${SMQ_CHANNELS_HTTP_PORT}
21 | ${SMQ_HTTP_ADAPTER_PORT}
22 | ${SMQ_NGINX_MQTT_PORT}
23 | ${SMQ_NGINX_MQTTS_PORT}
24 | ${MG_RE_HTTP_PORT}
25 | ${MG_ALARMS_HTTP_PORT}
26 | ${SMQ_WS_ADAPTER_HTTP_PORT}' /etc/nginx/nginx.conf
27 |
28 | exec nginx -g "daemon off;"
29 |
--------------------------------------------------------------------------------
/docker/ssl/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | *grpc-server*
5 | *grpc-client*
6 | *srl
7 | *conf
8 |
--------------------------------------------------------------------------------
/docker/ssl/certs/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDyzCCArOgAwIBAgIUDIJg63dQVzoD9nmWi9YPscQwTgIwDQYJKoZIhvcNAQEN
3 | BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE
4 | CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3
5 | DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yMzEwMzAwODE5MDFaFw0yNjEw
6 | MjkwODE5MDFaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex
7 | EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg
8 | BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB
9 | AQUAA4IBDwAwggEKAoIBAQCWNIeGfo/SePOvviJE6UHJhBzWcPfNVbzSF6A42WgB
10 | DEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7xCgxcqFwEo33SyhAivwoHL2pRVHXn
11 | oee3z9U757T63YLE0qrXQY2cbyChX/OU99rZxyd5l5jUGN7MCu+RYurfTIiYN+Uv
12 | NZdl8a3X84g7fa70EOYas7cTunWUt9x64/jYDoYmn+XPXET1yEU1dQTnKY4cRjhv
13 | HS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknhFHTu8PVPxfowrVv/xzmxOe0zSZFd
14 | SbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW91WzOLS9AgMBAAGjUzBRMB0GA1Ud
15 | DgQWBBQkE4koZctEZpTz9pq6a6s6xg+myTAfBgNVHSMEGDAWgBQkE4koZctEZpTz
16 | 9pq6a6s6xg+myTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQA7
17 | w/oh5U9loJsigf3X3T3jQM8PVmhsUfNMJ3kc1Yumr72S4sGKjdWwuU0vk+B3eQzh
18 | zXAj65BHhs1pXcukeoLR7YcHABEsEMg6lar/E4A+MgAZfZFVSvPpsByIK8I5ARk+
19 | K1V/lWso+GJJM/lImPPnpvUWBdbntqC5WtjoMMGL9uyV3kVS6yT/kJ2ercnPzhPh
20 | uBkL1ZH3ivDn/0JDY+T8Sfeq08vNWaTcoC7qpPwqXhuT0ytY7oaBS5wmPcvvzpZg
21 | 6zZYPZfhjhdEFYY1hDrrPYNYO72jncUnwQVp3X0DQpSvbxp681hVkcEtwHB2B8l0
22 | tBGhgoH+TqZs0AUjoXM0
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/docker/ssl/certs/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWNIeGfo/SePOv
3 | viJE6UHJhBzWcPfNVbzSF6A42WgBDEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7x
4 | CgxcqFwEo33SyhAivwoHL2pRVHXnoee3z9U757T63YLE0qrXQY2cbyChX/OU99rZ
5 | xyd5l5jUGN7MCu+RYurfTIiYN+UvNZdl8a3X84g7fa70EOYas7cTunWUt9x64/jY
6 | DoYmn+XPXET1yEU1dQTnKY4cRjhvHS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknh
7 | FHTu8PVPxfowrVv/xzmxOe0zSZFdSbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW
8 | 91WzOLS9AgMBAAECggEAEOxEq6jFO/WgIPgHROPR42ok1J1AMgx7nGEIjnciImIX
9 | mJYBAtlOM+oUAYKoFBh/2eQTSyN2t4jo5AvZhjP6wBQKeE4HQN7supADRrwBF7KU
10 | WI+MKvZpW81KrzG8CUoLsikMEFpu52UAbYJkZmznzVeq/GqsAKGYLEXjauD7S5Tu
11 | GeGVKO4novus6t3AHnBvfalIQ1JUuJFvcd5ZDhPljlzPbbWdM4WpRPaFZIKmfXft
12 | G7Izt58yPCYwhxohjrunRudyX3oKvmCBUOBXC8HdHzND/dLxwlrVu7OjmXprmC6P
13 | 8ggNpjAPeO8Y6+EKGne1fETNsKgODY/lXGOwECY4eQKBgQDSGi3WuoT/+DecVeSF
14 | GfmavdGCQKOD0kdl7qCeQYAL+SPVz4157AtxZs3idapvlbrc7wvw4Ev1XT7ZmWUj
15 | Lc4/UAITR8EkkFRVbxt2PvV86AiQtmXFguTNEX5vTszRwZ2+eqijZga5niBkqyAi
16 | SRuTwR8WrDZau4mRNnF8bUl8dQKBgQC3BKYifRp4hHqBycHe9rSMZ8Xz+ZOy+IFA
17 | vYap1Az+e8KuqlmD9Kfpp2Mjba1+HL5WKeTJGpFE7bhvb/xMPJgbMgtQ/cw4uDJ/
18 | fwv4m6arf76ebOhaZtkT1vD4NyiyB+z6xP0TRgQRr2Or98XBSvGAYDXIn5vL7fUg
19 | KrDF0ePuKQKBgDfaOcFRiDW7uJzYwI0ZoJ8gQufLYyyR4+UXEJ/BbdbA/mPCbyuw
20 | MkKNP8Ip4YsUVL6S1avNFKQ/i4uxGY/Gh4ORM1wIwTGFJMYpaTV/+yafUFeYBWoC
21 | J+zT77aLTiucuuB+HwKBBtylSps4WqyCntAikK8oTLLGFAYEYRrgup5ZAoGAbQ8j
22 | JNghxwFCs0aT9ZZTfnt0NW9auUJmWzrVHSxUVe1P1J+EWiKXUJ/DbuAzizv7nAK4
23 | 57GiMU3rItS7pn5RMZt/rNKgOIhi5yDA9HNkPTwRTfyd9QjmgHEMBQ1xfa1FZSWv
24 | nSWS1SsLnPU37XgIMzShuByMTVhOQs3NqwPo7AkCgYAf8AzQNjFCoTwU3SJezJ4H
25 | 9j1jvMO232hAl8UDNtqvJ1APn87tOtnfX48OMoRrP9kKI0oygE3pq7rFxu1qmTns
26 | Zir0+KLeWGg58fSZkUEAp6kbO5CKwoeVAY9EMgd7BYBqlXLqUNfdH0L+KUOFKHha
27 | 7e82VxpgBeskzAqN1e7YRA==
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/docker/ssl/certs/magistrala-server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEYjCCA0oCFGXr7rfGAynaa4KMTG1+23EEF0lYMA0GCSqGSIb3DQEBCwUAMHUx
3 | IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h
4 | Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW
5 | E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjMxMDMwMDgxOTA4WhcNMjYwNzI2MDgx
6 | OTA4WjBmMRIwEAYDVQQDDAlsb2NhbGhvc3QxEzARBgNVBAoMCk1hZ2lzdHJhbGEx
7 | FzAVBgNVBAsMDm1hZ2lzdHJhbGFfY3J0MSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1h
8 | Z2lzdHJhbGEuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAojas
9 | t6M294uS5q8oFmYM6DULVQ1lY3K659VusJshjGvn8bi50vhKo8PpxL6ygVpjWcHG
10 | +/gclQnTaYZumC1TUohibpBnrFx1PZUvGiryAPudFY2nC5af5BQnYGi845FcVWx5
11 | FNLq+IsedgSZf7FuGcZruXiukBCWVyWJRJh+8FDakc65BPeG9FpCxbeLZ1nrDpnQ
12 | bhHbwEQrwwHk0FHZ/3cuVFJAjwqJSivJ9598eU0YWAsqsLM3uYyvOMd8alMs5vCZ
13 | 9tMCpO2v6xTdJ6kr68SwQQAiefRy6gsD5J5A4ySyCz7KX9fHCrqx1kdcDJ/CXZmh
14 | mXxrCFKSjqjuSn2qtm+gxvAc26Zbt5z5eihpdISDUKrjW11+yapNZLATGBX8ktek
15 | gW467V9DQYOsbA3fNkWgd5UcV5HIViUpqFMFvi1NpWc2INi/PTDWuAIBLUiVNk0W
16 | qMtG7/HqFRPn6MrNGpvFpglgxXGNfjsggkK/3INtFnAou2rN9+ieeuzO7Zjrtwsq
17 | sP64GVw/vLv3tgT6TIZmDnCDCqtEGEVutt7ldu3M0/fLm4qOUsZqFGrIOO1cfI4x
18 | 7FRnHwaTsTB1Og+I7lEujb4efHV+uRjKyrGh6L6hDt94IkGm6ZEj5z/iEmq16jRX
19 | dUbYsu4f1KlfTYdHWGHp+6kAmDn0jGCwz2BBrnsCAwEAATANBgkqhkiG9w0BAQsF
20 | AAOCAQEAKyg5kvDk+TQ6ZDCK7qxKY+uN9setYvvsLfde+Uy51a3zj8RIHRgkOT2C
21 | LuuTtTYKu3XmfCKId0oTXynGuP+yDAIuVwuZz3S0VmA8ijoZ87LJXzsLjjTjQSzZ
22 | ar6RmlRDH+8Bm4AOrT4TDupqifag4J0msHkNPo0jVK6fnuniqJoSlhIbbHrJTHhv
23 | jKNXrThjr/irgg1MZ7slojieOS0QoZHRE9eunIR5enDJwB5pWUJSmZWlisI7+Ibi
24 | 06+j8wZegU0nqeWp4wFSZxKnrzz5B5Qu9SrALwlHWirzBpyr0gAcF2v7nzbWviZ/
25 | 0VMyY4FGEbkp6trMxwJs5hGYhAiyXg==
26 | -----END CERTIFICATE-----
27 |
--------------------------------------------------------------------------------
/docker/ssl/dhparam.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIBCAKCAQEAquN8NRcSdLOM9RiumqWH8Jw3CGVR/eQQeq+jvT3zpxlUQPAMExQb
3 | MRCspm1oRgDWGvch3Z4zfMmBZyzKJA4BDTh4USzcE5zvnx8aUcUPZPQpwSicKgzb
4 | QGnl0Xf/75GAWrwhxn8GNyMP29wrpcd1Qg8fEQ3HAW1fCd9girKMKY9aBaHli/h2
5 | R9Rd/KTbeqN88aoMjUvZHooIIZXu0A+kyulOajYQO4k3Sp6CBqv0FFcoLQnYNH13
6 | kMUE5qJ68U732HybTw8sofTCOxKcCfM2kVP7dVoF3prlGjUw3z3l3STY8vuTdq0B
7 | R7PslkoQHNmqcL+2gouoWP3GI+IeRzGSSwIBAg==
8 | -----END DH PARAMETERS-----
9 |
--------------------------------------------------------------------------------
/docker/supermq-docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | FROM golang:1.24.3-alpine AS builder
5 | ARG SVC
6 | ARG GOARCH
7 | ARG GOARM
8 | ARG VERSION
9 | ARG COMMIT
10 | ARG TIME
11 |
12 | WORKDIR /go/src/github.com/absmach/supermq
13 | COPY . .
14 | RUN apk update \
15 | && apk add make upx\
16 | && make $SVC \
17 | && upx build/$SVC \
18 | && mv build/$SVC /exe
19 |
20 | FROM scratch
21 | # Certificates are needed so that mailing util can work.
22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
23 | COPY --from=builder /exe /
24 | ENTRYPOINT ["/exe"]
25 |
--------------------------------------------------------------------------------
/docker/supermq-docker/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | FROM scratch
5 | ARG SVC
6 | COPY $SVC /exe
7 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
8 | ENTRYPOINT ["/exe"]
9 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/certs/config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | common_name: "AbstractMachines_Selfsigned_ca"
5 | organization:
6 | - "AbstractMacines"
7 | organizational_unit:
8 | - "AbstractMachines_ca"
9 | country:
10 | - "France"
11 | province:
12 | - "Paris"
13 | locality:
14 | - "Quai de Valmy"
15 | postal_code:
16 | - "75010 Paris"
17 | dns_names:
18 | - "localhost"
19 | ip_addresses:
20 | - "localhost"
21 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/prometheus/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # This docker-compose file contains optional Prometheus and Grafana service for SuperMQ platform.
5 | # Since this service is optional, this file is dependent of docker-compose.yaml file
6 | # from /docker. In order to run this service, execute command:
7 | # docker compose -f docker/addons/prometheus/docker-compose.yaml up
8 | # from project root.
9 |
10 | networks:
11 | supermq-base-net:
12 | name: supermq-base-net
13 | external: true
14 |
15 | volumes:
16 | supermq-prometheus-volume:
17 |
18 |
19 | services:
20 | promethues:
21 | image: prom/prometheus:v2.49.1
22 | container_name: supermq-prometheus
23 | restart: on-failure
24 | ports:
25 | - ${SMQ_PROMETHEUS_PORT}:${SMQ_PROMETHEUS_PORT}
26 | networks:
27 | - supermq-base-net
28 | volumes:
29 | - type: bind
30 | source: ./metrics/prometheus.yaml
31 | target: /etc/prometheus/prometheus.yaml
32 | - supermq-prometheus-volume:/prometheus
33 |
34 | grafana:
35 | image: grafana/grafana:10.2.3
36 | container_name: supermq-grafana
37 | depends_on:
38 | - promethues
39 | restart: on-failure
40 | ports:
41 | - ${SMQ_GRAFANA_PORT}:${SMQ_GRAFANA_PORT}
42 | environment:
43 | - GF_SECURITY_ADMIN_USER=${SMQ_GRAFANA_ADMIN_USER}
44 | - GF_SECURITY_ADMIN_PASSWORD=${SMQ_GRAFANA_ADMIN_PASSWORD}
45 | networks:
46 | - supermq-base-net
47 | volumes:
48 | - type: bind
49 | source: ./grafana/datasource.yaml
50 | target: /etc/grafana/provisioning/datasources/datasource.yaml
51 | - type: bind
52 | source: ./grafana/dashboard.yaml
53 | target: /etc/grafana/provisioning/dashboards/main.yaml
54 | - type: bind
55 | source: ./grafana/example-dashboard.json
56 | target: /var/lib/grafana/dashboards/example-dashboard.json
57 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/prometheus/grafana/dashboard.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | apiVersion: 1
5 |
6 | providers:
7 | - name: "Dashboard provider"
8 | orgId: 1
9 | type: file
10 | disableDeletion: false
11 | updateIntervalSeconds: 10
12 | allowUiUpdates: false
13 | options:
14 | path: /var/lib/grafana/dashboards
15 | foldersFromFilesStructure: true
16 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/prometheus/grafana/datasource.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | apiVersion: 1
5 |
6 | datasources:
7 | - name: Prometheus
8 | type: prometheus
9 | url: http://supermq-prometheus:9090
10 | isDefault: true
11 | access: proxy
12 | editable: true
13 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/prometheus/metrics/prometheus.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | global:
5 | scrape_interval: 15s
6 | evaluation_interval: 15s
7 |
8 | scrape_configs:
9 | - job_name: 'supermq'
10 | honor_timestamps: true
11 | scrape_interval: 15s
12 | scrape_timeout: 10s
13 | metrics_path: /metrics
14 | follow_redirects: true
15 | enable_http2: true
16 | static_configs:
17 | - targets:
18 | - supermq-clients:9000
19 | - supermq-users:9002
20 | - supermq-http:8008
21 | - supermq-ws:8186
22 | - supermq-coap:5683
23 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/config.hcl:
--------------------------------------------------------------------------------
1 | storage "file" {
2 | path = "/vault/file"
3 | }
4 |
5 | listener "tcp" {
6 | address = "0.0.0.0:8200"
7 | tls_disable = 1
8 | }
9 |
10 | ui = true
11 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # This docker-compose file contains optional Vault service for SuperMQ platform.
5 | # Since this is optional, this file is dependent of docker-compose file
6 | # from /docker. In order to run these services, execute command:
7 | # docker compose -f docker/docker-compose.yaml -f docker/addons/vault/docker-compose.yaml up
8 | # from project root. Vault default port (8200) is exposed, so you can use Vault CLI tool for
9 | # vault inspection and administration, as well as access the UI.
10 |
11 | networks:
12 | supermq-base-net:
13 | name: supermq-base-net
14 | external: true
15 |
16 | volumes:
17 | supermq-vault-volume:
18 |
19 |
20 | services:
21 | vault:
22 | image: hashicorp/vault:1.15.4
23 | container_name: supermq-vault
24 | ports:
25 | - ${SMQ_VAULT_PORT}:8200
26 | networks:
27 | - supermq-base-net
28 | volumes:
29 | - supermq-vault-volume:/vault/file
30 | - supermq-vault-volume:/vault/logs
31 | - ./config.hcl:/vault/config/config.hcl
32 | - ./entrypoint.sh:/entrypoint.sh
33 | environment:
34 | VAULT_ADDR: http://127.0.0.1:${SMQ_VAULT_PORT}
35 | SMQ_VAULT_PORT: ${SMQ_VAULT_PORT}
36 | SMQ_VAULT_UNSEAL_KEY_1: ${SMQ_VAULT_UNSEAL_KEY_1}
37 | SMQ_VAULT_UNSEAL_KEY_2: ${SMQ_VAULT_UNSEAL_KEY_2}
38 | SMQ_VAULT_UNSEAL_KEY_3: ${SMQ_VAULT_UNSEAL_KEY_3}
39 | entrypoint: /bin/sh
40 | command: /entrypoint.sh
41 | cap_add:
42 | - IPC_LOCK
43 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/dumb-init /bin/sh
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | VAULT_CONFIG_DIR=/vault/config
6 |
7 | docker-entrypoint.sh server &
8 | VAULT_PID=$!
9 |
10 | sleep 2
11 |
12 | echo $SMQ_VAULT_UNSEAL_KEY_1
13 | echo $SMQ_VAULT_UNSEAL_KEY_2
14 | echo $SMQ_VAULT_UNSEAL_KEY_3
15 |
16 | if [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_1}" ]] &&
17 | [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_2}" ]] &&
18 | [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_3}" ]]; then
19 | echo "Unsealing Vault"
20 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_1}
21 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_2}
22 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_3}
23 | fi
24 |
25 | wait $VAULT_PID
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | data
5 | supermq_clients_certs_issue.hcl
6 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/supermq_clients_certs_issue.template.hcl:
--------------------------------------------------------------------------------
1 |
2 | # Allow issue certificate with role with default issuer from Intermediate PKI
3 | path "${SMQ_VAULT_PKI_INT_PATH}/issue/${SMQ_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME}" {
4 | capabilities = ["create", "update"]
5 | }
6 |
7 | ## Revole certificate from Intermediate PKI
8 | path "${SMQ_VAULT_PKI_INT_PATH}/revoke" {
9 | capabilities = ["create", "update"]
10 | }
11 |
12 | ## List Revoked Certificates from Intermediate PKI
13 | path "${SMQ_VAULT_PKI_INT_PATH}/certs/revoked" {
14 | capabilities = ["list"]
15 | }
16 |
17 |
18 | ## List Certificates from Intermediate PKI
19 | path "${SMQ_VAULT_PKI_INT_PATH}/certs" {
20 | capabilities = ["list"]
21 | }
22 |
23 | ## Read Certificate from Intermediate PKI
24 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+" {
25 | capabilities = ["read"]
26 | }
27 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+/raw" {
28 | capabilities = ["read"]
29 | }
30 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+/raw/pem" {
31 | capabilities = ["read"]
32 | }
33 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/vault_cmd.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | vault() {
6 | if is_container_running "supermq-vault"; then
7 | docker exec -it supermq-vault vault "$@"
8 | else
9 | if which vault &> /dev/null; then
10 | $(which vault) "$@"
11 | else
12 | echo "supermq-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/supermq/blob/main/docker/addons/vault/README.md"
13 | fi
14 | fi
15 | }
16 |
17 | is_container_running() {
18 | local container_name="$1"
19 | if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then
20 | return 0
21 | else
22 | return 1
23 | fi
24 | }
25 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/vault_copy_env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | write_env() {
35 | if [ -e "$scriptdir/data/secrets" ]; then
36 | if [[ "$(uname)" == "Darwin" ]]; then
37 | SED_OPT=(-i '')
38 | else
39 | SED_OPT=(-i)
40 | fi
41 |
42 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_1=.*,SMQ_VAULT_UNSEAL_KEY_1=$(awk -F ': ' '$1 == "Unseal Key 1" {print $2}' "$scriptdir/data/secrets")," "$env_file"
43 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_2=.*,SMQ_VAULT_UNSEAL_KEY_2=$(awk -F ': ' '$1 == "Unseal Key 2" {print $2}' "$scriptdir/data/secrets")," "$env_file"
44 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_3=.*,SMQ_VAULT_UNSEAL_KEY_3=$(awk -F ': ' '$1 == "Unseal Key 3" {print $2}' "$scriptdir/data/secrets")," "$env_file"
45 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_TOKEN=.*,SMQ_VAULT_TOKEN=$(awk -F ': ' '$1 == "Initial Root Token" {print $2}' "$scriptdir/data/secrets")," "$env_file"
46 | echo "Vault environment variables are set successfully in $env_file"
47 | else
48 | echo "Error: Source file '$scriptdir/data/secrets' not found."
49 | fi
50 | }
51 |
52 | write_env
53 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/vault_init.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | readDotEnv() {
35 | set -o allexport
36 | source "$env_file"
37 | set +o allexport
38 | }
39 |
40 | source "$scriptdir/vault_cmd.sh"
41 |
42 | readDotEnv
43 |
44 | mkdir -p "$scriptdir/data"
45 |
46 | vault operator init -address="$SMQ_VAULT_ADDR" 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > "$scriptdir/data/secrets")
47 |
--------------------------------------------------------------------------------
/docker/supermq-docker/addons/vault/scripts/vault_unseal.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | set -euo pipefail
6 |
7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
8 |
9 | # default env file path
10 | env_file="docker/.env"
11 |
12 | while [[ "$#" -gt 0 ]]; do
13 | case $1 in
14 | --env-file)
15 | if [[ -z "${2:-}" ]]; then
16 | echo "Error: --env-file requires a non-empty option argument."
17 | exit 1
18 | fi
19 | env_file="$2"
20 | if [[ ! -f "$env_file" ]]; then
21 | echo "Error: .env file not found at $env_file"
22 | exit 1
23 | fi
24 | shift
25 | ;;
26 | *)
27 | echo "Unknown parameter passed: $1"
28 | exit 1
29 | ;;
30 | esac
31 | shift
32 | done
33 |
34 | readDotEnv() {
35 | set -o allexport
36 | source "$env_file"
37 | set +o allexport
38 | }
39 |
40 | source "$scriptdir/vault_cmd.sh"
41 |
42 | readDotEnv
43 |
44 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_1}
45 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_2}
46 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_3}
47 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nats/nats.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | server_name: "nats_internal_broker"
5 | max_payload: 1MB
6 | max_connections: 1M
7 | port: $SMQ_NATS_PORT
8 | http_port: $SMQ_NATS_HTTP_PORT
9 | trace: true
10 |
11 | jetstream {
12 | store_dir: "/data"
13 | cipher: "aes"
14 | key: $SMQ_NATS_JETSTREAM_KEY
15 | max_mem: 1G
16 | }
17 |
18 | mqtt {
19 | port: 1883
20 | max_ack_pending: 1
21 | }
22 |
23 | websocket {
24 | port: 8080
25 |
26 | no_tls: true
27 | }
28 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | snippets/mqtt-upstream.conf
5 | snippets/mqtt-ws-upstream.conf
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/ash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | if [ -z "$SMQ_MQTT_CLUSTER" ]
6 | then
7 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-single.conf > /etc/nginx/snippets/mqtt-upstream.conf
8 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-single.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf
9 | else
10 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-cluster.conf > /etc/nginx/snippets/mqtt-upstream.conf
11 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-cluster.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf
12 | fi
13 |
14 | envsubst '
15 | ${SMQ_NGINX_SERVER_NAME}
16 | ${SMQ_AUTH_HTTP_PORT}
17 | ${SMQ_DOMAINS_HTTP_PORT}
18 | ${SMQ_GROUPS_HTTP_PORT}
19 | ${SMQ_USERS_HTTP_PORT}
20 | ${SMQ_CLIENTS_HTTP_PORT}
21 | ${SMQ_CLIENTS_AUTH_HTTP_PORT}
22 | ${SMQ_CHANNELS_HTTP_PORT}
23 | ${SMQ_HTTP_ADAPTER_PORT}
24 | ${SMQ_NGINX_MQTT_PORT}
25 | ${SMQ_NGINX_MQTTS_PORT}
26 | ${SMQ_WS_ADAPTER_HTTP_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
27 |
28 | exec nginx -g "daemon off;"
29 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/http_access_log.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | log_format access_log_format 'HTTP/WS '
5 | '$remote_addr: '
6 | '"$request" $status; '
7 | 'request time=$request_time '
8 | 'upstream connect time=$upstream_connect_time '
9 | 'upstream address $upstream_addr '
10 | 'upstream status $upstream_status '
11 | 'upstream response time=$upstream_response_time';
12 | access_log access.log access_log_format;
13 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/mqtt-upstream-cluster.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | upstream mqtt_cluster {
5 | least_conn;
6 | server mqtt-adapter-1:${SMQ_MQTT_ADAPTER_MQTT_PORT};
7 | server mqtt-adapter-2:${SMQ_MQTT_ADAPTER_MQTT_PORT};
8 | server mqtt-adapter-3:${SMQ_MQTT_ADAPTER_MQTT_PORT};
9 | }
10 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/mqtt-upstream-single.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | upstream mqtt_cluster {
5 | server mqtt-adapter:${SMQ_MQTT_ADAPTER_MQTT_PORT};
6 | }
7 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/mqtt-ws-upstream-cluster.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | upstream mqtt_ws_cluster {
5 | least_conn;
6 | server mqtt-adapter-1:${SMQ_MQTT_ADAPTER_WS_PORT};
7 | server mqtt-adapter-2:${SMQ_MQTT_ADAPTER_WS_PORT};
8 | server mqtt-adapter-3:${SMQ_MQTT_ADAPTER_WS_PORT};
9 | }
10 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/mqtt-ws-upstream-single.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | upstream mqtt_ws_cluster {
5 | server mqtt-adapter:${SMQ_MQTT_ADAPTER_WS_PORT};
6 | }
7 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/proxy-headers.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | proxy_redirect off;
5 | proxy_set_header Host $host;
6 | proxy_set_header X-Real-IP $remote_addr;
7 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
8 | proxy_set_header X-Forwarded-Proto $scheme;
9 |
10 | # Allow OPTIONS method CORS
11 | if ($request_method = OPTIONS) {
12 | add_header Content-Length 0;
13 | add_header Content-Type text/plain;
14 | return 200;
15 | }
16 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/ssl-client.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | ssl_client_certificate /etc/ssl/certs/ca.crt;
5 | ssl_verify_depth 2;
6 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/ssl.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # These paths are set to its default values as
5 | # a volume in the docker/docker-compose.yaml file.
6 | ssl_certificate /etc/ssl/certs/supermq-server.crt;
7 | ssl_certificate_key /etc/ssl/private/supermq-server.key;
8 | ssl_dhparam /etc/ssl/certs/dhparam.pem;
9 |
10 | ssl_protocols TLSv1.2 TLSv1.3;
11 | ssl_prefer_server_ciphers on;
12 | ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
13 | ssl_ecdh_curve secp384r1;
14 | ssl_session_tickets off;
15 | resolver 8.8.8.8 8.8.4.4 valid=300s;
16 | resolver_timeout 5s;
17 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/stream_access_log.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | log_format access_log_format '$protocol '
5 | '$remote_addr: '
6 | 'status=$status; upstream connect time=$upstream_connect_time';
7 | access_log access.log access_log_format;
8 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/verify-ssl-client.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | if ($ssl_client_verify != SUCCESS) {
5 | return 403;
6 | }
7 | if ($auth_key = '') {
8 | return 403;
9 | }
10 |
--------------------------------------------------------------------------------
/docker/supermq-docker/nginx/snippets/ws-upgrade.conf:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | proxy_http_version 1.1;
5 | proxy_set_header Upgrade $http_upgrade;
6 | proxy_set_header Connection "Upgrade";
7 | proxy_connect_timeout 7d;
8 | proxy_send_timeout 7d;
9 | proxy_read_timeout 7d;
10 |
--------------------------------------------------------------------------------
/docker/supermq-docker/rabbitmq/enabled_plugins:
--------------------------------------------------------------------------------
1 | [rabbitmq_management,rabbitmq_mqtt,rabbitmq_web_mqtt].
2 |
--------------------------------------------------------------------------------
/docker/supermq-docker/rabbitmq/rabbitmq.conf:
--------------------------------------------------------------------------------
1 | ## DEFAULT SETTINGS ARE NOT MEANT TO BE TAKEN STRAIGHT INTO PRODUCTION
2 | ## see https://www.rabbitmq.com/configure.html for further information
3 | ## on configuring RabbitMQ
4 |
5 | ## allow access to the guest user from anywhere on the network
6 | ## https://www.rabbitmq.com/access-control.html#loopback-users
7 | ## https://www.rabbitmq.com/production-checklist.html#users
8 | loopback_users.guest = false
9 |
10 | ## Send all logs to stdout/TTY. Necessary to see logs when running via
11 | ## a container
12 | log.console = true
13 |
14 | ## Enable anonymous connection
15 | mqtt.allow_anonymous = true
16 |
--------------------------------------------------------------------------------
/docker/supermq-docker/ssl/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | *grpc-server*
5 | *grpc-client*
6 | *srl
7 | *conf
8 |
--------------------------------------------------------------------------------
/docker/supermq-docker/ssl/certs/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDyzCCArOgAwIBAgIUDIJg63dQVzoD9nmWi9YPscQwTgIwDQYJKoZIhvcNAQEN
3 | BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE
4 | CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3
5 | DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yMzEwMzAwODE5MDFaFw0yNjEw
6 | MjkwODE5MDFaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex
7 | EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg
8 | BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB
9 | AQUAA4IBDwAwggEKAoIBAQCWNIeGfo/SePOvviJE6UHJhBzWcPfNVbzSF6A42WgB
10 | DEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7xCgxcqFwEo33SyhAivwoHL2pRVHXn
11 | oee3z9U757T63YLE0qrXQY2cbyChX/OU99rZxyd5l5jUGN7MCu+RYurfTIiYN+Uv
12 | NZdl8a3X84g7fa70EOYas7cTunWUt9x64/jYDoYmn+XPXET1yEU1dQTnKY4cRjhv
13 | HS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknhFHTu8PVPxfowrVv/xzmxOe0zSZFd
14 | SbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW91WzOLS9AgMBAAGjUzBRMB0GA1Ud
15 | DgQWBBQkE4koZctEZpTz9pq6a6s6xg+myTAfBgNVHSMEGDAWgBQkE4koZctEZpTz
16 | 9pq6a6s6xg+myTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQA7
17 | w/oh5U9loJsigf3X3T3jQM8PVmhsUfNMJ3kc1Yumr72S4sGKjdWwuU0vk+B3eQzh
18 | zXAj65BHhs1pXcukeoLR7YcHABEsEMg6lar/E4A+MgAZfZFVSvPpsByIK8I5ARk+
19 | K1V/lWso+GJJM/lImPPnpvUWBdbntqC5WtjoMMGL9uyV3kVS6yT/kJ2ercnPzhPh
20 | uBkL1ZH3ivDn/0JDY+T8Sfeq08vNWaTcoC7qpPwqXhuT0ytY7oaBS5wmPcvvzpZg
21 | 6zZYPZfhjhdEFYY1hDrrPYNYO72jncUnwQVp3X0DQpSvbxp681hVkcEtwHB2B8l0
22 | tBGhgoH+TqZs0AUjoXM0
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/docker/supermq-docker/ssl/certs/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWNIeGfo/SePOv
3 | viJE6UHJhBzWcPfNVbzSF6A42WgBDEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7x
4 | CgxcqFwEo33SyhAivwoHL2pRVHXnoee3z9U757T63YLE0qrXQY2cbyChX/OU99rZ
5 | xyd5l5jUGN7MCu+RYurfTIiYN+UvNZdl8a3X84g7fa70EOYas7cTunWUt9x64/jY
6 | DoYmn+XPXET1yEU1dQTnKY4cRjhvHS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknh
7 | FHTu8PVPxfowrVv/xzmxOe0zSZFdSbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW
8 | 91WzOLS9AgMBAAECggEAEOxEq6jFO/WgIPgHROPR42ok1J1AMgx7nGEIjnciImIX
9 | mJYBAtlOM+oUAYKoFBh/2eQTSyN2t4jo5AvZhjP6wBQKeE4HQN7supADRrwBF7KU
10 | WI+MKvZpW81KrzG8CUoLsikMEFpu52UAbYJkZmznzVeq/GqsAKGYLEXjauD7S5Tu
11 | GeGVKO4novus6t3AHnBvfalIQ1JUuJFvcd5ZDhPljlzPbbWdM4WpRPaFZIKmfXft
12 | G7Izt58yPCYwhxohjrunRudyX3oKvmCBUOBXC8HdHzND/dLxwlrVu7OjmXprmC6P
13 | 8ggNpjAPeO8Y6+EKGne1fETNsKgODY/lXGOwECY4eQKBgQDSGi3WuoT/+DecVeSF
14 | GfmavdGCQKOD0kdl7qCeQYAL+SPVz4157AtxZs3idapvlbrc7wvw4Ev1XT7ZmWUj
15 | Lc4/UAITR8EkkFRVbxt2PvV86AiQtmXFguTNEX5vTszRwZ2+eqijZga5niBkqyAi
16 | SRuTwR8WrDZau4mRNnF8bUl8dQKBgQC3BKYifRp4hHqBycHe9rSMZ8Xz+ZOy+IFA
17 | vYap1Az+e8KuqlmD9Kfpp2Mjba1+HL5WKeTJGpFE7bhvb/xMPJgbMgtQ/cw4uDJ/
18 | fwv4m6arf76ebOhaZtkT1vD4NyiyB+z6xP0TRgQRr2Or98XBSvGAYDXIn5vL7fUg
19 | KrDF0ePuKQKBgDfaOcFRiDW7uJzYwI0ZoJ8gQufLYyyR4+UXEJ/BbdbA/mPCbyuw
20 | MkKNP8Ip4YsUVL6S1avNFKQ/i4uxGY/Gh4ORM1wIwTGFJMYpaTV/+yafUFeYBWoC
21 | J+zT77aLTiucuuB+HwKBBtylSps4WqyCntAikK8oTLLGFAYEYRrgup5ZAoGAbQ8j
22 | JNghxwFCs0aT9ZZTfnt0NW9auUJmWzrVHSxUVe1P1J+EWiKXUJ/DbuAzizv7nAK4
23 | 57GiMU3rItS7pn5RMZt/rNKgOIhi5yDA9HNkPTwRTfyd9QjmgHEMBQ1xfa1FZSWv
24 | nSWS1SsLnPU37XgIMzShuByMTVhOQs3NqwPo7AkCgYAf8AzQNjFCoTwU3SJezJ4H
25 | 9j1jvMO232hAl8UDNtqvJ1APn87tOtnfX48OMoRrP9kKI0oygE3pq7rFxu1qmTns
26 | Zir0+KLeWGg58fSZkUEAp6kbO5CKwoeVAY9EMgd7BYBqlXLqUNfdH0L+KUOFKHha
27 | 7e82VxpgBeskzAqN1e7YRA==
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/docker/supermq-docker/ssl/certs/supermq-server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEYjCCA0oCFGXr7rfGAynaa4KMTG1+23EEF0lYMA0GCSqGSIb3DQEBCwUAMHUx
3 | IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h
4 | Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW
5 | E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjMxMDMwMDgxOTA4WhcNMjYwNzI2MDgx
6 | OTA4WjBmMRIwEAYDVQQDDAlsb2NhbGhvc3QxEzARBgNVBAoMCk1hZ2lzdHJhbGEx
7 | FzAVBgNVBAsMDm1hZ2lzdHJhbGFfY3J0MSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1h
8 | Z2lzdHJhbGEuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAojas
9 | t6M294uS5q8oFmYM6DULVQ1lY3K659VusJshjGvn8bi50vhKo8PpxL6ygVpjWcHG
10 | +/gclQnTaYZumC1TUohibpBnrFx1PZUvGiryAPudFY2nC5af5BQnYGi845FcVWx5
11 | FNLq+IsedgSZf7FuGcZruXiukBCWVyWJRJh+8FDakc65BPeG9FpCxbeLZ1nrDpnQ
12 | bhHbwEQrwwHk0FHZ/3cuVFJAjwqJSivJ9598eU0YWAsqsLM3uYyvOMd8alMs5vCZ
13 | 9tMCpO2v6xTdJ6kr68SwQQAiefRy6gsD5J5A4ySyCz7KX9fHCrqx1kdcDJ/CXZmh
14 | mXxrCFKSjqjuSn2qtm+gxvAc26Zbt5z5eihpdISDUKrjW11+yapNZLATGBX8ktek
15 | gW467V9DQYOsbA3fNkWgd5UcV5HIViUpqFMFvi1NpWc2INi/PTDWuAIBLUiVNk0W
16 | qMtG7/HqFRPn6MrNGpvFpglgxXGNfjsggkK/3INtFnAou2rN9+ieeuzO7Zjrtwsq
17 | sP64GVw/vLv3tgT6TIZmDnCDCqtEGEVutt7ldu3M0/fLm4qOUsZqFGrIOO1cfI4x
18 | 7FRnHwaTsTB1Og+I7lEujb4efHV+uRjKyrGh6L6hDt94IkGm6ZEj5z/iEmq16jRX
19 | dUbYsu4f1KlfTYdHWGHp+6kAmDn0jGCwz2BBrnsCAwEAATANBgkqhkiG9w0BAQsF
20 | AAOCAQEAKyg5kvDk+TQ6ZDCK7qxKY+uN9setYvvsLfde+Uy51a3zj8RIHRgkOT2C
21 | LuuTtTYKu3XmfCKId0oTXynGuP+yDAIuVwuZz3S0VmA8ijoZ87LJXzsLjjTjQSzZ
22 | ar6RmlRDH+8Bm4AOrT4TDupqifag4J0msHkNPo0jVK6fnuniqJoSlhIbbHrJTHhv
23 | jKNXrThjr/irgg1MZ7slojieOS0QoZHRE9eunIR5enDJwB5pWUJSmZWlisI7+Ibi
24 | 06+j8wZegU0nqeWp4wFSZxKnrzz5B5Qu9SrALwlHWirzBpyr0gAcF2v7nzbWviZ/
25 | 0VMyY4FGEbkp6trMxwJs5hGYhAiyXg==
26 | -----END CERTIFICATE-----
27 |
--------------------------------------------------------------------------------
/docker/supermq-docker/ssl/dhparam.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIBCAKCAQEAquN8NRcSdLOM9RiumqWH8Jw3CGVR/eQQeq+jvT3zpxlUQPAMExQb
3 | MRCspm1oRgDWGvch3Z4zfMmBZyzKJA4BDTh4USzcE5zvnx8aUcUPZPQpwSicKgzb
4 | QGnl0Xf/75GAWrwhxn8GNyMP29wrpcd1Qg8fEQ3HAW1fCd9girKMKY9aBaHli/h2
5 | R9Rd/KTbeqN88aoMjUvZHooIIZXu0A+kyulOajYQO4k3Sp6CBqv0FFcoLQnYNH13
6 | kMUE5qJ68U732HybTw8sofTCOxKcCfM2kVP7dVoF3prlGjUw3z3l3STY8vuTdq0B
7 | R7PslkoQHNmqcL+2gouoWP3GI+IeRzGSSwIBAg==
8 | -----END DH PARAMETERS-----
9 |
--------------------------------------------------------------------------------
/docker/supermq-docker/templates/smtp-notifier.tmpl:
--------------------------------------------------------------------------------
1 | To: {{range $index, $v := .To}}{{if $index}},{{end}}{{$v}}{{end}}
2 | From: {{.From}}
3 | Subject: {{.Subject}}
4 | {{.Header}}
5 | You have a new message:
6 | {{.Content}}
7 | {{.Footer}}
8 |
9 |
--------------------------------------------------------------------------------
/docker/supermq-docker/templates/users.tmpl:
--------------------------------------------------------------------------------
1 | Dear {{.User}},
2 |
3 | We have received a request to reset your password for your account on {{.Host}}. To proceed with resetting your password, please click on the link below:
4 |
5 | {{.Content}}
6 |
7 | If you did not initiate this request, please disregard this message and your password will remain unchanged.
8 |
9 | Thank you for using {{.Host}}.
10 |
11 | Best regards,
12 |
13 | {{.Footer}}
14 |
--------------------------------------------------------------------------------
/docker/templates/re.tmpl:
--------------------------------------------------------------------------------
1 | {{.Header}}
2 | {{.Content}}
3 | {{.Footer}}
4 |
5 |
--------------------------------------------------------------------------------
/internal/clients/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package clients contains the domain concept definitions needed to support
5 | // Magistrala clients functionality for example: postgres, redis, grpc, jaeger.
6 | package clients
7 |
--------------------------------------------------------------------------------
/internal/clients/redis/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package redis contains the domain concept definitions needed to support
5 | // Magistrala redis cache functionality.
6 | //
7 | // It provides the abstraction of the redis cache service, which is used
8 | // to configure, setup and connect to the redis cache.
9 | package redis
10 |
--------------------------------------------------------------------------------
/internal/clients/redis/redis.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package redis
5 |
6 | import "github.com/redis/go-redis/v9"
7 |
8 | // Connect create new RedisDB client and connect to RedisDB server.
9 | func Connect(url string) (*redis.Client, error) {
10 | opts, err := redis.ParseURL(url)
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | return redis.NewClient(opts), nil
16 | }
17 |
--------------------------------------------------------------------------------
/internal/email/README.md:
--------------------------------------------------------------------------------
1 | # Magistrala Email Agent
2 |
3 | Magistrala Email Agent is used for sending emails. It wraps basic SMTP features and
4 | provides a simple API that Magistrala services can use to send email notifications.
5 |
6 | ## Configuration
7 |
8 | Magistrala Email Agent is configured using the following configuration parameters:
9 |
10 | | Parameter | Description |
11 | | ----------------------------------- | ----------------------------------------------------------------------- |
12 | | MG_EMAIL_HOST | Mail server host |
13 | | MG_EMAIL_PORT | Mail server port |
14 | | MG_EMAIL_USERNAME | Mail server username |
15 | | MG_EMAIL_PASSWORD | Mail server password |
16 | | MG_EMAIL_FROM_ADDRESS | Email "from" address |
17 | | MG_EMAIL_FROM_NAME | Email "from" name |
18 | | MG_EMAIL_TEMPLATE | Email template for sending notification emails |
19 |
20 | There are two authentication methods supported: Basic Auth and CRAM-MD5.
21 | If `MG_EMAIL_USERNAME` is empty, no authentication will be used.
22 |
--------------------------------------------------------------------------------
/internal/email/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package email contains the domain concept definitions needed to support
5 | // Magistrala email functionality.
6 | package email
7 |
--------------------------------------------------------------------------------
/internal/proto/readers/v1/readers.proto:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | syntax = "proto3";
5 |
6 | package readers.v1;
7 |
8 | option go_package = "github.com/absmach/magistrala/api/grpc/readers/v1";
9 |
10 | // ReadersService is a service that provides access to
11 | // readers functionalities for Magistrala services.
12 | service ReadersService {
13 | rpc ReadMessages(ReadMessagesReq)
14 | returns (ReadMessagesRes) {}
15 | }
16 |
17 | message PageMetadata {
18 | uint64 limit = 1;
19 | uint64 offset = 2;
20 | string protocol = 3;
21 | string name = 4;
22 | double value = 5;
23 | string publisher = 6;
24 | bool bool_value = 7;
25 | string string_value = 8;
26 | string data_value = 9;
27 | double from = 10;
28 | double to = 11;
29 | string subtopic = 12;
30 | string interval = 13;
31 | bool read = 14;
32 | Aggregation aggregation = 15;
33 | string comparator = 16;
34 | string format = 17;
35 | }
36 |
37 | message ReadMessagesRes {
38 | uint64 total = 1;
39 | PageMetadata page_metadata = 2;
40 | repeated Message messages = 3;
41 | }
42 |
43 | message Message {
44 | oneof payload {
45 | SenMLMessage senml = 1;
46 | JsonMessage json = 2;
47 | }
48 | }
49 |
50 | message BaseMessage {
51 | string channel = 1;
52 | string subtopic = 2;
53 | string publisher = 3;
54 | string protocol = 4;
55 | }
56 |
57 | message SenMLMessage {
58 | BaseMessage base = 1;
59 | string name = 2;
60 | string unit = 3;
61 | double time = 4;
62 | double update_time = 5;
63 | optional double value = 6;
64 | optional string string_value = 7;
65 | optional string data_value = 8;
66 | optional bool bool_value = 9;
67 | optional double sum = 10;
68 | }
69 |
70 | message JsonMessage {
71 | BaseMessage base = 1;
72 | int64 created = 2;
73 | bytes payload = 3;
74 | }
75 |
76 | message ReadMessagesReq {
77 | string channel_id = 1;
78 | string domain_id = 2;
79 | PageMetadata page_metadata = 3;
80 | }
81 |
82 | // Aggregation defines supported data aggregations.
83 | enum Aggregation {
84 | AGGREGATION_UNSPECIFIED = 0;
85 | MAX = 1;
86 | MIN = 2;
87 | SUM = 3;
88 | COUNT = 4;
89 | AVG = 5;
90 | }
91 |
--------------------------------------------------------------------------------
/internal/testsutil/common.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package testsutil
5 |
6 | import (
7 | "fmt"
8 | "testing"
9 |
10 | "github.com/absmach/supermq/pkg/uuid"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func GenerateUUID(t *testing.T) string {
15 | idProvider := uuid.New()
16 | ulid, err := idProvider.ID()
17 | require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
18 | return ulid
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/README.md:
--------------------------------------------------------------------------------
1 | # Standalone packages
2 |
3 | The `pkg` directory (the current directory) contains a set of standalone packages that can be imported and used by external applications. The packages are specifically meant for the development of the Magistrala based back-end applications and implement common tasks needed by the programmatic operation of Magistrala platform.
4 |
--------------------------------------------------------------------------------
/pkg/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package pkg contains library packages used by Magistrala services
5 | // and external services that integrate with Magistrala.
6 | package pkg
7 |
--------------------------------------------------------------------------------
/pkg/errors/README.md:
--------------------------------------------------------------------------------
1 | # Errors
2 |
3 | `errors` package serve to build an arbitrary long error chain in order to capture errors returned from nested service calls.
4 |
5 | `errors` package contains the custom Go `error` interface implementation, `Error`. You use the `Error` interface to **wrap** two errors in a containing error as well as to test recursively if a given error **contains** some other error.
6 |
--------------------------------------------------------------------------------
/pkg/errors/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package errors contains Magistrala errors definitions.
5 | package errors
6 |
--------------------------------------------------------------------------------
/pkg/errors/repository/types.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package repository
5 |
6 | import "github.com/absmach/magistrala/pkg/errors"
7 |
8 | // Wrapper for Repository errors.
9 | var (
10 | // ErrMalformedEntity indicates a malformed entity specification.
11 | ErrMalformedEntity = errors.New("malformed entity specification")
12 |
13 | // ErrNotFound indicates a non-existent entity request.
14 | ErrNotFound = errors.New("entity not found")
15 |
16 | // ErrConflict indicates that entity already exists.
17 | ErrConflict = errors.New("entity already exists")
18 |
19 | // ErrCreateEntity indicates error in creating entity or entities.
20 | ErrCreateEntity = errors.New("failed to create entity in the db")
21 |
22 | // ErrViewEntity indicates error in viewing entity or entities.
23 | ErrViewEntity = errors.New("view entity failed")
24 |
25 | // ErrUpdateEntity indicates error in updating entity or entities.
26 | ErrUpdateEntity = errors.New("update entity failed")
27 |
28 | // ErrRemoveEntity indicates error in removing entity.
29 | ErrRemoveEntity = errors.New("failed to remove entity")
30 |
31 | // ErrFailedOpDB indicates a failure in a database operation.
32 | ErrFailedOpDB = errors.New("operation on db element failed")
33 |
34 | // ErrFailedToRetrieveAllGroups failed to retrieve groups.
35 | ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups")
36 |
37 | // ErrMissingNames indicates missing first and last names.
38 | ErrMissingNames = errors.New("missing first or last name")
39 | )
40 |
--------------------------------------------------------------------------------
/pkg/errors/types.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package errors
5 |
6 | import "errors"
7 |
8 | var (
9 | // ErrMalformedEntity indicates a malformed entity specification.
10 | ErrMalformedEntity = New("malformed entity specification")
11 |
12 | // ErrUnsupportedContentType indicates invalid content type.
13 | ErrUnsupportedContentType = errors.New("invalid content type")
14 |
15 | // ErrUnidentified indicates unidentified error.
16 | ErrUnidentified = errors.New("unidentified error")
17 |
18 | // ErrEmptyPath indicates empty file path.
19 | ErrEmptyPath = errors.New("empty file path")
20 |
21 | // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status.
22 | ErrStatusAlreadyAssigned = errors.New("status already assigned")
23 |
24 | // ErrRollbackTx indicates failed to rollback transaction.
25 | ErrRollbackTx = errors.New("failed to rollback transaction")
26 |
27 | // ErrAuthentication indicates failure occurred while authenticating the entity.
28 | ErrAuthentication = errors.New("failed to perform authentication over the entity")
29 |
30 | // ErrAuthorization indicates failure occurred while authorizing the entity.
31 | ErrAuthorization = errors.New("failed to perform authorization over the entity")
32 | )
33 |
--------------------------------------------------------------------------------
/pkg/prometheus/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package prometheus provides a framework for defining and collecting metrics
5 | // for prometheus.
6 | package prometheus
7 |
--------------------------------------------------------------------------------
/pkg/prometheus/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package prometheus
5 |
6 | import (
7 | kitprometheus "github.com/go-kit/kit/metrics/prometheus"
8 | stdprometheus "github.com/prometheus/client_golang/prometheus"
9 | )
10 |
11 | // MakeMetrics returns an instance of Prometheus implementations for metrics.
12 | // It returns a request counter and a request latency summary.
13 | //
14 | // counter, latency := metrics.MakeMetrics("demo-service", "api")
15 | func MakeMetrics(namespace, subsystem string) (*kitprometheus.Counter, *kitprometheus.Summary) {
16 | counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
17 | Namespace: namespace,
18 | Subsystem: subsystem,
19 | Name: "request_count",
20 | Help: "Number of requests received.",
21 | }, []string{"method"})
22 | latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
23 | Namespace: namespace,
24 | Subsystem: subsystem,
25 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
26 | Name: "request_latency_microseconds",
27 | Help: "Total duration of requests in microseconds.",
28 | }, []string{"method"})
29 |
30 | return counter, latency
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/reltime/reltime.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package reltime
5 |
6 | import (
7 | "fmt"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/absmach/magistrala/pkg/errors"
14 | )
15 |
16 | var (
17 | re = regexp.MustCompile(`(?i)^now\(\)([\+\-])(.+)$`)
18 |
19 | ErrInvalidDuration = errors.New("invalid duration format")
20 | ErrInvalidExpression = errors.New("invalid time expression")
21 | ErrUnsupportedUnit = errors.New("unsupported unit")
22 | )
23 |
24 | func Parse(expr string) (time.Time, error) {
25 | now := time.Now()
26 | expr = strings.ReplaceAll(expr, " ", "")
27 |
28 | if strings.EqualFold(expr, "now()") {
29 | return now, nil
30 | }
31 |
32 | matches := re.FindStringSubmatch(expr)
33 | if len(matches) != 3 {
34 | return time.Time{}, errors.Wrap(ErrInvalidExpression, fmt.Errorf("%s", expr))
35 | }
36 |
37 | sign := matches[1]
38 | durStr := matches[2]
39 | if strings.ContainsAny(durStr, "+-") {
40 | return time.Time{}, errors.Wrap(ErrInvalidExpression, fmt.Errorf("%s", expr))
41 | }
42 |
43 | dur, err := parseComplexDuration(durStr)
44 | if err != nil {
45 | return time.Time{}, err
46 | }
47 |
48 | if sign == "-" {
49 | return now.Add(-dur), nil
50 | }
51 | return now.Add(dur), nil
52 | }
53 |
54 | func parseComplexDuration(s string) (time.Duration, error) {
55 | var total time.Duration
56 | re := regexp.MustCompile(`(\d+)([smhdwMY])`)
57 | matches := re.FindAllStringSubmatch(s, -1)
58 |
59 | if matches == nil {
60 | return 0, errors.Wrap(ErrInvalidDuration, fmt.Errorf("%s", s))
61 | }
62 |
63 | for _, match := range matches {
64 | val, _ := strconv.Atoi(match[1])
65 | unit := match[2]
66 |
67 | var d time.Duration
68 | switch unit {
69 | case "s":
70 | d = time.Duration(val) * time.Second
71 | case "m":
72 | d = time.Duration(val) * time.Minute
73 | case "h":
74 | d = time.Duration(val) * time.Hour
75 | case "d":
76 | d = time.Duration(val) * 24 * time.Hour
77 | case "w":
78 | d = time.Duration(val) * 7 * 24 * time.Hour
79 | default:
80 | return 0, errors.Wrap(ErrUnsupportedUnit, fmt.Errorf("%s", unit))
81 | }
82 |
83 | total += d
84 | }
85 | return total, nil
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/reltime/reltime_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package reltime
5 |
6 | import (
7 | "fmt"
8 | "testing"
9 | "time"
10 |
11 | "github.com/absmach/magistrala/pkg/errors"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestParse(t *testing.T) {
16 | now := time.Now()
17 |
18 | tests := []struct {
19 | desc string
20 | expr string
21 | expected time.Time
22 | err error
23 | }{
24 | {
25 | desc: "testing expression now()-5d",
26 | expr: "now()-5d",
27 | expected: now.Add(-5 * 24 * time.Hour),
28 | err: nil,
29 | },
30 | {
31 | desc: "testing expression now()+2h30m",
32 | expr: "now()+2h30m",
33 | expected: now.Add(2*time.Hour + 30*time.Minute),
34 | err: nil,
35 | },
36 | {
37 | desc: "testing expression now()-1w3d10h40m",
38 | expr: "now()-1w3d10h40m",
39 | expected: now.Add(-(7*24+3*24+10)*time.Hour - 40*time.Minute),
40 | err: nil,
41 | },
42 | {
43 | desc: "testing expression yesterday",
44 | expr: "yesterday",
45 | err: ErrInvalidExpression,
46 | },
47 | {
48 | desc: "testing expression now()--5d",
49 | expr: "now()--5d",
50 | err: ErrInvalidExpression,
51 | },
52 | {
53 | desc: "testing expression now()+",
54 | expr: "now()+",
55 | err: ErrInvalidExpression,
56 | },
57 | {
58 | desc: "testing expression now()+5r",
59 | expr: "now()+5r",
60 | err: ErrInvalidDuration,
61 | },
62 | {
63 | desc: "testing expression now()+5M",
64 | expr: "now()+5M",
65 | err: ErrUnsupportedUnit,
66 | },
67 | }
68 |
69 | for _, tc := range tests {
70 | got, err := Parse(tc.expr)
71 | assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v and response time %v\n", tc.desc, tc.err, err, got))
72 | if err == nil {
73 | assert.WithinDuration(t, tc.expected, got, time.Duration(10*time.Second))
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/sdk/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package sdk contains Magistrala SDK.
5 | package sdk
6 |
--------------------------------------------------------------------------------
/pkg/sdk/messages.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package sdk
5 |
6 | import (
7 | "context"
8 | "encoding/json"
9 | "fmt"
10 | "net/http"
11 | "net/url"
12 | "strconv"
13 | "strings"
14 |
15 | "github.com/absmach/supermq/pkg/errors"
16 | )
17 |
18 | const channelParts = 2
19 |
20 | func (sdk mgSDK) ReadMessages(ctx context.Context, pm MessagePageMetadata, chanName, domainID, token string) (MessagesPage, errors.SDKError) {
21 | chanNameParts := strings.SplitN(chanName, ".", channelParts)
22 | chanID := chanNameParts[0]
23 | subtopicPart := ""
24 | if len(chanNameParts) == channelParts {
25 | subtopicPart = fmt.Sprintf("?subtopic=%s", chanNameParts[1])
26 | }
27 |
28 | msgURL, err := sdk.withMessageQueryParams(sdk.readersURL, fmt.Sprintf("%s/channels/%s/messages%s", domainID, chanID, subtopicPart), pm)
29 | if err != nil {
30 | return MessagesPage{}, errors.NewSDKError(err)
31 | }
32 |
33 | header := make(map[string]string)
34 | header["Content-Type"] = string(sdk.msgContentType)
35 |
36 | _, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, msgURL, token, nil, header, http.StatusOK)
37 | if sdkerr != nil {
38 | return MessagesPage{}, sdkerr
39 | }
40 |
41 | var mp MessagesPage
42 | if err := json.Unmarshal(body, &mp); err != nil {
43 | return MessagesPage{}, errors.NewSDKError(err)
44 | }
45 |
46 | return mp, nil
47 | }
48 |
49 | func (sdk mgSDK) withMessageQueryParams(baseURL, endpoint string, mpm MessagePageMetadata) (string, error) {
50 | b, err := json.Marshal(mpm)
51 | if err != nil {
52 | return "", err
53 | }
54 | q := map[string]interface{}{}
55 | if err := json.Unmarshal(b, &q); err != nil {
56 | return "", err
57 | }
58 | ret := url.Values{}
59 | for k, v := range q {
60 | switch t := v.(type) {
61 | case string:
62 | ret.Add(k, t)
63 | case float64:
64 | ret.Add(k, strconv.FormatFloat(t, 'f', -1, 64))
65 | case uint64:
66 | ret.Add(k, strconv.FormatUint(t, 10))
67 | case int64:
68 | ret.Add(k, strconv.FormatInt(t, 10))
69 | case json.Number:
70 | ret.Add(k, t.String())
71 | case bool:
72 | ret.Add(k, strconv.FormatBool(t))
73 | }
74 | }
75 | qs := ret.Encode()
76 |
77 | return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, qs), nil
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/sdk/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package sdk
5 |
6 | import "github.com/absmach/supermq/pkg/transformers/senml"
7 |
8 | type PageRes struct {
9 | Total uint64 `json:"total"`
10 | Offset uint64 `json:"offset"`
11 | Limit uint64 `json:"limit"`
12 | }
13 |
14 | // bootstrapsPage contains list of bootstrap configs in a page with proper metadata.
15 | type BootstrapPage struct {
16 | Configs []BootstrapConfig `json:"configs"`
17 | PageRes
18 | }
19 |
20 | type SubscriptionPage struct {
21 | Subscriptions []Subscription `json:"subscriptions"`
22 | PageRes
23 | }
24 |
25 | type MessagesPage struct {
26 | Messages []senml.Message `json:"messages,omitempty"`
27 | PageRes
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/sdk/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package sdk_test
5 |
6 | import (
7 | "os"
8 | "testing"
9 | )
10 |
11 | func TestMain(m *testing.M) {
12 | exitCode := m.Run()
13 | os.Exit(exitCode)
14 | }
15 |
--------------------------------------------------------------------------------
/provision/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/provision/api/endpoint.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "context"
8 |
9 | "github.com/absmach/magistrala/provision"
10 | apiutil "github.com/absmach/supermq/api/http/util"
11 | "github.com/absmach/supermq/pkg/errors"
12 | "github.com/go-kit/kit/endpoint"
13 | )
14 |
15 | func doProvision(svc provision.Service) endpoint.Endpoint {
16 | return func(ctx context.Context, request interface{}) (interface{}, error) {
17 | req := request.(provisionReq)
18 | if err := req.validate(); err != nil {
19 | return nil, errors.Wrap(apiutil.ErrValidation, err)
20 | }
21 |
22 | res, err := svc.Provision(ctx, req.domainID, req.token, req.Name, req.ExternalID, req.ExternalKey)
23 | if err != nil {
24 | return nil, err
25 | }
26 |
27 | provisionResponse := provisionRes{
28 | Clients: res.Clients,
29 | Channels: res.Channels,
30 | ClientCert: res.ClientCert,
31 | ClientKey: res.ClientKey,
32 | CACert: res.CACert,
33 | Whitelisted: res.Whitelisted,
34 | }
35 |
36 | return provisionResponse, nil
37 | }
38 | }
39 |
40 | func getMapping(svc provision.Service) endpoint.Endpoint {
41 | return func(ctx context.Context, request interface{}) (interface{}, error) {
42 | req := request.(mappingReq)
43 | if err := req.validate(); err != nil {
44 | return nil, errors.Wrap(apiutil.ErrValidation, err)
45 | }
46 |
47 | res, err := svc.Mapping(ctx, req.token)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | return mappingRes{Data: res}, nil
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/provision/api/logging.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !test
5 |
6 | package api
7 |
8 | import (
9 | "context"
10 | "log/slog"
11 | "time"
12 |
13 | "github.com/absmach/magistrala/provision"
14 | )
15 |
16 | var _ provision.Service = (*loggingMiddleware)(nil)
17 |
18 | type loggingMiddleware struct {
19 | logger *slog.Logger
20 | svc provision.Service
21 | }
22 |
23 | // NewLoggingMiddleware adds logging facilities to the core service.
24 | func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service {
25 | return &loggingMiddleware{logger, svc}
26 | }
27 |
28 | func (lm *loggingMiddleware) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res provision.Result, err error) {
29 | defer func(begin time.Time) {
30 | args := []any{
31 | slog.String("duration", time.Since(begin).String()),
32 | slog.String("name", name),
33 | slog.String("external_id", externalID),
34 | }
35 | if err != nil {
36 | args = append(args, slog.Any("error", err))
37 | lm.logger.Warn("Provision failed", args...)
38 | return
39 | }
40 | lm.logger.Info("Provision completed successfully", args...)
41 | }(time.Now())
42 |
43 | return lm.svc.Provision(ctx, domainID, token, name, externalID, externalKey)
44 | }
45 |
46 | func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID, duration string) (cert, key string, err error) {
47 | defer func(begin time.Time) {
48 | args := []any{
49 | slog.String("duration", time.Since(begin).String()),
50 | slog.String("client_id", clientID),
51 | slog.String("ttl", duration),
52 | }
53 | if err != nil {
54 | args = append(args, slog.Any("error", err))
55 | lm.logger.Warn("Client certificate failed to create successfully", args...)
56 | return
57 | }
58 | lm.logger.Info("Client certificate created successfully", args...)
59 | }(time.Now())
60 |
61 | return lm.svc.Cert(ctx, domainID, token, clientID, duration)
62 | }
63 |
64 | func (lm *loggingMiddleware) Mapping(ctx context.Context, token string) (res map[string]interface{}, err error) {
65 | defer func(begin time.Time) {
66 | args := []any{
67 | slog.String("duration", time.Since(begin).String()),
68 | }
69 | if err != nil {
70 | args = append(args, slog.Any("error", err))
71 | lm.logger.Warn("Mapping failed", args...)
72 | return
73 | }
74 | lm.logger.Info("Mapping completed successfully", args...)
75 | }(time.Now())
76 |
77 | return lm.svc.Mapping(ctx, token)
78 | }
79 |
--------------------------------------------------------------------------------
/provision/api/requests.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import apiutil "github.com/absmach/supermq/api/http/util"
7 |
8 | type provisionReq struct {
9 | token string
10 | domainID string
11 | Name string `json:"name"`
12 | ExternalID string `json:"external_id"`
13 | ExternalKey string `json:"external_key"`
14 | }
15 |
16 | func (req provisionReq) validate() error {
17 | if req.ExternalID == "" {
18 | return apiutil.ErrMissingID
19 | }
20 | if req.domainID == "" {
21 | return apiutil.ErrMissingDomainID
22 | }
23 |
24 | if req.ExternalKey == "" {
25 | return apiutil.ErrBearerKey
26 | }
27 |
28 | if req.Name == "" {
29 | return apiutil.ErrMissingName
30 | }
31 |
32 | return nil
33 | }
34 |
35 | type mappingReq struct {
36 | token string
37 | domainID string
38 | }
39 |
40 | func (req mappingReq) validate() error {
41 | if req.token == "" {
42 | return apiutil.ErrBearerToken
43 | }
44 | if req.domainID == "" {
45 | return apiutil.ErrMissingDomainID
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/provision/api/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "encoding/json"
8 | "net/http"
9 |
10 | "github.com/absmach/supermq"
11 | sdk "github.com/absmach/supermq/pkg/sdk"
12 | )
13 |
14 | var _ supermq.Response = (*provisionRes)(nil)
15 |
16 | type provisionRes struct {
17 | Clients []sdk.Client `json:"clients"`
18 | Channels []sdk.Channel `json:"channels"`
19 | ClientCert map[string]string `json:"client_cert,omitempty"`
20 | ClientKey map[string]string `json:"client_key,omitempty"`
21 | CACert string `json:"ca_cert,omitempty"`
22 | Whitelisted map[string]bool `json:"whitelisted,omitempty"`
23 | }
24 |
25 | func (res provisionRes) Code() int {
26 | return http.StatusCreated
27 | }
28 |
29 | func (res provisionRes) Headers() map[string]string {
30 | return map[string]string{}
31 | }
32 |
33 | func (res provisionRes) Empty() bool {
34 | return false
35 | }
36 |
37 | type mappingRes struct {
38 | Data interface{}
39 | }
40 |
41 | func (res mappingRes) Code() int {
42 | return http.StatusOK
43 | }
44 |
45 | func (res mappingRes) Headers() map[string]string {
46 | return map[string]string{}
47 | }
48 |
49 | func (res mappingRes) Empty() bool {
50 | return false
51 | }
52 |
53 | func (res mappingRes) MarshalJSON() ([]byte, error) {
54 | return json.Marshal(res.Data)
55 | }
56 |
--------------------------------------------------------------------------------
/provision/api/transport.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package api
5 |
6 | import (
7 | "context"
8 | "encoding/json"
9 | "log/slog"
10 | "net/http"
11 |
12 | "github.com/absmach/magistrala/provision"
13 | "github.com/absmach/supermq"
14 | api "github.com/absmach/supermq/api/http"
15 | apiutil "github.com/absmach/supermq/api/http/util"
16 | "github.com/absmach/supermq/pkg/errors"
17 | "github.com/go-chi/chi/v5"
18 | kithttp "github.com/go-kit/kit/transport/http"
19 | "github.com/prometheus/client_golang/prometheus/promhttp"
20 | )
21 |
22 | const (
23 | contentType = "application/json"
24 | )
25 |
26 | // MakeHandler returns a HTTP handler for API endpoints.
27 | func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler {
28 | opts := []kithttp.ServerOption{
29 | kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
30 | }
31 |
32 | r := chi.NewRouter()
33 |
34 | r.Route("/{domainID}", func(r chi.Router) {
35 | r.Route("/mapping", func(r chi.Router) {
36 | r.Post("/", kithttp.NewServer(
37 | doProvision(svc),
38 | decodeProvisionRequest,
39 | api.EncodeResponse,
40 | opts...,
41 | ).ServeHTTP)
42 | r.Get("/", kithttp.NewServer(
43 | getMapping(svc),
44 | decodeMappingRequest,
45 | api.EncodeResponse,
46 | opts...,
47 | ).ServeHTTP)
48 | })
49 | })
50 | r.Handle("/metrics", promhttp.Handler())
51 | r.Get("/health", supermq.Health("provision", instanceID))
52 |
53 | return r
54 | }
55 |
56 | func decodeProvisionRequest(_ context.Context, r *http.Request) (interface{}, error) {
57 | if r.Header.Get("Content-Type") != contentType {
58 | return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
59 | }
60 |
61 | req := provisionReq{
62 | token: apiutil.ExtractBearerToken(r),
63 | domainID: chi.URLParam(r, "domainID"),
64 | }
65 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
66 | return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
67 | }
68 |
69 | return req, nil
70 | }
71 |
72 | func decodeMappingRequest(_ context.Context, r *http.Request) (interface{}, error) {
73 | if r.Header.Get("Content-Type") != contentType {
74 | return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
75 | }
76 |
77 | req := mappingReq{
78 | token: apiutil.ExtractBearerToken(r),
79 | domainID: chi.URLParam(r, "domainID"),
80 | }
81 |
82 | return req, nil
83 | }
84 |
--------------------------------------------------------------------------------
/provision/configs/config.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | file = "config.toml"
5 |
6 | [bootstrap]
7 | autowhite_list = true
8 | content = ""
9 | provision = true
10 | x509_provision = false
11 |
12 |
13 | [server]
14 | LogLevel = "info"
15 | ca_certs = ""
16 | http_port = "8190"
17 | mg_api_key = ""
18 | mg_bs_url = "http://localhost:9013"
19 | mg_certs_url = "http://localhost:9019"
20 | mg_pass = ""
21 | mg_user = ""
22 | mqtt_url = ""
23 | port = ""
24 | server_cert = ""
25 | server_key = ""
26 | clients_location = "http://localhost:9006"
27 | tls = true
28 | users_location = ""
29 |
30 | [[clients]]
31 | name = "client"
32 |
33 | [client.metadata]
34 | external_id = "xxxxxx"
35 |
36 |
37 | [[channels]]
38 | name = "control-channel"
39 |
40 | [channels.metadata]
41 | type = "control"
42 |
43 | [[channels]]
44 | name = "data-channel"
45 |
46 | [channels.metadata]
47 | type = "data"
48 |
--------------------------------------------------------------------------------
/provision/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package provision contains domain concept definitions needed to support
5 | // Provision service feature, i.e. automate provision process.
6 | package provision
7 |
--------------------------------------------------------------------------------
/re/aes.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package re
5 |
6 | import (
7 | "crypto/aes"
8 | "crypto/cipher"
9 | "fmt"
10 | )
11 |
12 | // encrypt implements AES CBC-128 ENCRYPTION which requires 3 data fields
13 | // 1. Key (16 bytes)
14 | // 2. Initialization Vector (IV) (16 bytes)
15 | // 3. Encrypted Data (16 bytes or length multiple a of 16)
16 | // The encrypted data is divided into blocks of 16 bytes (128 bits) which then operated on with the IV and Key.
17 | func encrypt(key []byte, iv []byte, data []byte) ([]byte, error) {
18 | block, err := aes.NewCipher(key)
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | blockSize := block.BlockSize()
24 | if len(data)%blockSize != 0 {
25 | return nil, fmt.Errorf("payload length %d is not a multiple of AES block size %d", len(data), blockSize)
26 | }
27 |
28 | if len(iv) != blockSize {
29 | return nil, fmt.Errorf("size of the IV %d is not the same as block size %d", len(iv), blockSize)
30 | }
31 |
32 | mode := cipher.NewCBCEncrypter(block, iv)
33 | encrypted := make([]byte, len(data))
34 | mode.CryptBlocks(encrypted, data)
35 |
36 | return encrypted, nil
37 | }
38 |
39 | // decrypt implements AES CBC-128 DECRYPTION which requires 3 data fields
40 | // 1. Key (16 bytes)
41 | // 2. Initialization Vector (IV) (16 bytes)
42 | // 3. Encrypted Data (16 bytes or length multiple a of 16)
43 | // The encrypted data is divided into blocks of 16 bytes (128 bits) which then operated on with the IV and Key.
44 | func decrypt(key []byte, iv []byte, encrypted []byte) ([]byte, error) {
45 | block, err := aes.NewCipher(key)
46 | if err != nil {
47 | return nil, err
48 | }
49 |
50 | blockSize := block.BlockSize()
51 | if len(encrypted)%blockSize != 0 {
52 | return nil, fmt.Errorf("encrypted payload length %d is not a multiple of AES block size %d", len(encrypted), blockSize)
53 | }
54 |
55 | if len(iv) != blockSize {
56 | return nil, fmt.Errorf("size of the IV %d is not the same as block size %d", len(iv), blockSize)
57 | }
58 |
59 | mode := cipher.NewCBCDecrypter(block, iv)
60 | decrypted := make([]byte, len(encrypted))
61 | mode.CryptBlocks(decrypted, encrypted)
62 |
63 | return decrypted, nil
64 | }
65 |
--------------------------------------------------------------------------------
/re/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/re/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package re contain the domain concept definitions needed to
5 | // support Magistrala Rule Egine services functionality.
6 | package re
7 |
--------------------------------------------------------------------------------
/re/emailer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package re
5 |
6 | type Emailer interface {
7 | // SendEmailNotification sends an email to the recipients based on a trigger.
8 | SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error
9 | }
10 |
--------------------------------------------------------------------------------
/re/emailer/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package emailer contains the domain concept definitions needed to support
5 | // Magistrala re email service functionality.
6 | package emailer
7 |
--------------------------------------------------------------------------------
/re/emailer/emailer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package emailer
5 |
6 | import (
7 | "github.com/absmach/magistrala/internal/email"
8 | "github.com/absmach/magistrala/re"
9 | )
10 |
11 | var _ re.Emailer = (*emailer)(nil)
12 |
13 | type emailer struct {
14 | agent *email.Agent
15 | }
16 |
17 | func New(a *email.Config) (re.Emailer, error) {
18 | e, err := email.New(a)
19 | return &emailer{agent: e}, err
20 | }
21 |
22 | func (e *emailer) SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error {
23 | return e.agent.Send(to, from, subject, header, user, content, footer, attachments)
24 | }
25 |
--------------------------------------------------------------------------------
/re/postgres/init.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import (
7 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
8 | migrate "github.com/rubenv/sql-migrate"
9 | )
10 |
11 | func Migration() *migrate.MemoryMigrationSource {
12 | return &migrate.MemoryMigrationSource{
13 | Migrations: []*migrate.Migration{
14 | {
15 | Id: "rules_01",
16 | // VARCHAR(36) for colums with IDs as UUIDS have a maximum of 36 characters
17 | // STATUS 0 to imply enabled and 1 to imply disabled
18 | Up: []string{
19 | `CREATE TABLE IF NOT EXISTS rules (
20 | id VARCHAR(36) PRIMARY KEY,
21 | name VARCHAR(1024),
22 | domain_id VARCHAR(36) NOT NULL,
23 | metadata JSONB,
24 | created_by VARCHAR(254),
25 | created_at TIMESTAMP,
26 | updated_at TIMESTAMP,
27 | updated_by VARCHAR(254),
28 | input_channel VARCHAR(36),
29 | input_topic TEXT,
30 | output_channel VARCHAR(36),
31 | output_topic TEXT,
32 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
33 | logic_type SMALLINT NOT NULL DEFAULT 0 CHECK (logic_type >= 0),
34 | logic_output SMALLINT[] NOT NULL DEFAULT '{}',
35 | logic_value BYTEA,
36 | time TIMESTAMP,
37 | recurring SMALLINT,
38 | recurring_period SMALLINT,
39 | start_datetime TIMESTAMP
40 | )`,
41 | },
42 | Down: []string{
43 | `DROP TABLE IF EXISTS rules`,
44 | },
45 | },
46 | {
47 | Id: "rules_02",
48 | Up: []string{
49 | `CREATE TABLE IF NOT EXISTS report_config (
50 | id VARCHAR(36) PRIMARY KEY,
51 | name VARCHAR(1024),
52 | description TEXT,
53 | domain_id VARCHAR(36) NOT NULL,
54 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
55 | created_at TIMESTAMP,
56 | created_by VARCHAR(254),
57 | updated_at TIMESTAMP,
58 | updated_by VARCHAR(254),
59 | time TIMESTAMP,
60 | recurring SMALLINT,
61 | recurring_period SMALLINT,
62 | start_datetime TIMESTAMP,
63 | config JSONB,
64 | email JSONB,
65 | metrics JSONB
66 | );`,
67 | },
68 | Down: []string{
69 | `DROP TABLE IF EXISTS report_config;`,
70 | },
71 | },
72 | },
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/re/status.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package re
5 |
6 | import (
7 | "encoding/json"
8 | "strings"
9 |
10 | svcerr "github.com/absmach/supermq/pkg/errors/service"
11 | )
12 |
13 | // Status represents Rule status.
14 | type Status uint8
15 |
16 | // Possible User status values.
17 | const (
18 | // EnabledStatus represents enabled Rule.
19 | EnabledStatus Status = iota
20 | // DisabledStatus represents disabled Rule.
21 | DisabledStatus
22 | // DeletedStatus represents a rule that will be deleted.
23 | DeletedStatus
24 |
25 | // AllStatus is used for querying purposes to list rules irrespective
26 | // of their status - both enabled and disabled. It is never stored in the
27 | // database as the actual User status and should always be the largest
28 | // value in this enumeration.
29 | AllStatus
30 | )
31 |
32 | // String representation of the possible status values.
33 | const (
34 | Disabled = "disabled"
35 | Enabled = "enabled"
36 | Deleted = "deleted"
37 | All = "all"
38 | Unknown = "unknown"
39 | )
40 |
41 | func (s Status) String() string {
42 | switch s {
43 | case DisabledStatus:
44 | return Disabled
45 | case EnabledStatus:
46 | return Enabled
47 | case DeletedStatus:
48 | return Deleted
49 | case AllStatus:
50 | return All
51 | default:
52 | return Unknown
53 | }
54 | }
55 |
56 | // ToStatus converts string value to a valid status.
57 | func ToStatus(status string) (Status, error) {
58 | switch status {
59 | case "", Enabled:
60 | return EnabledStatus, nil
61 | case Disabled:
62 | return DisabledStatus, nil
63 | case Deleted:
64 | return DeletedStatus, nil
65 | case All:
66 | return AllStatus, nil
67 | }
68 | return Status(0), svcerr.ErrInvalidStatus
69 | }
70 |
71 | func (s Status) MarshalJSON() ([]byte, error) {
72 | return json.Marshal(s.String())
73 | }
74 |
75 | func (s *Status) UnmarshalJSON(data []byte) error {
76 | str := strings.Trim(string(data), "\"")
77 | val, err := ToStatus(str)
78 | *s = val
79 | return err
80 | }
81 |
--------------------------------------------------------------------------------
/re/ticker.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package re
5 |
6 | import "time"
7 |
8 | type Ticker interface {
9 | Tick() <-chan time.Time
10 | Stop()
11 | }
12 |
13 | type timeTicker struct {
14 | *time.Ticker
15 | }
16 |
17 | func NewTicker(d time.Duration) Ticker {
18 | return &timeTicker{time.NewTicker(d)}
19 | }
20 |
21 | func (t *timeTicker) Tick() <-chan time.Time {
22 | return t.C
23 | }
24 |
--------------------------------------------------------------------------------
/readers/README.md:
--------------------------------------------------------------------------------
1 | # Readers
2 |
3 | Readers provide implementations of various `message readers`. Message readers are services that consume normalized (in `SenML` format) SuperMQ messages from data storage and expose HTTP API for message consumption.
4 |
5 | For an in-depth explanation of the usage of `reader`, as well as thorough understanding of SuperMQ, please check out the [official documentation][doc].
6 |
7 | [doc]: https://docs.supermq.abstractmachines.fr
8 |
--------------------------------------------------------------------------------
/readers/api/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package api contains API-related concerns: endpoint definitions, middlewares
5 | // and all resource representations.
6 | package api
7 |
--------------------------------------------------------------------------------
/readers/api/grpc/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package grpc contains implementation of Readers service gRPC API.
5 | package grpc
6 |
--------------------------------------------------------------------------------
/readers/api/grpc/endpoint.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package grpc
5 |
6 | import (
7 | "context"
8 |
9 | readers "github.com/absmach/supermq/readers"
10 | "github.com/go-kit/kit/endpoint"
11 | )
12 |
13 | func readMessagesEndpoint(svc readers.MessageRepository) endpoint.Endpoint {
14 | return func(ctx context.Context, request interface{}) (interface{}, error) {
15 | req := request.(readMessagesReq)
16 | if err := req.validate(); err != nil {
17 | return readMessagesRes{}, err
18 | }
19 |
20 | page, err := svc.ReadAll(req.chanID, req.pageMeta)
21 | if err != nil {
22 | return readMessagesRes{}, err
23 | }
24 |
25 | return readMessagesRes{
26 | PageMetadata: page.PageMetadata,
27 | Total: page.Total,
28 | Messages: page.Messages,
29 | }, nil
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/readers/api/grpc/request.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package grpc
5 |
6 | import (
7 | "slices"
8 | "strings"
9 | "time"
10 |
11 | apiutil "github.com/absmach/supermq/api/http/util"
12 | "github.com/absmach/supermq/readers"
13 | )
14 |
15 | const maxLimitSize = 1000
16 |
17 | var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"}
18 |
19 | type readMessagesReq struct {
20 | chanID string
21 | domain string
22 | pageMeta readers.PageMetadata
23 | }
24 |
25 | func (req readMessagesReq) validate() error {
26 | if req.chanID == "" {
27 | return apiutil.ErrMissingID
28 | }
29 | if req.domain == "" {
30 | return apiutil.ErrMissingID
31 | }
32 |
33 | if req.pageMeta.Limit < 1 || req.pageMeta.Limit > maxLimitSize {
34 | return apiutil.ErrLimitSize
35 | }
36 |
37 | if req.pageMeta.Comparator != "" &&
38 | req.pageMeta.Comparator != readers.EqualKey &&
39 | req.pageMeta.Comparator != readers.LowerThanKey &&
40 | req.pageMeta.Comparator != readers.LowerThanEqualKey &&
41 | req.pageMeta.Comparator != readers.GreaterThanKey &&
42 | req.pageMeta.Comparator != readers.GreaterThanEqualKey {
43 | return apiutil.ErrInvalidComparator
44 | }
45 |
46 | if req.pageMeta.Aggregation == "AGGREGATION_UNSPECIFIED" {
47 | req.pageMeta.Aggregation = ""
48 | }
49 |
50 | if agg := strings.ToUpper(req.pageMeta.Aggregation); agg != "" && agg != "AGGREGATION_UNSPECIFIED" {
51 | if req.pageMeta.From == 0 {
52 | return apiutil.ErrMissingFrom
53 | }
54 |
55 | if req.pageMeta.To == 0 {
56 | return apiutil.ErrMissingTo
57 | }
58 |
59 | if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) {
60 | return apiutil.ErrInvalidAggregation
61 | }
62 |
63 | if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil {
64 | return apiutil.ErrInvalidInterval
65 | }
66 | }
67 |
68 | return nil
69 | }
70 |
--------------------------------------------------------------------------------
/readers/api/grpc/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package grpc
5 |
6 | import (
7 | "github.com/absmach/supermq/readers"
8 | )
9 |
10 | type readMessagesRes struct {
11 | Total uint64
12 | Messages []readers.Message
13 | readers.PageMetadata
14 | }
15 |
16 | type Message interface{}
17 |
--------------------------------------------------------------------------------
/readers/api/grpc/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package grpc_test
5 |
6 | import (
7 | "os"
8 | "testing"
9 |
10 | "github.com/absmach/supermq/readers/mocks"
11 | )
12 |
13 | var svc *mocks.MessageRepository
14 |
15 | func TestMain(m *testing.M) {
16 | svc = new(mocks.MessageRepository)
17 | server := startGRPCServer(svc, port)
18 |
19 | code := m.Run()
20 |
21 | server.GracefulStop()
22 |
23 | os.Exit(code)
24 | }
25 |
--------------------------------------------------------------------------------
/readers/api/http/endpoint.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package http
5 |
6 | import (
7 | "context"
8 |
9 | grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1"
10 | grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1"
11 | apiutil "github.com/absmach/supermq/api/http/util"
12 | smqauthn "github.com/absmach/supermq/pkg/authn"
13 | "github.com/absmach/supermq/pkg/errors"
14 | svcerr "github.com/absmach/supermq/pkg/errors/service"
15 | "github.com/absmach/supermq/readers"
16 | "github.com/go-kit/kit/endpoint"
17 | )
18 |
19 | func listMessagesEndpoint(svc readers.MessageRepository, authn smqauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) endpoint.Endpoint {
20 | return func(ctx context.Context, request interface{}) (interface{}, error) {
21 | req := request.(listMessagesReq)
22 | if err := req.validate(); err != nil {
23 | return nil, errors.Wrap(apiutil.ErrValidation, err)
24 | }
25 |
26 | if err := authnAuthz(ctx, req, authn, clients, channels); err != nil {
27 | return nil, errors.Wrap(svcerr.ErrAuthorization, err)
28 | }
29 |
30 | page, err := svc.ReadAll(req.chanID, req.pageMeta)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return pageRes{
36 | PageMetadata: page.PageMetadata,
37 | Total: page.Total,
38 | Messages: page.Messages,
39 | }, nil
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/readers/api/http/requests.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package http
5 |
6 | import (
7 | "slices"
8 | "strings"
9 | "time"
10 |
11 | apiutil "github.com/absmach/supermq/api/http/util"
12 | "github.com/absmach/supermq/readers"
13 | )
14 |
15 | const maxLimitSize = 1000
16 |
17 | var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"}
18 |
19 | type listMessagesReq struct {
20 | chanID string
21 | token string
22 | domain string
23 | key string
24 | pageMeta readers.PageMetadata
25 | }
26 |
27 | func (req listMessagesReq) validate() error {
28 | if req.token == "" && req.key == "" {
29 | return apiutil.ErrBearerToken
30 | }
31 |
32 | if req.chanID == "" {
33 | return apiutil.ErrMissingID
34 | }
35 |
36 | if req.pageMeta.Limit < 1 || req.pageMeta.Limit > maxLimitSize {
37 | return apiutil.ErrLimitSize
38 | }
39 |
40 | if req.pageMeta.Comparator != "" &&
41 | req.pageMeta.Comparator != readers.EqualKey &&
42 | req.pageMeta.Comparator != readers.LowerThanKey &&
43 | req.pageMeta.Comparator != readers.LowerThanEqualKey &&
44 | req.pageMeta.Comparator != readers.GreaterThanKey &&
45 | req.pageMeta.Comparator != readers.GreaterThanEqualKey {
46 | return apiutil.ErrInvalidComparator
47 | }
48 |
49 | if req.pageMeta.Aggregation != "" {
50 | if req.pageMeta.From == 0 {
51 | return apiutil.ErrMissingFrom
52 | }
53 |
54 | if req.pageMeta.To == 0 {
55 | return apiutil.ErrMissingTo
56 | }
57 |
58 | if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) {
59 | return apiutil.ErrInvalidAggregation
60 | }
61 |
62 | if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil {
63 | return apiutil.ErrInvalidInterval
64 | }
65 | }
66 |
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/readers/api/http/responses.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package http
5 |
6 | import (
7 | "net/http"
8 |
9 | "github.com/absmach/supermq"
10 | "github.com/absmach/supermq/readers"
11 | )
12 |
13 | var _ supermq.Response = (*pageRes)(nil)
14 |
15 | type pageRes struct {
16 | readers.PageMetadata
17 | Total uint64 `json:"total"`
18 | Messages []readers.Message `json:"messages"`
19 | }
20 |
21 | func (res pageRes) Headers() map[string]string {
22 | return map[string]string{}
23 | }
24 |
25 | func (res pageRes) Code() int {
26 | return http.StatusOK
27 | }
28 |
29 | func (res pageRes) Empty() bool {
30 | return false
31 | }
32 |
--------------------------------------------------------------------------------
/readers/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package readers provides a set of readers for various formats.
5 | package readers
6 |
--------------------------------------------------------------------------------
/readers/middleware/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package middleware provides middleware for Magistrala Readers service.
5 | package middleware
6 |
--------------------------------------------------------------------------------
/readers/middleware/logging.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !test
5 |
6 | package middleware
7 |
8 | import (
9 | "log/slog"
10 | "time"
11 |
12 | "github.com/absmach/supermq/readers"
13 | )
14 |
15 | var _ readers.MessageRepository = (*loggingMiddleware)(nil)
16 |
17 | type loggingMiddleware struct {
18 | logger *slog.Logger
19 | svc readers.MessageRepository
20 | }
21 |
22 | // LoggingMiddleware adds logging facilities to the core service.
23 | func LoggingMiddleware(svc readers.MessageRepository, logger *slog.Logger) readers.MessageRepository {
24 | return &loggingMiddleware{
25 | logger: logger,
26 | svc: svc,
27 | }
28 | }
29 |
30 | func (lm *loggingMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (page readers.MessagesPage, err error) {
31 | defer func(begin time.Time) {
32 | args := []any{
33 | slog.String("duration", time.Since(begin).String()),
34 | slog.String("channel_id", chanID),
35 | slog.Group("page",
36 | slog.Uint64("offset", rpm.Offset),
37 | slog.Uint64("limit", rpm.Limit),
38 | slog.Uint64("total", page.Total),
39 | ),
40 | }
41 | if rpm.Subtopic != "" {
42 | args = append(args, slog.String("subtopic", rpm.Subtopic))
43 | }
44 | if rpm.Publisher != "" {
45 | args = append(args, slog.String("publisher", rpm.Publisher))
46 | }
47 | if err != nil {
48 | args = append(args, slog.Any("error", err))
49 | lm.logger.Warn("Read all failed", args...)
50 | return
51 | }
52 | lm.logger.Info("Read all completed successfully", args...)
53 | }(time.Now())
54 |
55 | return lm.svc.ReadAll(chanID, rpm)
56 | }
57 |
--------------------------------------------------------------------------------
/readers/middleware/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | //go:build !test
5 |
6 | package middleware
7 |
8 | import (
9 | "time"
10 |
11 | "github.com/absmach/supermq/readers"
12 | "github.com/go-kit/kit/metrics"
13 | )
14 |
15 | var _ readers.MessageRepository = (*metricsMiddleware)(nil)
16 |
17 | type metricsMiddleware struct {
18 | counter metrics.Counter
19 | latency metrics.Histogram
20 | svc readers.MessageRepository
21 | }
22 |
23 | // MetricsMiddleware instruments core service by tracking request count and latency.
24 | func MetricsMiddleware(svc readers.MessageRepository, counter metrics.Counter, latency metrics.Histogram) readers.MessageRepository {
25 | return &metricsMiddleware{
26 | counter: counter,
27 | latency: latency,
28 | svc: svc,
29 | }
30 | }
31 |
32 | func (mm *metricsMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) {
33 | defer func(begin time.Time) {
34 | mm.counter.With("method", "read_all").Add(1)
35 | mm.latency.With("method", "read_all").Observe(time.Since(begin).Seconds())
36 | }(time.Now())
37 |
38 | return mm.svc.ReadAll(chanID, rpm)
39 | }
40 |
--------------------------------------------------------------------------------
/readers/postgres/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres contains repository implementations using Postgres as
5 | // the underlying database.
6 | package postgres
7 |
--------------------------------------------------------------------------------
/readers/postgres/init.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package postgres
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/jmoiron/sqlx"
10 | migrate "github.com/rubenv/sql-migrate"
11 | )
12 |
13 | // Table for SenML messages.
14 | const defTable = "messages"
15 |
16 | // Config defines the options that are used when connecting to a PostgreSQL instance.
17 | type Config struct {
18 | Host string
19 | Port string
20 | User string
21 | Pass string
22 | Name string
23 | SSLMode string
24 | SSLCert string
25 | SSLKey string
26 | SSLRootCert string
27 | }
28 |
29 | // Connect creates a connection to the PostgreSQL instance and applies any
30 | // unapplied database migrations. A non-nil error is returned to indicate
31 | // failure.
32 | func Connect(cfg Config) (*sqlx.DB, error) {
33 | url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
34 |
35 | db, err := sqlx.Open("pgx", url)
36 | if err != nil {
37 | return nil, err
38 | }
39 |
40 | if err := migrateDB(db); err != nil {
41 | return nil, err
42 | }
43 |
44 | return db, nil
45 | }
46 |
47 | func migrateDB(db *sqlx.DB) error {
48 | migrations := &migrate.MemoryMigrationSource{
49 | Migrations: []*migrate.Migration{
50 | {
51 | Id: "messages_1",
52 | Up: []string{
53 | `CREATE TABLE IF NOT EXISTS messages (
54 | id UUID,
55 | channel UUID,
56 | subtopic VARCHAR(254),
57 | publisher UUID,
58 | protocol TEXT,
59 | name TEXT,
60 | unit TEXT,
61 | value FLOAT,
62 | string_value TEXT,
63 | bool_value BOOL,
64 | data_value TEXT,
65 | sum FLOAT,
66 | time FlOAT,
67 | update_time FLOAT,
68 | PRIMARY KEY (id)
69 | )`,
70 | },
71 | Down: []string{
72 | "DROP TABLE messages",
73 | },
74 | },
75 | },
76 | }
77 |
78 | _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
79 | return err
80 | }
81 |
--------------------------------------------------------------------------------
/readers/postgres/setup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package postgres_test contains tests for PostgreSQL repository
5 | // implementations.
6 | package postgres_test
7 |
8 | import (
9 | "fmt"
10 | "log"
11 | "os"
12 | "testing"
13 |
14 | "github.com/absmach/magistrala/readers/postgres"
15 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
16 | "github.com/jmoiron/sqlx"
17 | "github.com/ory/dockertest/v3"
18 | "github.com/ory/dockertest/v3/docker"
19 | )
20 |
21 | var db *sqlx.DB
22 |
23 | func TestMain(m *testing.M) {
24 | pool, err := dockertest.NewPool("")
25 | if err != nil {
26 | log.Fatalf("Could not connect to docker: %s", err)
27 | }
28 | container, err := pool.RunWithOptions(&dockertest.RunOptions{
29 | Repository: "postgres",
30 | Tag: "16.2-alpine",
31 | Env: []string{
32 | "POSTGRES_USER=test",
33 | "POSTGRES_PASSWORD=test",
34 | "POSTGRES_DB=test",
35 | "listen_addresses = '*'",
36 | },
37 | }, func(config *docker.HostConfig) {
38 | config.AutoRemove = true
39 | config.RestartPolicy = docker.RestartPolicy{Name: "no"}
40 | })
41 | if err != nil {
42 | log.Fatalf("Could not start container: %s", err)
43 | }
44 |
45 | port := container.GetPort("5432/tcp")
46 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
47 |
48 | if err = pool.Retry(func() error {
49 | db, err = sqlx.Open("pgx", url)
50 | if err != nil {
51 | return err
52 | }
53 | return db.Ping()
54 | }); err != nil {
55 | log.Fatalf("Could not connect to docker: %s", err)
56 | }
57 |
58 | dbConfig := postgres.Config{
59 | Host: "localhost",
60 | Port: port,
61 | User: "test",
62 | Pass: "test",
63 | Name: "test",
64 | SSLMode: "disable",
65 | SSLCert: "",
66 | SSLKey: "",
67 | SSLRootCert: "",
68 | }
69 |
70 | if db, err = postgres.Connect(dbConfig); err != nil {
71 | log.Fatalf("Could not setup test DB connection: %s", err)
72 | }
73 |
74 | code := m.Run()
75 |
76 | // Defers will not be run when using os.Exit
77 | db.Close()
78 | if err = pool.Purge(container); err != nil {
79 | log.Fatalf("Could not purge container: %s", err)
80 | }
81 |
82 | os.Exit(code)
83 | }
84 |
--------------------------------------------------------------------------------
/readers/timescale/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package timescale contains repository implementations using Timescale as
5 | // the underlying database.
6 | package timescale
7 |
--------------------------------------------------------------------------------
/scripts/csv/channels.csv:
--------------------------------------------------------------------------------
1 | channel_1
2 | channel_2
3 | channel_3
4 |
--------------------------------------------------------------------------------
/scripts/csv/clients.csv:
--------------------------------------------------------------------------------
1 | client_1
2 | client_2
3 | client_3
4 | client_4
5 | client_5
6 | client_6
7 | client_7
8 | client_8
9 | client_9
10 | client_10
11 |
--------------------------------------------------------------------------------
/scripts/gen-ts-data/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | *.csv
5 |
--------------------------------------------------------------------------------
/scripts/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | ###
6 | # Runs all SuperMQ microservices (must be previously built and installed).
7 | #
8 | # Expects that PostgreSQL and needed messaging DB are alredy running.
9 | # Additionally, MQTT microservice demands that Redis is up and running.
10 | #
11 | ###
12 |
13 | BUILD_DIR=../build
14 |
15 | # Kill all supermq-* stuff
16 | function cleanup {
17 | pkill supermq
18 | pkill nats
19 | }
20 |
21 | ###
22 | # NATS
23 | ###
24 | nats-server &
25 | counter=1
26 | until fuser 4222/tcp 1>/dev/null 2>&1;
27 | do
28 | sleep 0.5
29 | ((counter++))
30 | if [ ${counter} -gt 10 ]
31 | then
32 | echo "NATS failed to start in 5 sec, exiting"
33 | exit 1
34 | fi
35 | echo "Waiting for NATS server"
36 | done
37 |
38 | ###
39 | # Users
40 | ###
41 | SMQ_USERS_LOG_LEVEL=info SMQ_USERS_HTTP_PORT=9002 SMQ_USERS_GRPC_PORT=7001 SMQ_USERS_ADMIN_EMAIL=admin@supermq.com SMQ_USERS_ADMIN_PASSWORD=12345678 SMQ_USERS_ADMIN_USERNAME=admin SMQ_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/supermq-users &
42 |
43 | ###
44 | # Clients
45 | ###
46 | SMQ_CLIENTS_LOG_LEVEL=info SMQ_CLIENTS_HTTP_PORT=9000 SMQ_CLIENTS_GRPC_PORT=7000 SMQ_CLIENTS_HTTP_PORT=9002 $BUILD_DIR/supermq-clients &
47 |
48 | ###
49 | # HTTP
50 | ###
51 | SMQ_HTTP_ADAPTER_LOG_LEVEL=info SMQ_HTTP_ADAPTER_PORT=8008 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-http &
52 |
53 | ###
54 | # WS
55 | ###
56 | SMQ_WS_ADAPTER_LOG_LEVEL=info SMQ_WS_ADAPTER_HTTP_PORT=8190 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-ws &
57 |
58 | ###
59 | # MQTT
60 | ###
61 | SMQ_MQTT_ADAPTER_LOG_LEVEL=info SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-mqtt &
62 |
63 | ###
64 | # CoAP
65 | ###
66 | SMQ_COAP_ADAPTER_LOG_LEVEL=info SMQ_COAP_ADAPTER_PORT=5683 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-coap &
67 |
68 | trap cleanup EXIT
69 |
70 | while : ; do sleep 1 ; done
71 |
--------------------------------------------------------------------------------
/scripts/supermq.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | ###
6 | # Fetches the latest version of the docker files from the SuperMQ repository.
7 | ###
8 |
9 | set -e
10 | set -o pipefail
11 |
12 | REPO_URL=https://github.com/absmach/supermq
13 | TEMP_DIR="supermq"
14 | DOCKER_DIR="docker"
15 | DOCKER_DST_DIR="../docker"
16 | DEST_DIR="../../docker/supermq-docker"
17 |
18 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19 | cd "$SCRIPT_DIR" || exit 1
20 |
21 | if [ -n "$(git status --porcelain "$DOCKER_DST_DIR")" ]; then
22 | echo "There are uncommitted changes in '$DOCKER_DIR' dir. Please commit or stash them before running this script."
23 | exit 1
24 | fi
25 |
26 | cleanup() {
27 | rm -rf "$TEMP_DIR"
28 | }
29 | cleanup
30 | trap cleanup EXIT
31 |
32 | git clone --depth 1 --filter=blob:none --sparse "$REPO_URL"
33 | cd "$TEMP_DIR"
34 | git sparse-checkout set "$DOCKER_DIR"
35 |
36 | if [ -d "$DEST_DIR" ]; then
37 | rm -r "$DEST_DIR"
38 | fi
39 | mkdir -p "$DEST_DIR"
40 | mv -f "$DOCKER_DIR"/.??* "$DOCKER_DIR"/* "$DEST_DIR"/
41 | cd ..
42 | rm -rf "$TEMP_DIR"
43 |
--------------------------------------------------------------------------------
/tools/config/.codecov.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # CoAP is temporarily ignored since we don't have tests for it yet.
5 | coverage:
6 | ignore:
7 | - "tools/*"
8 | - "coap/*"
9 | - "**/mocks*"
10 | - "*/middleware/*"
11 |
--------------------------------------------------------------------------------
/tools/config/.mockery.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 | pkgname: mocks
4 | template: testify
5 | mockname: "{{.InterfaceName}}"
6 | filename: "{{snakecase .InterfaceName}}.go"
7 | dir: "{{.InterfaceDirRelative}}/mocks"
8 | force-file-write: true
9 | template-data:
10 | boilerplate-file: ./tools/config/boilerplate.txt
11 | with-expecter: true
12 | packages:
13 | github.com/absmach/magistrala/pkg/sdk:
14 | interfaces:
15 | SDK:
16 | config:
17 | mockname: "SDK"
18 | filename: "sdk.go"
19 | github.com/absmach/magistrala/re:
20 | interfaces:
21 | Repository:
22 | Service:
23 | Ticker:
24 | Emailer:
25 | github.com/absmach/magistrala/bootstrap:
26 | interfaces:
27 | ConfigRepository:
28 | ConfigReader:
29 | Service:
30 | github.com/absmach/magistrala/consumers/notifiers:
31 | interfaces:
32 | Service:
33 | SubscriptionsRepository:
34 | github.com/absmach/magistrala/provision:
35 | interfaces:
36 | Service:
37 | github.com/absmach/magistrala/alarms:
38 | interfaces:
39 | Service:
40 | Repository:
41 | github.com/absmach/magistrala/api/grpc/readers/v1:
42 | interfaces:
43 | ReadersServiceClient:
44 | config:
45 | dir: "./readers/mocks"
46 | mockname: "ReadersServiceClient"
47 | filename: "readers_client.go"
48 |
--------------------------------------------------------------------------------
/tools/config/boilerplate.txt:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 |
3 | // SPDX-License-Identifier: Apache-2.0
4 |
--------------------------------------------------------------------------------
/tools/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package tools contains tools for SuperMQ.
5 | package tools
6 |
--------------------------------------------------------------------------------
/tools/e2e/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | PROGRAM = e2e
5 | SOURCES = $(wildcard *.go) cmd/main.go
6 |
7 | all: $(PROGRAM)
8 |
9 | .PHONY: all clean
10 |
11 | $(PROGRAM): $(SOURCES)
12 | go build -ldflags "-s -w" -o $@ cmd/main.go
13 |
14 | clean:
15 | rm -rf $(PROGRAM)
16 |
--------------------------------------------------------------------------------
/tools/e2e/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package main contains e2e tool for testing SuperMQ.
5 | package main
6 |
7 | import (
8 | "log"
9 |
10 | "github.com/absmach/magistrala/tools/e2e"
11 | cc "github.com/ivanpirog/coloredcobra"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | const defNum = uint64(10)
16 |
17 | func main() {
18 | econf := e2e.Config{}
19 |
20 | rootCmd := &cobra.Command{
21 | Use: "e2e",
22 | Short: "e2e is end-to-end testing tool for SuperMQ",
23 | Long: "Tool for testing end-to-end flow of supermq by doing a couple of operations namely:\n" +
24 | "1. Creating, viewing, updating and changing status of users, groups, clients and channels.\n" +
25 | "2. Connecting users and groups to each other and clients and channels to each other.\n" +
26 | "3. Sending messages from clients to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT).\n" +
27 | "Complete documentation is available at https://docs.supermq.abstractmachines.fr",
28 | Example: "Here is a simple example of using e2e tool.\n" +
29 | "Use the following commands from the root supermq directory:\n\n" +
30 | "go run tools/e2e/cmd/main.go\n" +
31 | "go run tools/e2e/cmd/main.go --host 142.93.118.47\n" +
32 | "go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --prefix e2e",
33 | Run: func(cmd *cobra.Command, _ []string) {
34 | e2e.Test(cmd.Context(), econf)
35 | },
36 | }
37 |
38 | cc.Init(&cc.Config{
39 | RootCmd: rootCmd,
40 | Headings: cc.HiCyan + cc.Bold + cc.Underline,
41 | CmdShortDescr: cc.Magenta,
42 | Example: cc.Italic + cc.Magenta,
43 | ExecName: cc.Bold,
44 | Flags: cc.HiGreen + cc.Bold,
45 | FlagsDescr: cc.Green,
46 | FlagsDataType: cc.White + cc.Italic,
47 | })
48 |
49 | // Root Flags
50 | rootCmd.PersistentFlags().StringVarP(&econf.Host, "host", "H", "localhost", "address for a running supermq instance")
51 | rootCmd.PersistentFlags().StringVarP(&econf.Prefix, "prefix", "p", "", "name prefix for users, groups, clients and channels")
52 | rootCmd.PersistentFlags().Uint64VarP(&econf.Num, "num", "n", defNum, "number of users, groups, channels and clients to create and connect")
53 | rootCmd.PersistentFlags().Uint64VarP(&econf.NumOfMsg, "num_of_messages", "N", defNum, "number of messages to send")
54 |
55 | if err := rootCmd.Execute(); err != nil {
56 | log.Fatal(err)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tools/e2e/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package e2e contains entry point for end-to-end tests.
5 | package e2e
6 |
--------------------------------------------------------------------------------
/tools/mqtt-bench/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | PROGRAM = mqtt-bench
5 | SOURCES = $(wildcard *.go) cmd/main.go
6 |
7 | all: $(PROGRAM)
8 |
9 | .PHONY: all clean
10 |
11 | $(PROGRAM): $(SOURCES)
12 | go build -ldflags "-s -w" -o $@ cmd/main.go
13 |
14 | clean:
15 | rm -rf $(PROGRAM)
16 |
--------------------------------------------------------------------------------
/tools/mqtt-bench/config.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | package bench
5 |
6 | // Keep struct names exported, otherwise Viper unmarshalling won't work.
7 | type mqttBrokerConfig struct {
8 | URL string `toml:"url" mapstructure:"url"`
9 | }
10 |
11 | type mqttMessageConfig struct {
12 | Size int `toml:"size" mapstructure:"size"`
13 | Payload string `toml:"payload" mapstructure:"payload"`
14 | Format string `toml:"format" mapstructure:"format"`
15 | QoS int `toml:"qos" mapstructure:"qos"`
16 | Retain bool `toml:"retain" mapstructure:"retain"`
17 | }
18 |
19 | type mqttTLSConfig struct {
20 | MTLS bool `toml:"mtls" mapstructure:"mtls"`
21 | SkipTLSVer bool `toml:"skiptlsver" mapstructure:"skiptlsver"`
22 | CA string `toml:"ca" mapstructure:"ca"`
23 | }
24 |
25 | type mqttConfig struct {
26 | Broker mqttBrokerConfig `toml:"broker" mapstructure:"broker"`
27 | Message mqttMessageConfig `toml:"message" mapstructure:"message"`
28 | Timeout int `toml:"timeout" mapstructure:"timeout"`
29 | TLS mqttTLSConfig `toml:"tls" mapstructure:"tls"`
30 | }
31 |
32 | type testConfig struct {
33 | Count int `toml:"count" mapstructure:"count"`
34 | Pubs int `toml:"pubs" mapstructure:"pubs"`
35 | Subs int `toml:"subs" mapstructure:"subs"`
36 | }
37 |
38 | type logConfig struct {
39 | Quiet bool `toml:"quiet" mapstructure:"quiet"`
40 | }
41 |
42 | type smqFile struct {
43 | ConnFile string `toml:"connections_file" mapstructure:"connections_file"`
44 | }
45 |
46 | type client struct {
47 | ClientID string `toml:"client_id" mapstructure:"client_id"`
48 | ClientSecret string `toml:"client_secret" mapstructure:"client_secret"`
49 | MTLSCert string `toml:"mtls_cert" mapstructure:"mtls_cert"`
50 | MTLSKey string `toml:"mtls_key" mapstructure:"mtls_key"`
51 | }
52 |
53 | type channel struct {
54 | ChannelID string `toml:"channel_id" mapstructure:"channel_id"`
55 | }
56 |
57 | type superMQ struct {
58 | Clients []client `toml:"clients" mapstructure:"clients"`
59 | Channels []channel `toml:"channels" mapstructure:"channels"`
60 | }
61 |
62 | // Config struct holds benchmark configuration.
63 | type Config struct {
64 | MQTT mqttConfig `toml:"mqtt" mapstructure:"mqtt"`
65 | Test testConfig `toml:"test" mapstructure:"test"`
66 | Log logConfig `toml:"log" mapstructure:"log"`
67 | Smq smqFile `toml:"supermq" mapstructure:"supermq"`
68 | }
69 |
--------------------------------------------------------------------------------
/tools/mqtt-bench/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package bench contains benchmarking tool for MQTT broker.
5 | package bench
6 |
--------------------------------------------------------------------------------
/tools/mqtt-bench/scripts/mqtt-bench.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) Abstract Machines
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | i=0
6 | echo "BEGIN TEST " > result.$1.out
7 | for mtls in true
8 | do
9 | for ret in false true
10 | do
11 | for qos in 0 1 2
12 | do
13 | for pub in 1 10 100
14 | do
15 | for sub in 1 10
16 | do
17 | for message in 100 1000
18 | do
19 | if [[ $pub -eq 100 && $message -eq 1000 ]];
20 | then
21 | continue
22 | fi
23 |
24 | for size in 100 500
25 | do
26 | let "i += 1"
27 | echo "=================================TEST $i=========================================" >> $1-$i.out
28 | echo "MTLS: $mtls RETAIN: $ret, QOS $qos" >> $1-$i.out
29 | echo "Pub:" $pub ", Sub:" $sub ", MsgSize:" $size ", MsgPerPub:" $message >> $1-$i.out
30 | echo "=================================================================================" >> $1-$i.out
31 | if [ "$mtls" = true ];
32 | then
33 | echo "| " >> $1-$i.out
34 | echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true" >> $1-$i.out
35 | echo "| " >> $1-$i.out
36 | ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true >> $1-$i.out
37 | else
38 | echo "| " >> $1-$i.out
39 | echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true" >> $1-$i.out
40 | echo "| " >> $1-$i.out
41 | ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true >> $1-$i.out
42 | fi
43 | sleep 2
44 | done
45 | done
46 | done
47 | done
48 | done
49 |
50 | done
51 | done
52 | files=`ls test*.out | sort --version-sort `
53 | for file in $files
54 | do
55 | cat $file >> result.$1.out
56 | done
57 | echo "END TEST " >> result.$1.out
58 |
--------------------------------------------------------------------------------
/tools/mqtt-bench/templates/reference.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | [mqtt]
5 | timeout = 1000
6 | [mqtt.broker]
7 | url = "tcp://localhost:1883"
8 |
9 | [mqtt.message]
10 | size = 1000
11 | format = "text"
12 | qos = 2
13 | retain = true
14 | payload = "{\"bn\":\"some-base-name\",\"bt\":1.276020076001e+09, \"bu\":\"A\",\"bver\":5, \"n\":\"voltage\",\"u\":\"V\",\"v\":120.1}"
15 |
16 | [mqtt.tls]
17 | mtls = false
18 | skiptlsver = true
19 | ca = "ca.crt"
20 |
21 | [test]
22 | pubs = 2000
23 | count = 70
24 |
25 | [log]
26 | quiet = true
27 |
28 | [supermq]
29 | connections_file = "../provision/mgconn.toml"
30 |
--------------------------------------------------------------------------------
/tools/provision/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Abstract Machines
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | PROGRAM = provision
5 | SOURCES = $(wildcard *.go) cmd/main.go
6 |
7 | all: $(PROGRAM)
8 |
9 | .PHONY: all clean
10 |
11 | $(PROGRAM): $(SOURCES)
12 | go build -ldflags "-s -w" -o $@ cmd/main.go
13 |
14 | clean:
15 | rm -rf $(PROGRAM)
16 |
--------------------------------------------------------------------------------
/tools/provision/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package main contains entry point for provisioning tool.
5 | package main
6 |
7 | import (
8 | "log"
9 |
10 | "github.com/absmach/magistrala/tools/provision"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | func main() {
15 | pconf := provision.Config{}
16 |
17 | rootCmd := &cobra.Command{
18 | Use: "provision",
19 | Short: "provision is provisioning tool for SuperMQ",
20 | Long: `Tool for provisioning series of SuperMQ channels and clients and connecting them together.
21 | Complete documentation is available at https://docs.supermq.abstractmachines.fr`,
22 | Run: func(cmd *cobra.Command, _ []string) {
23 | if err := provision.Provision(cmd.Context(), pconf); err != nil {
24 | log.Fatal(err)
25 | }
26 | },
27 | }
28 |
29 | // Root Flags
30 | rootCmd.PersistentFlags().StringVarP(&pconf.Host, "host", "", "https://localhost", "address for supermq instance")
31 | rootCmd.PersistentFlags().StringVarP(&pconf.Prefix, "prefix", "", "", "name prefix for clients and channels")
32 | rootCmd.PersistentFlags().StringVarP(&pconf.Username, "username", "u", "", "supermq user")
33 | rootCmd.PersistentFlags().StringVarP(&pconf.Password, "password", "p", "", "supermq users password")
34 | rootCmd.PersistentFlags().IntVarP(&pconf.Num, "num", "", 10, "number of channels and clients to create and connect")
35 | rootCmd.PersistentFlags().BoolVarP(&pconf.SSL, "ssl", "", false, "create certificates for mTLS access")
36 | rootCmd.PersistentFlags().StringVarP(&pconf.CAKey, "cakey", "", "ca.key", "ca.key for creating and signing clients certificate")
37 | rootCmd.PersistentFlags().StringVarP(&pconf.CA, "ca", "", "ca.crt", "CA for creating and signing clients certificate")
38 |
39 | if err := rootCmd.Execute(); err != nil {
40 | log.Fatal(err)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tools/provision/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) Abstract Machines
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // Package provision is a simple utility to create
5 | // a list of channels and clients connected to these channels
6 | // with possibility to create certificates for mTLS use case.
7 | package provision
8 |
--------------------------------------------------------------------------------