feat(actions): Add actions and secrets (with proper asynchronous enqueue and dequeue mechanism) (#22)
All checks were successful
/ build (push) Successful in 3m37s
All checks were successful
/ build (push) Successful in 3m37s
Reviewed-on: #22 Co-authored-by: Varakh <varakh@varakh.de> Co-committed-by: Varakh <varakh@varakh.de>
This commit is contained in:
parent
1d4910d62b
commit
3532af74f2
44 changed files with 6665 additions and 1014 deletions
|
@ -3,8 +3,8 @@ on:
|
|||
tags:
|
||||
- '*'
|
||||
env:
|
||||
VERSION_MAJOR: 1
|
||||
VERSION_MINOR: 1
|
||||
VERSION_MAJOR: 2
|
||||
VERSION_MINOR: 0
|
||||
VERSION_PATCH: 0
|
||||
IMAGE_TAG: varakh/upda
|
||||
IMAGE_TAG_PRIVATE: git.myservermanager.com/varakh/upda
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -2,10 +2,16 @@
|
|||
|
||||
Changes adhere to [semantic versioning](https://semver.org).
|
||||
|
||||
## [1.1.0] - UNRELEASED
|
||||
## [2.0.0] - UNRELEASED
|
||||
|
||||
> This is a major version upgrade. Other versions are incompatible with this release.
|
||||
|
||||
* Added mandatory `SECRET` environment variable to encrypt some data inside the database
|
||||
* Switched to encrypting webhook tokens in database
|
||||
* Added _Actions_, a simple way to trigger notifications via [shoutrrr](https://containrrr.dev/shoutrrr) which supports secrets
|
||||
* Switched to producing events only for _Updates_
|
||||
* Adapted logging which defaults to JSON encoding
|
||||
* ...
|
||||
* Updated dependencies
|
||||
|
||||
## [1.0.3] - 2024/01/21
|
||||
|
||||
|
|
228
README.md
228
README.md
|
@ -23,6 +23,7 @@ Contributions are very welcome!
|
|||
* [Configuration](#configuration)
|
||||
* [3rd party integrations](#3rd-party-integrations)
|
||||
* [Webhooks](#webhooks)
|
||||
* [Actions](#actions)
|
||||
* [Prometheus Metrics](#prometheus-metrics)
|
||||
* [Deployment](#deployment)
|
||||
* [Native](#native)
|
||||
|
@ -59,21 +60,23 @@ via [alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/).
|
|||
|
||||
In addition, you can use _upda_'s UI to manage updates, e.g. _approve_ them when they have been rolled out to a host.
|
||||
|
||||
Important to note:
|
||||
|
||||
* _upda_ is **NOT a scraper** to watch docker registries or GitHub releases, it simply collects and consolidates updates
|
||||
from different sources via _webhooks_. If you like to watch GitHub releases, write a scraper and use `upda-cli` to
|
||||
report back to _upda_.
|
||||
* _upda_ uses basic auth for administrative tasks like viewing available updates or setting up the initial webhooks.
|
||||
> _upda_ is **NOT** a scraper to watch docker registries or GitHub releases, it simply tracks and consolidates updates
|
||||
> from different sources provided via _webhooks_. If you like to watch GitHub releases, write a scraper and
|
||||
> use `upda-cli` to report back to _upda_.
|
||||
|
||||
## Concepts
|
||||
|
||||
_upda_ retrieves new updates when webhooks of upda are invoked, e.g., [duin](https://crazymax.dev/diun/) invokes it
|
||||
or any other application which can reach the instance.
|
||||
Tracked updates are unique for the attributes `(application,provider,host)` which means that subsequent updates for an
|
||||
identical _application_, _provider_ and _host_ simply updates the `version` and `metadata` attributes for that tracked
|
||||
_update_ (regardless if the version or metadata payload _actually_ changed - reasoning behind this is to get reflected
|
||||
metadata updates independent if version attribute has changed).
|
||||
1. Create a webhook in upda.
|
||||
2. Use the webhook's URL in a 3rd party application to start tracking an update or use `upda-cli` to report an update.
|
||||
3. Enjoy visualization and state management of tracked updates in one place.
|
||||
4. Optionally, define [actions](#actions) for tracked updates as they arrive
|
||||
|
||||
_upda_ retrieves new updates when webhooks of upda are invoked, e.g., [duin](https://crazymax.dev/diun/) invokes it or
|
||||
any other application which can reach the instance. Tracked updates are unique for the
|
||||
attributes `(application,provider,host)` which means that subsequent updates for an identical _application_, _provider_
|
||||
and _host_ simply updates the `version` and `metadata` attributes for that tracked _update_ (regardless if the version
|
||||
or metadata payload _actually_ changed - reasoning behind this is to get reflected metadata updates independent if
|
||||
version attribute has changed).
|
||||
|
||||
State management of tracked updates:
|
||||
|
||||
|
@ -127,52 +130,68 @@ via web interface or API.
|
|||
|
||||
The following environment variables can be used to modify application behavior.
|
||||
|
||||
| Variable | Purpose | Default/Description |
|
||||
|:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `TZ` | The time zone (**recommended** to set it properly, background tasks depend on it) | Defaults to `Europe/Berlin`, can be any time zone according to _tz database_ |
|
||||
| `ADMIN_USER` | Admin user name for login | Not set by default, you need to explicitly set it to user name |
|
||||
| `ADMIN_PASSWORD` | Admin password for login | Not set by default, you need to explicitly set it to a secure random |
|
||||
| | | |
|
||||
| `DB_TYPE` | The database type (Postgres is **recommended**) | Defaults to `sqlite`, possible values are `sqlite` or `postgres` |
|
||||
| `DB_SQLITE_FILE` | Path to the SQLITE file | Defaults to `<XDG_DATA_DIR>/upda/upda.db`, e.g. `~/.local/share/upda/upda.db` |
|
||||
| `DB_POSTGRES_HOST` | The postgres host | Postgres host address, defaults to `localhost` |
|
||||
| `DB_POSTGRES_PORT` | The postgres port | Postgres port, defaults to `5432` |
|
||||
| `DB_POSTGRES_NAME` | The postgres database name | Postgres database name, needs to be set |
|
||||
| `DB_POSTGRES_TZ` | The postgres time zone | Postgres time zone settings, defaults to `Europe/Berlin` |
|
||||
| `DB_POSTGRES_USER` | The postgres user | Postgres user name, needs to be set |
|
||||
| `DB_POSTGRES_PASSWORD` | The postgres password | Postgres user password, needs to be set |
|
||||
| | | |
|
||||
| `SERVER_PORT` | Port | Defaults to `8080` |
|
||||
| `SERVER_LISTEN` | Server's listen address | Defaults to empty which equals `0.0.0.0` |
|
||||
| `SERVER_TLS_ENABLED` | If server uses TLS | Defaults `false` |
|
||||
| `SERVER_TLS_CERT_PATH` | When TLS enabled, provide the certificate path | |
|
||||
| `SERVER_TLS_KEY_PATH` | When TLS enabled, provide the key path | |
|
||||
| `SERVER_TIMEOUT` | Timeout the server waits before shutting down to end any pending tasks | Defaults to `1s` (1 second), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `CORS_ALLOW_ORIGIN` | CORS configuration | Defaults to `*` |
|
||||
| `CORS_ALLOW_METHODS` | CORS configuration | Defaults to `GET, POST, PUT, PATCH, DELETE, OPTIONS` |
|
||||
| `CORS_ALLOW_HEADERS` | CORS configuration | Defaults to `Authorization, Content-Type` |
|
||||
| | | |
|
||||
| `LOGGING_LEVEL` | Logging level. Possible are `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal`. Setting to `debug` enables high verbosity output. | Defaults to `info` |
|
||||
| `LOGGING_ENCODING` | Logging encoding. Possible are `console` and `json` | Defaults to `json` |
|
||||
| `LOGGING_DIRECTORY` | Logging directory. When set, logs will be added to a file called `upda.log` in addition to the standard output. Ensure that upda has access permissions. Use an external program for log rotation if desired. | |
|
||||
| | | |
|
||||
| `WEBHOOKS_TOKEN_LENGTH` | The length of the token | Defaults to `16`, positive number |
|
||||
| | | |
|
||||
| `TASK_UPDATE_CLEAN_STALE_ENABLED` | If background task should run to do housekeeping of stale (ignored/approved) updates from the database | Defaults to `false` |
|
||||
| `TASK_UPDATE_CLEAN_STALE_INTERVAL` | Interval at which a background task does housekeeping by deleting stale (ignored/approved) updates from the database | Defaults to `1h` (1 hour), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_UPDATE_CLEAN_STALE_MAX_AGE` | Number defining at which age stale (ignored/approved) updates are deleted by the background task (_updatedAt_ attribute decides) | Defaults to `168h` (168 hours = 1 week), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_EVENT_CLEAN_STALE_ENABLED` | If background task should run to do housekeeping of stale (old) events from the database | Defaults to `false` |
|
||||
| `TASK_EVENT_CLEAN_STALE_INTERVAL` | Interval at which a background task does housekeeping by deleting stale (old) events from the database | Defaults to `8h` (8 hours), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_EVENT_CLEAN_STALE_MAX_AGE` | Number defining at which age stale (old) events are deleted by the background task (_updatedAt_ attribute decides) | Defaults to `2190h` (2190 hours = 3 months), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_PROMETHEUS_REFRESH_INTERVAL` | Interval at which a background task updates custom metrics | Defaults to `60s` (60 seconds), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| | | |
|
||||
| `LOCK_REDIS_ENABLED` | If locking via REDIS (multiple instances) is enabled. Requires REDIS. Otherwise uses in-memory locks. | Defaults to `false` |
|
||||
| `LOCK_REDIS_URL` | If locking via REDIS is enabled, this should point to a resolvable REDIS instance, e.g. `redis://<user>:<pass>@localhost:6379/<db>`. | |
|
||||
| | | |
|
||||
| `PROMETHEUS_ENABLED` | If Prometheus metrics are exposed | Defaults to `false` |
|
||||
| `PROMETHEUS_METRICS_PATH` | Defines the metrics endpoint path | Defaults to `/metrics` |
|
||||
| `PROMETHEUS_SECURE_TOKEN_ENABLED` | If Prometheus metrics endpoint is protected by a token when enabled (**recommended**) | Defaults to `true` |
|
||||
| `PROMETHEUS_SECURE_TOKEN` | The token securing the metrics endpoint when enabled (**recommended**) | Not set by default, you need to explicitly set it to a secure random |
|
||||
| Variable | Purpose | Default/Description |
|
||||
|:------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `SECRET` | A 32 character long secure random secret used for encrypting some data inside the database. When data has been created inside the database, the secret cannot be changed anymore, otherwise decryption fails. | Not set by default, you need to explicitly set it, e.g., generate via `openssl rand -hex 16` |
|
||||
| `TZ` | The time zone (**recommended** to set it properly, background tasks depend on it) | Defaults to `Europe/Berlin`, can be any time zone according to _tz database_ |
|
||||
| `ADMIN_USER` | Admin user name for login | Not set by default, you need to explicitly set it to user name |
|
||||
| `ADMIN_PASSWORD` | Admin password for login | Not set by default, you need to explicitly set it to a secure random |
|
||||
| | | |
|
||||
| `DB_TYPE` | The database type (Postgres is **recommended**) | Defaults to `sqlite`, possible values are `sqlite` or `postgres` |
|
||||
| `DB_SQLITE_FILE` | Path to the SQLITE file | Defaults to `<XDG_DATA_DIR>/upda/upda.db`, e.g. `~/.local/share/upda/upda.db` |
|
||||
| `DB_POSTGRES_HOST` | The postgres host | Postgres host address, defaults to `localhost` |
|
||||
| `DB_POSTGRES_PORT` | The postgres port | Postgres port, defaults to `5432` |
|
||||
| `DB_POSTGRES_NAME` | The postgres database name | Postgres database name, needs to be set |
|
||||
| `DB_POSTGRES_TZ` | The postgres time zone | Postgres time zone settings, defaults to `Europe/Berlin` |
|
||||
| `DB_POSTGRES_USER` | The postgres user | Postgres user name, needs to be set |
|
||||
| `DB_POSTGRES_PASSWORD` | The postgres password | Postgres user password, needs to be set |
|
||||
| | | |
|
||||
| `SERVER_PORT` | Port | Defaults to `8080` |
|
||||
| `SERVER_LISTEN` | Server's listen address | Defaults to empty which equals `0.0.0.0` |
|
||||
| `SERVER_TLS_ENABLED` | If server uses TLS | Defaults `false` |
|
||||
| `SERVER_TLS_CERT_PATH` | When TLS enabled, provide the certificate path | |
|
||||
| `SERVER_TLS_KEY_PATH` | When TLS enabled, provide the key path | |
|
||||
| `SERVER_TIMEOUT` | Timeout the server waits before shutting down to end any pending tasks | Defaults to `1s` (1 second), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `CORS_ALLOW_ORIGIN` | CORS configuration | Defaults to `*` |
|
||||
| `CORS_ALLOW_METHODS` | CORS configuration | Defaults to `GET, POST, PUT, PATCH, DELETE, OPTIONS` |
|
||||
| `CORS_ALLOW_HEADERS` | CORS configuration | Defaults to `Authorization, Content-Type` |
|
||||
| | | |
|
||||
| `LOGGING_LEVEL` | Logging level. Possible are `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal`. Setting to `debug` enables high verbosity output. | Defaults to `info` |
|
||||
| `LOGGING_ENCODING` | Logging encoding. Possible are `console` and `json` | Defaults to `json` |
|
||||
| `LOGGING_DIRECTORY` | Logging directory. When set, logs will be added to a file called `upda.log` in addition to the standard output. Ensure that upda has access permissions. Use an external program for log rotation if desired. | |
|
||||
| | | |
|
||||
| `WEBHOOKS_TOKEN_LENGTH` | The length of the token | Defaults to `16`, positive number |
|
||||
| | | |
|
||||
| `TASK_UPDATE_CLEAN_STALE_ENABLED` | If background task should run to do housekeeping of stale (ignored/approved) updates from the database | Defaults to `false` |
|
||||
| `TASK_UPDATE_CLEAN_STALE_INTERVAL` | Interval at which a background task does housekeeping by deleting stale (ignored/approved) updates from the database | Defaults to `1h` (1 hour), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_UPDATE_CLEAN_STALE_MAX_AGE` | Number defining at which age stale (ignored/approved) updates are deleted by the background task (_updatedAt_ attribute decides) | Defaults to `720h` (168 hours = 1 week), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| | | |
|
||||
| `TASK_EVENT_CLEAN_STALE_ENABLED` | If background task should run to do housekeeping of stale (old) events from the database | Defaults to `false` |
|
||||
| `TASK_EVENT_CLEAN_STALE_INTERVAL` | Interval at which a background task does housekeeping by deleting stale (old) events from the database | Defaults to `8h` (8 hours), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_EVENT_CLEAN_STALE_MAX_AGE` | Number defining at which age stale (old) events are deleted by the background task (_updatedAt_ attribute decides) | Defaults to `2190h` (2190 hours = 3 months), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| | | |
|
||||
| `TASK_ACTIONS_ENQUEUE_ENABLED` | If background task should run to enqueue matching actions derived from events (actions are invocation separately after being enqueued) | Defaults to `true` |
|
||||
| `TASK_ACTIONS_ENQUEUE_INTERVAL` | Interval at which a background task does check to enqueue actions | Defaults to `10s` (10 seconds), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_ACTIONS_ENQUEUE_BATCH_SIZE` | Number defining how many unhandled events are processed in a batch by the background task | Defaults to `1`, must be positive number |
|
||||
| | | |
|
||||
| `TASK_ACTIONS_INVOKE_ENABLED` | If background task should run to invoke enqueued actions derived | Defaults to `true` |
|
||||
| `TASK_ACTIONS_INVOKE_INTERVAL` | Interval at which a background task does check to invoke enqueued actions | Defaults to `10s` (10 seconds), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_ACTIONS_INVOKE_BATCH_SIZE` | Number defining how many enqueued actions are processed in a batch by the background task | Defaults to `1`, must be positive number |
|
||||
| `TASK_ACTIONS_INVOKE_MAX_RETRIES` | Number defining how often actions are invoked in case of an error, if exceeded, those actions are not retried again | Defaults to `3`, must be positive number |
|
||||
| | | |
|
||||
| `TASK_ACTIONS_CLEAN_STALE_ENABLED` | If background task should run to do housekeeping of stale (handled, meaning success or error state) actions from the database | Defaults to `true` |
|
||||
| `TASK_ACTIONS_CLEAN_STALE_INTERVAL` | Interval at which a background task does housekeeping by deleting stale (handled) actions from the database | Defaults to `12h` (12 hours), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| `TASK_ACTIONS_CLEAN_STALE_MAX_AGE` | Number defining at which age stale (handled) actions are deleted by the background task (_updatedAt_ attribute decides) | Defaults to `720h` (720 hours = 30 days), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| | | |
|
||||
| `TASK_PROMETHEUS_REFRESH_INTERVAL` | Interval at which a background task updates custom metrics | Defaults to `60s` (60 seconds), qualifier can be `s = second`, `m = minute`, `h = hour` prefixed with a positive number |
|
||||
| | | |
|
||||
| `LOCK_REDIS_ENABLED` | If locking via REDIS (multiple instances) is enabled. Requires REDIS. Otherwise uses in-memory locks. | Defaults to `false` |
|
||||
| `LOCK_REDIS_URL` | If locking via REDIS is enabled, this should point to a resolvable REDIS instance, e.g. `redis://<user>:<pass>@localhost:6379/<db>`. | |
|
||||
| | | |
|
||||
| `PROMETHEUS_ENABLED` | If Prometheus metrics are exposed | Defaults to `false` |
|
||||
| `PROMETHEUS_METRICS_PATH` | Defines the metrics endpoint path | Defaults to `/metrics` |
|
||||
| `PROMETHEUS_SECURE_TOKEN_ENABLED` | If Prometheus metrics endpoint is protected by a token when enabled (**recommended**) | Defaults to `true` |
|
||||
| `PROMETHEUS_SECURE_TOKEN` | The token securing the metrics endpoint when enabled (**recommended**) | Not set by default, you need to explicitly set it to a secure random |
|
||||
|
||||
## 3rd party integrations
|
||||
|
||||
|
@ -182,8 +201,10 @@ This is the core mechanism of _upda_ and why it exists. Webhooks are the central
|
|||
updates.
|
||||
|
||||
In order to configure a 3rd party application like [duin](https://crazymax.dev/diun/) to send updates to _upda_ with
|
||||
the [duin webhook notification configuration](https://crazymax.dev/diun/notif/webhook/), **create** a new _upda_ webhook
|
||||
token via _upda_'s web interface or via API call. This gives you
|
||||
the [duin webhook notification configuration](https://crazymax.dev/diun/notif/webhook/), create a new _upda_ webhook via
|
||||
web interface or via API call.
|
||||
|
||||
This gives you
|
||||
|
||||
* a unique _upda_ URL to configure in the notification part of [duin](https://crazymax.dev/diun/),
|
||||
e.g., `/api/v1/webhooks/<a unique identifier>`
|
||||
|
@ -204,6 +225,64 @@ notif:
|
|||
timeout: 10s
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
Actions can be used to invoke arbitrary third party tools when an _event_ occurs, e.g., an update has been created or
|
||||
modified. An action is triggered when its condition meet the action's definition (event name, host, application,
|
||||
provider).
|
||||
|
||||
Actions have types. Different types require different payload to set them up. [shoutrrr](#shoutrrr) is supported as
|
||||
action type, which can send notifications to a variety of services like Gotify, Ntfy, Teams, OpsGenie and many more.
|
||||
|
||||
Supported events are the following:
|
||||
|
||||
| Event name | Description |
|
||||
|:--------------------------------|:--------------------------------------------------------------------|
|
||||
| `update_created` | An update has been created |
|
||||
| `update_updated` | An update has been updated (not necessarily its version attribute!) |
|
||||
| `update_updated_state_pending` | An update's state changed to pending |
|
||||
| `update_updated_state_approved` | An update's state changed to approved |
|
||||
| `update_updated_state_ignored` | An update's state changed to ignored |
|
||||
| `update_deleted` | An update has been removed |
|
||||
|
||||
For privacy, an action's configuration supports upda's **secrets** vault, which means that before an action is
|
||||
triggered, any occurrence of `<SECRET>SECRET_KEY</SECRET>` is properly replaced by the value of the `SECRET_KEY` defined
|
||||
inside the vault.
|
||||
|
||||
In addition to secrets, upda provides **variables** which can be used with the `<VAR>VARIABLE_NAME</VAR>` syntax and any
|
||||
occurrence is replaced before invocation as well.
|
||||
|
||||
| Variable name | Description |
|
||||
|:-------------------------|:--------------------------------------------------|
|
||||
| `<VAR>APPLICATION</VAR>` | The update's application name invoking the action |
|
||||
| `<VAR>PROVIDER</VAR>` | The update's provider name invoking the action |
|
||||
| `<VAR>HOST</VAR>` | The update's host invoking the action |
|
||||
| `<VAR>VERSION</VAR>` | The update's version (latest) invoking the action |
|
||||
|
||||
#### shoutrrr
|
||||
|
||||
[shoutrrr](https://github.com/containrrr/shoutrrr?tab=readme-ov-file#documentation) supports multiple services directly
|
||||
which can be provided as simple URL, e.g., `gotify://gotify.example.com:443/<token>`, where `<token>`
|
||||
can also be provided as secret: `gotify://gotify.example.com:443/<SECRET>GOTIFY_TOKEN</SECRET>`.
|
||||
|
||||
A full payload for defining an upda shoutrrr action looks like the following. No worries, there's
|
||||
a [web interface](https://git.myservermanager.com/varakh/upda-ui) for configuring actions:
|
||||
|
||||
```json5
|
||||
{
|
||||
// ...
|
||||
"type": "shoutrrr",
|
||||
"matchEvent": "update_created",
|
||||
// payload 'urls' and 'body' are specific to the shoutrrr action type
|
||||
"payload": {
|
||||
"urls": [
|
||||
"gotify://myurl/<SECRET>GOTIFY_TOKEN</SECRET>/?title=Great+News+On+Upda"
|
||||
],
|
||||
"body": "A new update arrived on <VAR>HOST</VAR> for <VAR>APPLICATION</VAR>. Its version is <VAR>VERSION</VAR>."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
When `PROMETHEUS_ENABLED` is set to `true`, default metrics about memory utilization, but also custom metrics specific
|
||||
|
@ -224,11 +303,6 @@ Custom exposed metrics are exposed under the `upda_` namespace.
|
|||
Examples:
|
||||
|
||||
```shell
|
||||
# HELP upda_updates details for all updates, -1=deleted (deleted next restart), 0=pending, 1=approved, 2=ignored
|
||||
upda_updates{application="codeberg.org/forgejo/forgejo",host="myserver",provider="oci"} 0
|
||||
upda_updates{application="docker.io/library/mysql",host="myserver",provider="oci"} 2
|
||||
upda_updates{application="quay.io/navidys/prometheus-podman-exporter",host="myserver",provider="oci"} 1
|
||||
upda_updates{application="quay.io/navidys/prometheus-podman-exporter",host="myserver2",provider="oci"} 1
|
||||
# HELP upda_updates_all amount of all updates
|
||||
upda_updates_all 4
|
||||
# HELP upda_updates_approved amount of all updates in approved state
|
||||
|
@ -241,6 +315,8 @@ upda_updates_pending 1
|
|||
upda_webhooks 2
|
||||
# HELP upda_events amount of all events
|
||||
upda_events 146
|
||||
# HELP upda_actions amount of all actions
|
||||
upda_actions 0
|
||||
```
|
||||
|
||||
There's an example [Grafana](https://grafana.com) dashboard in the `_doc/` folder.
|
||||
|
@ -248,17 +324,17 @@ There's an example [Grafana](https://grafana.com) dashboard in the `_doc/` folde
|
|||
[Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) could check for the following:
|
||||
|
||||
```yaml
|
||||
- name: update_checks
|
||||
rules:
|
||||
- alert: UpdatesAvailable
|
||||
expr: upda_updates == 0 and upda_updates_pending > 0
|
||||
for: 4w
|
||||
labels:
|
||||
severity: high
|
||||
class: update
|
||||
annotations:
|
||||
summary: "Updates available from upda for {{ $labels.job }}"
|
||||
description: "Updates available from upda for {{ $labels.job }}"
|
||||
- name: update_checks
|
||||
rules:
|
||||
- alert: UpdatesAvailable
|
||||
expr: upda_updates == 0 and upda_updates_pending > 0
|
||||
for: 4w
|
||||
labels:
|
||||
severity: high
|
||||
class: update
|
||||
annotations:
|
||||
summary: "Updates available from upda for {{ $labels.job }}"
|
||||
description: "Updates available from upda for {{ $labels.job }}"
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
|
|
@ -60,6 +60,8 @@ services:
|
|||
- DB_POSTGRES_PASSWORD=upda
|
||||
- ADMIN_USER=admin
|
||||
- ADMIN_PASSWORD=changeit
|
||||
# generate 32 character long secret, e.g., with "openssl rand -hex 16"
|
||||
- SECRET=generated-secure-secret-32-chars
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
|
@ -121,6 +123,8 @@ services:
|
|||
- TZ=Europe/Berlin
|
||||
- ADMIN_USER=admin
|
||||
- ADMIN_PASSWORD=changeit
|
||||
# generate 32 character long secret, e.g., with "openssl rand -hex 16"
|
||||
- SECRET=generated-secure-secret-32-chars
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
|
|
1603
_doc/api.yaml
1603
_doc/api.yaml
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -39,16 +39,12 @@ func (e WebhookType) Value() string {
|
|||
type EventName string
|
||||
|
||||
const (
|
||||
EventNameUpdateCreated EventName = "update_created"
|
||||
EventNameUpdateUpdated EventName = "update_updated"
|
||||
EventNameUpdateUpdatedPending EventName = "update_updated_state_pending"
|
||||
EventNameUpdateUpdatedApproved EventName = "update_updated_state_approved"
|
||||
EventNameUpdateUpdatedIgnored EventName = "update_updated_state_ignored"
|
||||
EventNameUpdateDeleted EventName = "update_deleted"
|
||||
EventNameWebhookCreated EventName = "webhook_created"
|
||||
EventNameWebhookUpdatedLabel EventName = "webhook_updated_label"
|
||||
EventNameWebhookUpdatedIgnoreHost EventName = "webhook_updated_ignore_host"
|
||||
EventNameWebhookDeleted EventName = "webhook_deleted"
|
||||
EventNameUpdateCreated EventName = "update_created"
|
||||
EventNameUpdateUpdated EventName = "update_updated"
|
||||
EventNameUpdateUpdatedPending EventName = "update_updated_state_pending"
|
||||
EventNameUpdateUpdatedApproved EventName = "update_updated_state_approved"
|
||||
EventNameUpdateUpdatedIgnored EventName = "update_updated_state_ignored"
|
||||
EventNameUpdateDeleted EventName = "update_deleted"
|
||||
)
|
||||
|
||||
func (e *EventName) Scan(value interface{}) error {
|
||||
|
@ -64,7 +60,8 @@ func (e EventName) Value() string {
|
|||
type EventState string
|
||||
|
||||
const (
|
||||
EventStateCreated EventState = "created"
|
||||
EventStateCreated EventState = "created"
|
||||
EventStateEnqueued EventState = "enqueued"
|
||||
)
|
||||
|
||||
func (e *EventState) Scan(value interface{}) error {
|
||||
|
@ -75,3 +72,39 @@ func (e *EventState) Scan(value interface{}) error {
|
|||
func (e EventState) Value() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// ActionType state of an update
|
||||
type ActionType string
|
||||
|
||||
const (
|
||||
ActionTypeShoutrrr ActionType = "shoutrrr"
|
||||
)
|
||||
|
||||
func (e *ActionType) Scan(value interface{}) error {
|
||||
*e = ActionType(value.([]byte))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ActionType) Value() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// ActionInvocationState state of an action invocation
|
||||
type ActionInvocationState string
|
||||
|
||||
const (
|
||||
ActionInvocationStateCreated ActionInvocationState = "created"
|
||||
ActionInvocationStateRunning ActionInvocationState = "running"
|
||||
ActionInvocationStateRetrying ActionInvocationState = "retrying"
|
||||
ActionInvocationStateSuccess ActionInvocationState = "success"
|
||||
ActionInvocationStateError ActionInvocationState = "error"
|
||||
)
|
||||
|
||||
func (e *ActionInvocationState) Scan(value interface{}) error {
|
||||
*e = ActionInvocationState(value.([]byte))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ActionInvocationState) Value() string {
|
||||
return string(e)
|
||||
}
|
||||
|
|
255
api/dto.go
255
api/dto.go
|
@ -25,6 +25,62 @@ type CreateWebhookRequest struct {
|
|||
IgnoreHost bool `json:"ignoreHost"`
|
||||
}
|
||||
|
||||
type CreateSecretRequest struct {
|
||||
Key string `json:"key" binding:"required,min=1"`
|
||||
Value string `json:"value" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
type CreateActionRequest struct {
|
||||
Label string `json:"label" binding:"required,min=1,max=255"`
|
||||
Type string `json:"type" binding:"required,oneof=shoutrrr"`
|
||||
MatchEvent *string `json:"matchEvent"`
|
||||
MatchHost *string `json:"matchHost"`
|
||||
MatchApplication *string `json:"matchApplication"`
|
||||
MatchProvider *string `json:"matchProvider"`
|
||||
Payload interface{} `json:"payload"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type ModifySecretValueRequest struct {
|
||||
Value string `json:"value" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
type ModifyActionLabelRequest struct {
|
||||
Label string `json:"label" binding:"required,min=1,max=255"`
|
||||
}
|
||||
|
||||
type ModifyActionMatchEventRequest struct {
|
||||
MatchEvent *string `json:"matchEvent"`
|
||||
}
|
||||
|
||||
type ModifyActionMatchHostRequest struct {
|
||||
MatchHost *string `json:"matchHost"`
|
||||
}
|
||||
|
||||
type ModifyActionMatchApplicationRequest struct {
|
||||
MatchApplication *string `json:"matchApplication"`
|
||||
}
|
||||
|
||||
type ModifyActionMatchProviderRequest struct {
|
||||
MatchProvider *string `json:"matchProvider"`
|
||||
}
|
||||
|
||||
type ModifyActionTypeAndPayloadRequest struct {
|
||||
Type ActionType `json:"type" binding:"required,oneof=shoutrrr"`
|
||||
Payload interface{} `json:"payload" binding:"required"`
|
||||
}
|
||||
|
||||
type ModifyActionEnabledRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type TestActionRequest struct {
|
||||
Application string `json:"application" binding:"required,min=1"`
|
||||
Provider string `json:"provider" binding:"required,min=1"`
|
||||
Host string `json:"host" binding:"required,min=1"`
|
||||
Version string `json:"version" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
type PaginateUpdateRequest struct {
|
||||
PageSize int `form:"pageSize,default=5" binding:"numeric,gte=1"`
|
||||
Page int `form:"page,default=1" binding:"numeric,gte=1"`
|
||||
|
@ -35,10 +91,24 @@ type PaginateUpdateRequest struct {
|
|||
}
|
||||
|
||||
type PaginateWebhookRequest struct {
|
||||
PageSize int `form:"pageSize,default=5" binding:"numeric,gte=1"`
|
||||
Page int `form:"page,default=1" binding:"numeric,gte=1"`
|
||||
Order string `form:"order,default=asc" binding:"oneof=asc desc"`
|
||||
OrderBy string `form:"orderBy,default=label" binding:"oneof=id label type created_at updated_at"`
|
||||
}
|
||||
|
||||
type PaginateActionRequest struct {
|
||||
PageSize int `form:"pageSize,default=5" binding:"numeric,gte=1"`
|
||||
Page int `form:"page,default=1" binding:"numeric,gte=1"`
|
||||
Order string `form:"order,default=asc" binding:"oneof=asc desc"`
|
||||
OrderBy string `form:"orderBy,default=label" binding:"oneof=id label type created_at updated_at"`
|
||||
}
|
||||
|
||||
type PaginateActionInvocationRequest struct {
|
||||
PageSize int `form:"pageSize,default=5" binding:"numeric,gte=1"`
|
||||
Page int `form:"page,default=1" binding:"numeric,gte=1"`
|
||||
Order string `form:"order,default=desc" binding:"oneof=asc desc"`
|
||||
OrderBy string `form:"orderBy,default=updated_at" binding:"oneof=id label type created_at updated_at"`
|
||||
OrderBy string `form:"orderBy,default=created_at" binding:"oneof=id state retry_count created_at updated_at"`
|
||||
}
|
||||
|
||||
type WebhookGenericRequest struct {
|
||||
|
@ -216,12 +286,25 @@ func NewWebhookPageResponse(content []*WebhookResponse, page int, pageSize int,
|
|||
type EventResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Payload interface{} `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
type EventSingleResponse struct {
|
||||
Data EventResponse `json:"data"`
|
||||
}
|
||||
|
||||
func NewEventSingleResponse(id uuid.UUID, name string, createdAt time.Time, updatedAt time.Time, payload interface{}) *EventSingleResponse {
|
||||
e := new(EventSingleResponse)
|
||||
e.Data.ID = id
|
||||
e.Data.Name = name
|
||||
e.Data.CreatedAt = createdAt
|
||||
e.Data.UpdatedAt = updatedAt
|
||||
e.Data.Payload = payload
|
||||
return e
|
||||
}
|
||||
|
||||
type EventWindowResponse struct {
|
||||
Content []*EventResponse `json:"content"`
|
||||
Size int `json:"size"`
|
||||
|
@ -269,24 +352,160 @@ func NewEventWindowResponse(content []*EventResponse, size int, skip int, orderB
|
|||
return e
|
||||
}
|
||||
|
||||
type EventPayloadWebhookCreatedDto struct {
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IgnoreHost bool `json:"ignoreHost"`
|
||||
type SecretResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type EventPayloadWebhookUpdatedDto struct {
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
LabelPrior string `json:"labelPrior,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
IgnoreHostPrior bool `json:"ignoreHostPrior"`
|
||||
IgnoreHost bool `json:"ignoreHost"`
|
||||
Type string `json:"type,omitempty"`
|
||||
type SecretSingleResponse struct {
|
||||
Data SecretResponse `json:"data"`
|
||||
}
|
||||
|
||||
type EventPayloadWebhookDeletedDto struct {
|
||||
Label string `json:"label,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IgnoreHost bool `json:"ignoreHost"`
|
||||
func NewSecretSingleResponse(id uuid.UUID, key string, value string, createdAt time.Time, updatedAt time.Time) *SecretSingleResponse {
|
||||
e := new(SecretSingleResponse)
|
||||
e.Data.ID = id
|
||||
e.Data.Key = key
|
||||
e.Data.Value = value
|
||||
e.Data.CreatedAt = createdAt
|
||||
e.Data.UpdatedAt = updatedAt
|
||||
return e
|
||||
}
|
||||
|
||||
type SecretPageResponse struct {
|
||||
Content []*SecretResponse `json:"content"`
|
||||
}
|
||||
|
||||
type SecretDataPageResponse struct {
|
||||
Data *SecretPageResponse `json:"data"`
|
||||
}
|
||||
|
||||
func NewSecretPageResponse(content []*SecretResponse) *SecretPageResponse {
|
||||
e := new(SecretPageResponse)
|
||||
e.Content = content
|
||||
return e
|
||||
}
|
||||
|
||||
type ActionResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
MatchEvent *string `json:"matchEvent,omitempty"`
|
||||
MatchHost *string `json:"matchHost,omitempty"`
|
||||
MatchApplication *string `json:"matchApplication,omitempty"`
|
||||
MatchProvider *string `json:"matchProvider,omitempty"`
|
||||
Payload interface{} `json:"payload,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ActionSingleResponse struct {
|
||||
Data ActionResponse `json:"data"`
|
||||
}
|
||||
|
||||
func NewActionSingleResponse(id uuid.UUID, label string, t string, matchEvent *string, matchHost *string, matchApplication *string, matchProvider *string, payload interface{}, enabled bool, createdAt time.Time, updatedAt time.Time) *ActionSingleResponse {
|
||||
e := new(ActionSingleResponse)
|
||||
e.Data.ID = id
|
||||
e.Data.Label = label
|
||||
e.Data.Type = t
|
||||
e.Data.MatchEvent = matchEvent
|
||||
e.Data.MatchHost = matchHost
|
||||
e.Data.MatchApplication = matchApplication
|
||||
e.Data.MatchProvider = matchProvider
|
||||
e.Data.Payload = payload
|
||||
e.Data.Enabled = enabled
|
||||
e.Data.CreatedAt = createdAt
|
||||
e.Data.UpdatedAt = updatedAt
|
||||
return e
|
||||
}
|
||||
|
||||
type ActionPageResponse struct {
|
||||
Content []*ActionResponse `json:"content"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
TotalElements int64 `json:"totalElements"`
|
||||
TotalPages int64 `json:"totalPages"`
|
||||
}
|
||||
|
||||
func NewActionPageResponse(content []*ActionResponse, page int, pageSize int, orderBy string, order string, totalElements int64, totalPages int64) *ActionPageResponse {
|
||||
e := new(ActionPageResponse)
|
||||
e.Content = content
|
||||
e.Page = page
|
||||
e.PageSize = pageSize
|
||||
e.OrderBy = orderBy
|
||||
e.Order = order
|
||||
e.TotalElements = totalElements
|
||||
e.TotalPages = totalPages
|
||||
return e
|
||||
}
|
||||
|
||||
type ActionTestResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ActionTestSingleResponse struct {
|
||||
Data ActionTestResponse `json:"data"`
|
||||
}
|
||||
|
||||
func NewActionTestSingleResponse(success bool, message string) *ActionTestSingleResponse {
|
||||
e := new(ActionTestSingleResponse)
|
||||
e.Data.Success = success
|
||||
e.Data.Message = message
|
||||
return e
|
||||
}
|
||||
|
||||
type ActionInvocationResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
RetryCount int `json:"retryCount"`
|
||||
State string `json:"state"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
ActionID string `json:"actionId"`
|
||||
EventID string `json:"eventId"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ActionInvocationSingleResponse struct {
|
||||
Data ActionInvocationResponse `json:"data"`
|
||||
}
|
||||
|
||||
func NewActionInvocationSingleResponse(id uuid.UUID, retryCount int, state string, message *string, actionId string, eventId string, createdAt time.Time, updatedAt time.Time) *ActionInvocationSingleResponse {
|
||||
e := new(ActionInvocationSingleResponse)
|
||||
e.Data.ID = id
|
||||
e.Data.RetryCount = retryCount
|
||||
e.Data.State = state
|
||||
e.Data.Message = message
|
||||
e.Data.ActionID = actionId
|
||||
e.Data.EventID = eventId
|
||||
e.Data.CreatedAt = createdAt
|
||||
e.Data.UpdatedAt = updatedAt
|
||||
return e
|
||||
}
|
||||
|
||||
type ActionInvocationPageResponse struct {
|
||||
Content []*ActionInvocationResponse `json:"content"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
TotalElements int64 `json:"totalElements"`
|
||||
TotalPages int64 `json:"totalPages"`
|
||||
}
|
||||
|
||||
func NewActionInvocationPageResponse(content []*ActionInvocationResponse, page int, pageSize int, orderBy string, order string, totalElements int64, totalPages int64) *ActionInvocationPageResponse {
|
||||
e := new(ActionInvocationPageResponse)
|
||||
e.Content = content
|
||||
e.Page = page
|
||||
e.PageSize = pageSize
|
||||
e.OrderBy = orderBy
|
||||
e.Order = order
|
||||
e.TotalElements = totalElements
|
||||
e.TotalPages = totalPages
|
||||
return e
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -5,6 +5,7 @@ go 1.21
|
|||
require (
|
||||
github.com/Depado/ginprom v1.8.1
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/gin-contrib/cors v1.7.1
|
||||
github.com/gin-contrib/zap v1.1.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
|
@ -32,13 +33,13 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-redsync/redsync/v4 v4.11.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
|
@ -49,9 +50,9 @@ require (
|
|||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
|
127
go.sum
127
go.sum
|
@ -2,8 +2,6 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
|||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Depado/ginprom v1.8.0 h1:zaaibRLNI1dMiiuj1MKzatm8qrcHzikMlCc1anqOdyo=
|
||||
github.com/Depado/ginprom v1.8.0/go.mod h1:XBaKzeNBqPF4vxJpNLincSQZeMDnZp1tIbU0FU0UKgg=
|
||||
github.com/Depado/ginprom v1.8.1 h1:lrQTddbRqlHq1j6SpJDySDumJlR7FEybzdX0PS3HXPc=
|
||||
github.com/Depado/ginprom v1.8.1/go.mod h1:9Z+ahPJLSeMndDfnDTfiuBn2SKVAuL2yvihApWzof9A=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
|
@ -23,10 +21,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
|
||||
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
|
||||
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
|
@ -37,7 +31,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F
|
|||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
|
@ -45,6 +38,8 @@ github.com/containerd/containerd v1.7.7 h1:QOC2K4A42RQpcrZyptP6z9EJZnlHfHJUfZrAA
|
|||
github.com/containerd/containerd v1.7.7/go.mod h1:3c4XZv6VeT9qgf9GMTxNTMFxGJrGpI2vz1yk4ye+YY8=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
|
@ -63,22 +58,14 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
|
|||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
||||
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
||||
github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg=
|
||||
github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro=
|
||||
github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs=
|
||||
github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/zap v0.2.0 h1:HLvt3rZXyC8XC+s2lHzMFow3UDqiEbfrBWJyHHS6L8A=
|
||||
github.com/gin-contrib/zap v0.2.0/go.mod h1:eqfbe9ZmI+GgTZF6nRiC2ZwDeM4DK1Viwc8OxTCphh0=
|
||||
github.com/gin-contrib/zap v1.1.0 h1:GWzL9+zmK8OJdiycaK2SK1/D3SZIYpieJDD0QCNAU1o=
|
||||
github.com/gin-contrib/zap v1.1.0/go.mod h1:KzROP9rAL7ofFd1P8lx7Oo2lerwPWNL5vv4f6U/mAk8=
|
||||
github.com/gin-contrib/zap v1.1.1 h1:DDyIF9YQorl3gZzAabIowRywHJuohDfiLnhwvWKl6SY=
|
||||
github.com/gin-contrib/zap v1.1.1/go.mod h1:YW8KOko2kYLy8g6k9YgVNTj7SIcrUEzYiAd9IjiBPs0=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
|
@ -87,6 +74,8 @@ github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b
|
|||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-co-op/gocron-redis-lock v1.3.0 h1:PKwtuc/BhrDll/DxJfnXoW/+D1VXubd47xcGaB9pDuM=
|
||||
github.com/go-co-op/gocron-redis-lock v1.3.0/go.mod h1:9+H7ZfqVtJfx94uEAELwH+uHkn1UpM6lRM99wOBTGtg=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
|
@ -95,12 +84,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
||||
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
||||
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
|
@ -111,29 +94,24 @@ github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F4
|
|||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-redsync/redsync/v4 v4.11.0 h1:OPEcAxHBb95EzfwCKWM93ksOwHd5bTce2BD4+R14N6k=
|
||||
github.com/go-redsync/redsync/v4 v4.11.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc=
|
||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
|
||||
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -147,6 +125,8 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/
|
|||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
|
@ -157,8 +137,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
|
@ -171,22 +149,19 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
|
@ -202,16 +177,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
@ -222,28 +197,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
||||
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.5.0 h1:Xe9TKMmZv939gwTBcvc0n1tzK5l2re0pKw/W/tN3amw=
|
||||
github.com/redis/go-redis/v9 v9.5.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||
|
@ -272,7 +233,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
|
@ -288,12 +248,8 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
|
|||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
||||
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
|
@ -307,31 +263,22 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
|
||||
|
@ -350,13 +297,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -372,12 +315,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
@ -385,7 +326,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -393,11 +333,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
@ -409,18 +347,11 @@ golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
|||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg=
|
||||
google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -433,23 +364,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU=
|
||||
gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
|
||||
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
moul.io/zapgorm2 v1.3.0 h1:+CzUTMIcnafd0d/BvBce8T4uPn6DQnpIrz64cyixlkk=
|
||||
|
|
230
server/api_handler_action.go
Normal file
230
server/api_handler_action.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type actionHandler struct {
|
||||
service actionService
|
||||
}
|
||||
|
||||
func newActionHandler(s *actionService) *actionHandler {
|
||||
return &actionHandler{service: *s}
|
||||
}
|
||||
|
||||
func (h *actionHandler) paginate(c *gin.Context) {
|
||||
var queryParams api.PaginateActionRequest
|
||||
var err error
|
||||
if err = c.ShouldBindQuery(&queryParams); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var actions []*Action
|
||||
if actions, err = h.service.paginate(queryParams.Page, queryParams.PageSize, queryParams.OrderBy, queryParams.Order); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
var data []*api.ActionResponse
|
||||
data = make([]*api.ActionResponse, 0)
|
||||
|
||||
for _, e := range actions {
|
||||
data = append(data, &api.ActionResponse{
|
||||
ID: e.ID,
|
||||
Label: e.Label,
|
||||
Type: e.Type,
|
||||
MatchEvent: e.MatchEvent,
|
||||
MatchHost: e.MatchHost,
|
||||
MatchApplication: e.MatchApplication,
|
||||
MatchProvider: e.MatchProvider,
|
||||
Payload: e.Payload,
|
||||
Enabled: e.Enabled,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
var totalElements int64
|
||||
if totalElements, err = h.service.count(); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := (totalElements + int64(queryParams.PageSize) - 1) / int64(queryParams.PageSize)
|
||||
c.JSON(http.StatusOK, api.NewDataResponseWithPayload(api.NewActionPageResponse(data, queryParams.Page, queryParams.PageSize, queryParams.OrderBy, queryParams.Order, totalElements, totalPages)))
|
||||
}
|
||||
|
||||
func (h *actionHandler) get(c *gin.Context) {
|
||||
e, err := h.service.get(c.Param("id"))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) create(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.CreateActionRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.create(req.Label, api.ActionType(req.Type), req.MatchEvent, req.MatchHost, req.MatchApplication, req.MatchProvider, req.Payload, req.Enabled); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateLabel(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionLabelRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateLabel(c.Param("id"), req.Label); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateMatchEvent(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionMatchEventRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateMatchEvent(c.Param("id"), req.MatchEvent); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateMatchHost(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionMatchHostRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateMatchHost(c.Param("id"), req.MatchHost); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateMatchApplication(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionMatchApplicationRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateMatchApplication(c.Param("id"), req.MatchApplication); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateMatchProvider(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionMatchProviderRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateMatchProvider(c.Param("id"), req.MatchProvider); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updatePayload(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionTypeAndPayloadRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateTypeAndPayload(c.Param("id"), req.Type, req.Payload); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) updateEnabled(c *gin.Context) {
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
var req api.ModifyActionEnabledRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateEnabled(c.Param("id"), req.Enabled); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionSingleResponse(e.ID, e.Label, e.Type, e.MatchEvent, e.MatchHost, e.MatchApplication, e.MatchProvider, e.Payload, e.Enabled, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionHandler) delete(c *gin.Context) {
|
||||
if err := h.service.delete(c.Param("id")); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header(headerContentType, headerContentTypeApplicationJson)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
102
server/api_handler_action_invocation.go
Normal file
102
server/api_handler_action_invocation.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type actionInvocationHandler struct {
|
||||
actionService actionService
|
||||
actionInvocationService actionInvocationService
|
||||
}
|
||||
|
||||
func newActionInvocationHandler(as *actionService, ais *actionInvocationService) *actionInvocationHandler {
|
||||
return &actionInvocationHandler{actionService: *as, actionInvocationService: *ais}
|
||||
}
|
||||
|
||||
func (h *actionInvocationHandler) test(c *gin.Context) {
|
||||
var err error
|
||||
var req api.TestActionRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var e *Action
|
||||
if e, err = h.actionService.get(c.Param("id")); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.actionInvocationService.execute(e, &eventPayloadInformationDto{Application: req.Application, Host: req.Host, Provider: req.Provider, Version: req.Version})
|
||||
|
||||
isSuccess := err == nil
|
||||
var message string
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionTestSingleResponse(isSuccess, message))
|
||||
}
|
||||
|
||||
func (h *actionInvocationHandler) paginate(c *gin.Context) {
|
||||
var queryParams api.PaginateActionInvocationRequest
|
||||
var err error
|
||||
if err = c.ShouldBindQuery(&queryParams); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var actionInvocations []*ActionInvocation
|
||||
if actionInvocations, err = h.actionInvocationService.paginate(queryParams.Page, queryParams.PageSize, queryParams.OrderBy, queryParams.Order); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
var data []*api.ActionInvocationResponse
|
||||
data = make([]*api.ActionInvocationResponse, 0)
|
||||
|
||||
for _, e := range actionInvocations {
|
||||
data = append(data, &api.ActionInvocationResponse{
|
||||
ID: e.ID,
|
||||
RetryCount: e.RetryCount,
|
||||
State: e.State,
|
||||
Message: e.Message,
|
||||
ActionID: e.ActionID,
|
||||
EventID: e.EventID,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
var totalElements int64
|
||||
if totalElements, err = h.actionInvocationService.count(); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := (totalElements + int64(queryParams.PageSize) - 1) / int64(queryParams.PageSize)
|
||||
c.JSON(http.StatusOK, api.NewDataResponseWithPayload(api.NewActionInvocationPageResponse(data, queryParams.Page, queryParams.PageSize, queryParams.OrderBy, queryParams.Order, totalElements, totalPages)))
|
||||
}
|
||||
|
||||
func (h *actionInvocationHandler) get(c *gin.Context) {
|
||||
e, err := h.actionInvocationService.get(c.Param("id"))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewActionInvocationSingleResponse(e.ID, e.RetryCount, e.State, e.Message, e.ActionID, e.EventID, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *actionInvocationHandler) delete(c *gin.Context) {
|
||||
if err := h.actionInvocationService.delete(c.Param("id")); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header(headerContentType, headerContentTypeApplicationJson)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
|
@ -35,7 +35,6 @@ func (h *eventHandler) window(c *gin.Context) {
|
|||
data = append(data, &api.EventResponse{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
State: e.State,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
Payload: e.Payload,
|
||||
|
@ -51,6 +50,16 @@ func (h *eventHandler) window(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, api.NewDataResponseWithPayload(api.NewEventWindowResponse(data, queryParams.Size, queryParams.Skip, queryParams.OrderBy, queryParams.Order, hasNext)))
|
||||
}
|
||||
|
||||
func (h *eventHandler) get(c *gin.Context) {
|
||||
e, err := h.service.get(c.Param("id"))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewEventSingleResponse(e.ID, e.Name, e.CreatedAt, e.UpdatedAt, e.Payload))
|
||||
}
|
||||
|
||||
func (h *eventHandler) delete(c *gin.Context) {
|
||||
if err := h.service.delete(c.Param("id")); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
|
|
97
server/api_handler_secret.go
Normal file
97
server/api_handler_secret.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type secretHandler struct {
|
||||
service secretService
|
||||
}
|
||||
|
||||
func newSecretHandler(s *secretService) *secretHandler {
|
||||
return &secretHandler{service: *s}
|
||||
}
|
||||
|
||||
func (h *secretHandler) getAll(c *gin.Context) {
|
||||
var secrets []*Secret
|
||||
var err error
|
||||
|
||||
if secrets, err = h.service.getAll(); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
var data []*api.SecretResponse
|
||||
data = make([]*api.SecretResponse, 0)
|
||||
|
||||
for _, e := range secrets {
|
||||
data = append(data, &api.SecretResponse{
|
||||
ID: e.ID,
|
||||
Key: e.Key,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewDataResponseWithPayload(api.NewSecretPageResponse(data)))
|
||||
}
|
||||
|
||||
func (h *secretHandler) create(c *gin.Context) {
|
||||
var e *Secret
|
||||
var err error
|
||||
|
||||
var req api.CreateSecretRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.upsert(req.Key, req.Value); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewSecretSingleResponse(e.ID, e.Key, e.Value, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *secretHandler) updateValue(c *gin.Context) {
|
||||
var e *Secret
|
||||
var err error
|
||||
|
||||
var req api.ModifySecretValueRequest
|
||||
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if e, err = h.service.updateValue(c.Param("id"), req.Value); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewSecretSingleResponse(e.ID, e.Key, e.Value, e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *secretHandler) get(c *gin.Context) {
|
||||
e, err := h.service.get(c.Param("id"))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewSecretSingleResponse(e.ID, e.Key, "", e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *secretHandler) delete(c *gin.Context) {
|
||||
if err := h.service.delete(c.Param("id")); err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header(headerContentType, headerContentTypeApplicationJson)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
|
@ -82,7 +82,7 @@ func (h *updateHandler) updateState(c *gin.Context) {
|
|||
|
||||
var req api.ModifyUpdateStateRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -52,13 +52,23 @@ func (h *webhookHandler) paginate(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, api.NewDataResponseWithPayload(api.NewWebhookPageResponse(data, queryParams.Page, queryParams.PageSize, queryParams.OrderBy, queryParams.Order, totalElements, totalPages)))
|
||||
}
|
||||
|
||||
func (h *webhookHandler) get(c *gin.Context) {
|
||||
e, err := h.service.get(c.Param("id"))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(errToHttpStatus(err), err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.NewWebhookSingleResponse(e.ID, e.Label, e.Type, e.IgnoreHost, "", e.CreatedAt, e.UpdatedAt))
|
||||
}
|
||||
|
||||
func (h *webhookHandler) create(c *gin.Context) {
|
||||
var e *Webhook
|
||||
var err error
|
||||
|
||||
var req api.CreateWebhookRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
@ -77,7 +87,7 @@ func (h *webhookHandler) updateLabel(c *gin.Context) {
|
|||
|
||||
var req api.ModifyWebhookLabelRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
@ -96,7 +106,7 @@ func (h *webhookHandler) updateIgnoreHost(c *gin.Context) {
|
|||
|
||||
var req api.ModifyWebhookIgnoreHostRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
errAbortWithValidatorPayload(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -45,15 +45,22 @@ func Start() {
|
|||
updateRepo := newUpdateDbRepo(env.db)
|
||||
webhookRepo := newWebhookDbRepo(env.db)
|
||||
eventRepo := newEventDbRepo(env.db)
|
||||
secretRepo := newSecretDbRepo(env.db)
|
||||
actionRepo := newActionDbRepo(env.db)
|
||||
actionInvocationRepo := newActionInvocationDbRepo(env.db)
|
||||
|
||||
lockService := newLockMemService()
|
||||
|
||||
eventService := newEventService(eventRepo)
|
||||
updateService := newUpdateService(updateRepo, eventService, prometheusService)
|
||||
webhookService := newWebhookService(webhookRepo, env.webhookConfig, eventService)
|
||||
updateService := newUpdateService(updateRepo, eventService)
|
||||
webhookService := newWebhookService(webhookRepo, env.webhookConfig)
|
||||
webhookInvocationService := newWebhookInvocationService(webhookService, updateService, env.webhookConfig)
|
||||
|
||||
taskService := newTaskService(updateService, eventService, webhookService, lockService, prometheusService, env.appConfig, env.taskConfig, env.lockConfig, env.prometheusConfig)
|
||||
secretService := newSecretService(secretRepo)
|
||||
actionService := newActionService(actionRepo, eventService)
|
||||
actionInvocationService := newActionInvocationService(actionInvocationRepo, actionService, eventService, secretService)
|
||||
|
||||
taskService := newTaskService(updateService, eventService, webhookService, actionService, actionInvocationService, lockService, prometheusService, env.appConfig, env.taskConfig, env.lockConfig, env.prometheusConfig)
|
||||
taskService.init()
|
||||
taskService.start()
|
||||
|
||||
|
@ -61,6 +68,10 @@ func Start() {
|
|||
webhookHandler := newWebhookHandler(webhookService)
|
||||
webhookInvocationHandler := newWebhookInvocationHandler(webhookInvocationService, webhookService)
|
||||
eventHandler := newEventHandler(eventService)
|
||||
secretHandler := newSecretHandler(secretService)
|
||||
actionHandler := newActionHandler(actionService)
|
||||
actionInvocationHandler := newActionInvocationHandler(actionService, actionInvocationService)
|
||||
|
||||
infoHandler := newInfoHandler(env.appConfig)
|
||||
healthHandler := newHealthHandler()
|
||||
authHandler := newAuthHandler()
|
||||
|
@ -97,13 +108,38 @@ func Start() {
|
|||
|
||||
apiAuthGroup.GET("/webhooks", webhookHandler.paginate)
|
||||
apiAuthGroup.POST("/webhooks", webhookHandler.create)
|
||||
apiAuthGroup.GET("/webhooks/:id", webhookHandler.get)
|
||||
apiAuthGroup.PATCH("/webhooks/:id/label", webhookHandler.updateLabel)
|
||||
apiAuthGroup.PATCH("/webhooks/:id/ignore-host", webhookHandler.updateIgnoreHost)
|
||||
apiAuthGroup.DELETE("/webhooks/:id", webhookHandler.delete)
|
||||
|
||||
apiAuthGroup.GET("/events", eventHandler.window)
|
||||
apiAuthGroup.GET("/events/:id", eventHandler.get)
|
||||
apiAuthGroup.DELETE("/events/:id", eventHandler.delete)
|
||||
|
||||
apiAuthGroup.GET("/secrets", secretHandler.getAll)
|
||||
apiAuthGroup.GET("/secrets/:id", secretHandler.get)
|
||||
apiAuthGroup.POST("/secrets", secretHandler.create)
|
||||
apiAuthGroup.PATCH("/secrets/:id/value", secretHandler.updateValue)
|
||||
apiAuthGroup.DELETE("/secrets/:id", secretHandler.delete)
|
||||
|
||||
apiAuthGroup.GET("/actions", actionHandler.paginate)
|
||||
apiAuthGroup.POST("/actions", actionHandler.create)
|
||||
apiAuthGroup.GET("/actions/:id", actionHandler.get)
|
||||
apiAuthGroup.PATCH("/actions/:id/label", actionHandler.updateLabel)
|
||||
apiAuthGroup.PATCH("/actions/:id/match-event", actionHandler.updateMatchEvent)
|
||||
apiAuthGroup.PATCH("/actions/:id/match-host", actionHandler.updateMatchHost)
|
||||
apiAuthGroup.PATCH("/actions/:id/match-application", actionHandler.updateMatchApplication)
|
||||
apiAuthGroup.PATCH("/actions/:id/match-provider", actionHandler.updateMatchProvider)
|
||||
apiAuthGroup.PATCH("/actions/:id/payload", actionHandler.updatePayload)
|
||||
apiAuthGroup.PATCH("/actions/:id/enabled", actionHandler.updateEnabled)
|
||||
apiAuthGroup.DELETE("/actions/:id", actionHandler.delete)
|
||||
apiAuthGroup.POST("/actions/:id/test", actionInvocationHandler.test)
|
||||
|
||||
apiAuthGroup.GET("/action-invocations", actionInvocationHandler.paginate)
|
||||
apiAuthGroup.GET("/action-invocations/:id", actionInvocationHandler.get)
|
||||
apiAuthGroup.DELETE("/action-invocations/:id", actionInvocationHandler.delete)
|
||||
|
||||
// start server
|
||||
serverAddress := fmt.Sprintf("%s:%d", env.serverConfig.listen, env.serverConfig.port)
|
||||
srv := &http.Server{
|
||||
|
|
|
@ -2,5 +2,5 @@ package server
|
|||
|
||||
const (
|
||||
Name = "upda"
|
||||
Version = "1.1.0"
|
||||
Version = "2.0.0"
|
||||
)
|
||||
|
|
|
@ -11,6 +11,8 @@ const (
|
|||
envLoggingDirectory = "LOGGING_DIRECTORY"
|
||||
loggingFileNameDefault = "upda.log"
|
||||
|
||||
envSecret = "SECRET"
|
||||
|
||||
envTZ = "TZ"
|
||||
tzDefault = "Europe/Berlin"
|
||||
|
||||
|
@ -70,7 +72,7 @@ const (
|
|||
envTaskUpdateCleanStaleMaxAge = "TASK_UPDATE_CLEAN_STALE_MAX_AGE"
|
||||
taskUpdateCleanStaleEnabledDefault = "false"
|
||||
taskUpdateCleanStaleIntervalDefault = "1h"
|
||||
taskUpdateCleanStaleMaxAgeDefault = "168h"
|
||||
taskUpdateCleanStaleMaxAgeDefault = "720h"
|
||||
|
||||
envTaskEventCleanStaleEnabled = "TASK_EVENT_CLEAN_STALE_ENABLED"
|
||||
envTaskEventCleanStaleInterval = "TASK_EVENT_CLEAN_STALE_INTERVAL"
|
||||
|
@ -79,6 +81,29 @@ const (
|
|||
taskEventCleanStaleIntervalDefault = "8h"
|
||||
taskEventCleanStaleMaxAgeDefault = "2190h"
|
||||
|
||||
envTaskActionsEnqueueEnabled = "TASK_ACTIONS_ENQUEUE_ENABLED"
|
||||
envTaskActionsEnqueueInterval = "TASK_ACTIONS_ENQUEUE_INTERVAL"
|
||||
envTaskActionsEnqueueBatchSize = "TASK_ACTIONS_ENQUEUE_BATCH_SIZE"
|
||||
taskActionsEnqueueEnabledDefault = "true"
|
||||
taskActionsEnqueueIntervalDefault = "10s"
|
||||
taskActionsEnqueueBatchSizeDefault = "1"
|
||||
|
||||
envTaskActionsInvokeEnabled = "TASK_ACTIONS_INVOKE_ENABLED"
|
||||
envTaskActionsInvokeInterval = "TASK_ACTIONS_INVOKE_INTERVAL"
|
||||
envTaskActionsInvokeBatchSize = "TASK_ACTIONS_INVOKE_BATCH_SIZE"
|
||||
envTaskActionsInvokeMaxRetries = "TASK_ACTIONS_INVOKE_MAX_RETRIES"
|
||||
taskActionsInvokeEnabledDefault = "true"
|
||||
taskActionsInvokeIntervalDefault = "10s"
|
||||
taskActionsInvokeBatchSizeDefault = "1"
|
||||
taskActionsInvokeMaxRetriesDefault = "3"
|
||||
|
||||
envTaskActionsCleanStaleEnabled = "TASK_ACTIONS_CLEAN_STALE_ENABLED"
|
||||
envTaskActionsCleanStaleInterval = "TASK_ACTIONS_CLEAN_STALE_INTERVAL"
|
||||
envTaskActionsCleanStaleMaxAge = "TASK_ACTIONS_CLEAN_STALE_MAX_AGE"
|
||||
taskActionsCleanStaleEnabledDefault = "true"
|
||||
taskActionsCleanStaleIntervalDefault = "12h"
|
||||
taskActionsCleanStaleMaxAgeDefault = "720h"
|
||||
|
||||
envLockRedisEnabled = "LOCK_REDIS_ENABLED"
|
||||
envLockRedisUrl = "LOCK_REDIS_URL"
|
||||
redisEnabledDefault = "false"
|
||||
|
|
|
@ -13,12 +13,12 @@ const (
|
|||
metricUpdatesApproved = "updates_approved"
|
||||
metricUpdatesApprovedHelp = "amount of all updates in approved state"
|
||||
|
||||
metricUpdates = "updates"
|
||||
metricUpdatesHelp = "details for all updates, 0=pending, 1=approved, 2=ignored"
|
||||
|
||||
metricWebhooks = "webhooks"
|
||||
metricWebhooksHelp = "amount of all webhooks"
|
||||
|
||||
metricEvents = "events"
|
||||
metricEventsHelp = "amount of all events"
|
||||
|
||||
metricActions = "actions"
|
||||
metricActionsHelp = "amount of all actions"
|
||||
)
|
||||
|
|
15
server/dto.go
Normal file
15
server/dto.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package server
|
||||
|
||||
// DTOs
|
||||
|
||||
type actionPayloadShoutrrrDto struct {
|
||||
Body string `json:"body" binding:"required" validate:"required"`
|
||||
Urls []string `json:"urls" binding:"required" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type eventPayloadInformationDto struct {
|
||||
Host string
|
||||
Application string
|
||||
Provider string
|
||||
Version string
|
||||
}
|
142
server/entity.go
142
server/entity.go
|
@ -1,8 +1,10 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"git.myservermanager.com/varakh/upda/util"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -11,16 +13,6 @@ func (u *Update) BeforeCreate(tx *gorm.DB) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (wh *Webhook) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
wh.ID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Event) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
e.ID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
// Update entity holding information for updates
|
||||
type Update struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
|
@ -34,6 +26,32 @@ type Update struct {
|
|||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
||||
// BeforeCreate encrypts secret value before storing to database
|
||||
func (wh *Webhook) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var encryptedToken string
|
||||
|
||||
if encryptedToken, er = util.EncryptAndEncode(wh.Token, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
wh.ID = uuid.New()
|
||||
wh.Token = encryptedToken
|
||||
return
|
||||
}
|
||||
|
||||
// AfterSave decrypt secret value after encrypted value has been retrieved from database
|
||||
func (wh *Webhook) AfterSave(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var decrypted string
|
||||
if decrypted, er = util.DecryptAndDecode(wh.Token, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
wh.Token = decrypted
|
||||
return
|
||||
}
|
||||
|
||||
// Webhook entity holding information for webhooks
|
||||
type Webhook struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
|
@ -45,6 +63,11 @@ type Webhook struct {
|
|||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
||||
func (e *Event) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
e.ID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
// Event entity holding information for events
|
||||
type Event struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
|
@ -54,3 +77,102 @@ type Event struct {
|
|||
CreatedAt time.Time `gorm:"time;autoCreateTime;not null"`
|
||||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
||||
// BeforeCreate encrypts secret value before storing to database
|
||||
func (e *Secret) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var encryptedValue string
|
||||
|
||||
if encryptedValue, er = util.EncryptAndEncode(e.Value, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
e.ID = uuid.New()
|
||||
e.Value = encryptedValue
|
||||
return
|
||||
}
|
||||
|
||||
// BeforeUpdate encrypts secret value before storing to database
|
||||
func (e *Secret) BeforeUpdate(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var encryptedValue string
|
||||
|
||||
if encryptedValue, er = util.EncryptAndEncode(e.Value, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
e.Value = encryptedValue
|
||||
return
|
||||
}
|
||||
|
||||
// AfterSave decrypt secret value after encrypted value has been retrieved from database
|
||||
func (e *Secret) AfterSave(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var decrypted string
|
||||
if decrypted, er = util.DecryptAndDecode(e.Value, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
e.Value = decrypted
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind decrypt secret value after encrypted value has been retrieved from database
|
||||
func (e *Secret) AfterFind(tx *gorm.DB) (err error) {
|
||||
var er error
|
||||
var decrypted string
|
||||
if decrypted, er = util.DecryptAndDecode(e.Value, os.Getenv(envSecret)); er != nil {
|
||||
return er
|
||||
}
|
||||
|
||||
e.Value = decrypted
|
||||
return
|
||||
}
|
||||
|
||||
// Secret entity holding information for secrets
|
||||
type Secret struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
Key string `gorm:"unique;not null"`
|
||||
Value string `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"time;autoCreateTime;not null"`
|
||||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
||||
func (e *Action) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
e.ID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
// Action entity holding information for actions
|
||||
type Action struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
Label string `gorm:"not null"`
|
||||
Type string `gorm:"not null"`
|
||||
MatchEvent *string `gorm:""`
|
||||
MatchApplication *string `gorm:""`
|
||||
MatchProvider *string `gorm:""`
|
||||
MatchHost *string `gorm:""`
|
||||
Payload JSONMap `gorm:"jsonb"`
|
||||
Enabled bool `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"time;autoCreateTime;not null"`
|
||||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
||||
func (e *ActionInvocation) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
e.ID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
// ActionInvocation entity holding information for invocations of actions
|
||||
type ActionInvocation struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"`
|
||||
RetryCount int `gorm:"not null;default:1"`
|
||||
State string `gorm:"not null"`
|
||||
Message *string
|
||||
Event Event `gorm:"foreignKey:EventID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
EventID string `gorm:"not null"`
|
||||
Action Action `gorm:"foreignKey:ActionID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
ActionID string `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"time;autoCreateTime;not null"`
|
||||
UpdatedAt time.Time `gorm:"time;autoUpdateTime;not null"`
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@ type taskConfig struct {
|
|||
eventCleanStaleEnabled bool
|
||||
eventCleanStaleInterval string
|
||||
eventCleanStaleMaxAge time.Duration
|
||||
actionsEnqueueEnabled bool
|
||||
actionsEnqueueInterval string
|
||||
actionsEnqueueBatchSize int
|
||||
actionsInvokeEnabled bool
|
||||
actionsInvokeInterval string
|
||||
actionsInvokeBatchSize int
|
||||
actionsInvokeMaxRetries int
|
||||
actionsCleanStaleEnabled bool
|
||||
actionsCleanStaleInterval string
|
||||
actionsCleanStaleMaxAge time.Duration
|
||||
prometheusRefreshInterval string
|
||||
}
|
||||
|
||||
|
@ -217,6 +227,35 @@ func bootstrapEnvironment() *Environment {
|
|||
zap.L().Sugar().Fatalf("Could not parse max age for cleaning stale events. Reason: %s", errParse.Error())
|
||||
}
|
||||
|
||||
var actionsEnqueueBatchSize int
|
||||
if actionsEnqueueBatchSize, err = strconv.Atoi(os.Getenv(envTaskActionsEnqueueBatchSize)); err != nil {
|
||||
zap.L().Sugar().Fatalf("Invalid actions enqueue batch size. Reason: %v", err)
|
||||
}
|
||||
if actionsEnqueueBatchSize <= 0 {
|
||||
zap.L().Sugar().Fatalf("Invalid actions enqueue batch size, must be a positive number.")
|
||||
}
|
||||
|
||||
var actionsInvokeBatchSize int
|
||||
if actionsInvokeBatchSize, err = strconv.Atoi(os.Getenv(envTaskActionsInvokeBatchSize)); err != nil {
|
||||
zap.L().Sugar().Fatalf("Invalid actions invoke batch size. Reason: %v", err)
|
||||
}
|
||||
if actionsInvokeBatchSize <= 0 {
|
||||
zap.L().Sugar().Fatalf("Invalid actions invoke batch size, must be a positive number.")
|
||||
}
|
||||
|
||||
var actionsInvokeMaxRetries int
|
||||
if actionsInvokeMaxRetries, err = strconv.Atoi(os.Getenv(envTaskActionsInvokeMaxRetries)); err != nil {
|
||||
zap.L().Sugar().Fatalf("Invalid actions invoke max retries. Reason: %v", err)
|
||||
}
|
||||
if actionsInvokeMaxRetries <= 0 {
|
||||
zap.L().Sugar().Fatalf("Invalid actions invoke max retries, must be a positive number.")
|
||||
}
|
||||
|
||||
var actionsCleanStaleMaxAge time.Duration
|
||||
if actionsCleanStaleMaxAge, errParse = time.ParseDuration(os.Getenv(envTaskActionsCleanStaleMaxAge)); errParse != nil {
|
||||
zap.L().Sugar().Fatalf("Could not parse max age for cleaning stale actions. Reason: %s", errParse.Error())
|
||||
}
|
||||
|
||||
tc = &taskConfig{
|
||||
updateCleanStaleEnabled: os.Getenv(envTaskUpdateCleanStaleEnabled) == "true",
|
||||
updateCleanStaleInterval: os.Getenv(envTaskUpdateCleanStaleInterval),
|
||||
|
@ -224,6 +263,16 @@ func bootstrapEnvironment() *Environment {
|
|||
eventCleanStaleEnabled: os.Getenv(envTaskEventCleanStaleEnabled) == "true",
|
||||
eventCleanStaleInterval: os.Getenv(envTaskEventCleanStaleInterval),
|
||||
eventCleanStaleMaxAge: eventCleanStaleMaxAge,
|
||||
actionsEnqueueEnabled: os.Getenv(envTaskActionsEnqueueEnabled) == "true",
|
||||
actionsEnqueueInterval: os.Getenv(envTaskActionsEnqueueInterval),
|
||||
actionsEnqueueBatchSize: actionsEnqueueBatchSize,
|
||||
actionsInvokeEnabled: os.Getenv(envTaskActionsInvokeEnabled) == "true",
|
||||
actionsInvokeInterval: os.Getenv(envTaskActionsInvokeInterval),
|
||||
actionsInvokeBatchSize: actionsInvokeBatchSize,
|
||||
actionsInvokeMaxRetries: actionsInvokeMaxRetries,
|
||||
actionsCleanStaleEnabled: os.Getenv(envTaskActionsCleanStaleEnabled) == "true",
|
||||
actionsCleanStaleInterval: os.Getenv(envTaskActionsCleanStaleInterval),
|
||||
actionsCleanStaleMaxAge: actionsCleanStaleMaxAge,
|
||||
prometheusRefreshInterval: os.Getenv(envTaskPrometheusRefreshInterval),
|
||||
}
|
||||
|
||||
|
@ -289,7 +338,7 @@ func bootstrapEnvironment() *Environment {
|
|||
}
|
||||
|
||||
if res := db.Exec("PRAGMA foreign_keys = ON"); res.Error != nil {
|
||||
zap.L().Sugar().Fatalf("Could not invoke foreign key for SQLite: %v", res.Error)
|
||||
zap.L().Sugar().Fatalf("Could not execute foreign key for SQLite: %v", res.Error)
|
||||
}
|
||||
|
||||
sqlDb, _ := db.DB()
|
||||
|
@ -328,7 +377,7 @@ func bootstrapEnvironment() *Environment {
|
|||
prometheusConfig: pc,
|
||||
db: db}
|
||||
|
||||
if err = env.db.AutoMigrate(&Update{}, &Webhook{}, &Event{}); err != nil {
|
||||
if err = env.db.AutoMigrate(&Update{}, &Webhook{}, &Event{}, &Secret{}, &Action{}, &ActionInvocation{}); err != nil {
|
||||
zap.L().Sugar().Fatalf("Could not migrate database schema: %s", err)
|
||||
}
|
||||
|
||||
|
@ -344,6 +393,7 @@ func bootstrapFromEnvironmentAndValidate() {
|
|||
// app
|
||||
setEnvKeyDefault(envTZ, tzDefault)
|
||||
|
||||
failIfEnvKeyNotPresent(envSecret)
|
||||
failIfEnvKeyNotPresent(envAdminUser)
|
||||
failIfEnvKeyNotPresent(envAdminPassword)
|
||||
|
||||
|
@ -362,6 +412,19 @@ func bootstrapFromEnvironmentAndValidate() {
|
|||
setEnvKeyDefault(envTaskEventCleanStaleInterval, taskEventCleanStaleIntervalDefault)
|
||||
setEnvKeyDefault(envTaskEventCleanStaleMaxAge, taskEventCleanStaleMaxAgeDefault)
|
||||
|
||||
setEnvKeyDefault(envTaskActionsEnqueueEnabled, taskActionsEnqueueEnabledDefault)
|
||||
setEnvKeyDefault(envTaskActionsEnqueueInterval, taskActionsEnqueueIntervalDefault)
|
||||
setEnvKeyDefault(envTaskActionsEnqueueBatchSize, taskActionsEnqueueBatchSizeDefault)
|
||||
|
||||
setEnvKeyDefault(envTaskActionsInvokeEnabled, taskActionsInvokeEnabledDefault)
|
||||
setEnvKeyDefault(envTaskActionsInvokeInterval, taskActionsInvokeIntervalDefault)
|
||||
setEnvKeyDefault(envTaskActionsInvokeBatchSize, taskActionsInvokeBatchSizeDefault)
|
||||
setEnvKeyDefault(envTaskActionsInvokeMaxRetries, taskActionsInvokeMaxRetriesDefault)
|
||||
|
||||
setEnvKeyDefault(envTaskActionsCleanStaleEnabled, taskActionsCleanStaleEnabledDefault)
|
||||
setEnvKeyDefault(envTaskActionsCleanStaleInterval, taskActionsCleanStaleIntervalDefault)
|
||||
setEnvKeyDefault(envTaskActionsCleanStaleMaxAge, taskActionsCleanStaleMaxAgeDefault)
|
||||
|
||||
setEnvKeyDefault(envTaskPrometheusRefreshInterval, taskPrometheusRefreshDefault)
|
||||
|
||||
// prometheus
|
||||
|
|
|
@ -6,10 +6,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
errorValidationNotEmpty = newServiceError(IllegalArgument, errors.New("assert: empty values are not allowed"))
|
||||
errorValidationNotBlank = newServiceError(IllegalArgument, errors.New("assert: blank values are not allowed"))
|
||||
errorValidationPageGreaterZero = newServiceError(IllegalArgument, errors.New("assert: page has to be greater 0"))
|
||||
errorValidationPageSizeGreaterZero = newServiceError(IllegalArgument, errors.New("assert: pageSize has to be greater 0"))
|
||||
errorValidationNotEmpty = newServiceError(IllegalArgument, errors.New("assert: empty values are not allowed"))
|
||||
errorValidationNotBlank = newServiceError(IllegalArgument, errors.New("assert: blank values are not allowed"))
|
||||
errorValidationPageGreaterZero = newServiceError(IllegalArgument, errors.New("assert: page has to be greater 0"))
|
||||
errorValidationPageSizeGreaterZero = newServiceError(IllegalArgument, errors.New("assert: pageSize has to be greater 0"))
|
||||
errorValidationLimitGreaterZero = newServiceError(IllegalArgument, errors.New("assert: limit has to be greater 0"))
|
||||
errorValidationSizeGreaterZero = newServiceError(IllegalArgument, errors.New("assert: size has to be greater 0"))
|
||||
errorValidationMaxRetriesGreaterZero = newServiceError(IllegalArgument, errors.New("assert: max retries has to be greater 0"))
|
||||
|
||||
errorResourceNotFound = newServiceError(NotFound, errors.New("resource not found"))
|
||||
errorResourceAccessDenied = newServiceError(Forbidden, errors.New("resource access denied"))
|
||||
|
|
375
server/repository_action.go
Normal file
375
server/repository_action.go
Normal file
|
@ -0,0 +1,375 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ActionRepository interface {
|
||||
paginate(page int, pageSize int, orderBy string, order string) ([]*Action, error)
|
||||
count() (int64, error)
|
||||
find(id string) (*Action, error)
|
||||
findByEnabled(enabled bool) ([]*Action, error)
|
||||
findAll() ([]*Action, error)
|
||||
create(label string, t api.ActionType, matchEvent *string, matchHost *string, matchApplication *string, matchProvider *string, payload interface{}, enabled bool) (*Action, error)
|
||||
updateLabel(id string, label string) (*Action, error)
|
||||
updateMatchEvent(id string, matchEvent *string) (*Action, error)
|
||||
updateMatchApplication(id string, matchApplication *string) (*Action, error)
|
||||
updateMatchProvider(id string, matchProvider *string) (*Action, error)
|
||||
updateMatchHost(id string, matchHost *string) (*Action, error)
|
||||
updateTypeAndPayload(id string, t api.ActionType, payload interface{}) (*Action, error)
|
||||
updateEnabled(id string, enabled bool) (*Action, error)
|
||||
delete(id string) (int64, error)
|
||||
}
|
||||
|
||||
type actionDbRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func newActionDbRepo(db *gorm.DB) *actionDbRepo {
|
||||
return &actionDbRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) find(id string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e Action
|
||||
var res *gorm.DB
|
||||
if res = r.db.Find(&e, "id = ?", id); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorResourceNotFound
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) findByEnabled(enabled bool) ([]*Action, error) {
|
||||
var e []*Action
|
||||
|
||||
res := r.db.Find(&e, "enabled = ?", enabled)
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) create(label string, t api.ActionType, matchEvent *string, matchHost *string, matchApplication *string, matchProvider *string, payload interface{}, enabled bool) (*Action, error) {
|
||||
if label == "" || t == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
e := &Action{
|
||||
Label: label,
|
||||
Type: t.Value(),
|
||||
MatchEvent: matchEvent,
|
||||
MatchHost: matchHost,
|
||||
MatchApplication: matchApplication,
|
||||
MatchProvider: matchProvider,
|
||||
Enabled: enabled,
|
||||
}
|
||||
|
||||
if payload != nil {
|
||||
unmarshalledPayload := JSONMap{}
|
||||
marshalledMetadata, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unmarshalledPayload.UnmarshalJSON(marshalledMetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Payload = unmarshalledPayload
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Create(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateLabel(id string, label string) (*Action, error) {
|
||||
if id == "" || label == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.Label = label
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateType(id string, t api.ActionType) (*Action, error) {
|
||||
if id == "" || t == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.Type = t.Value()
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateMatchEvent(id string, matchEvent *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.MatchEvent = matchEvent
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateMatchApplication(id string, matchApplication *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.MatchApplication = matchApplication
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateMatchProvider(id string, matchProvider *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.MatchProvider = matchProvider
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateMatchHost(id string, matchHost *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.MatchHost = matchHost
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateTypeAndPayload(id string, t api.ActionType, payload interface{}) (*Action, error) {
|
||||
if id == "" || t == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
if payload == nil {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmarshalledPayload := JSONMap{}
|
||||
marshalledMetadata, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unmarshalledPayload.UnmarshalJSON(marshalledMetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Payload = unmarshalledPayload
|
||||
e.Type = t.Value()
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) updateEnabled(id string, enabled bool) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.Enabled = enabled
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) delete(id string) (int64, error) {
|
||||
if id == "" {
|
||||
return 0, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Delete(&Action{}, "id = ?", id); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return res.RowsAffected, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) paginate(page int, pageSize int, orderBy string, order string) ([]*Action, error) {
|
||||
if page == 0 {
|
||||
return nil, errorValidationPageGreaterZero
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
return nil, errorValidationPageSizeGreaterZero
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var e []*Action
|
||||
var res *gorm.DB
|
||||
|
||||
if orderBy != "" && order != "" {
|
||||
res = r.db.Order(orderBy + " " + order).Offset(offset).Limit(pageSize).Find(&e)
|
||||
} else {
|
||||
res = r.db.Offset(offset).Limit(pageSize).Find(&e)
|
||||
}
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) count() (int64, error) {
|
||||
var c int64
|
||||
var res *gorm.DB
|
||||
|
||||
if res = r.db.Model(&Action{}).Count(&c); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (r *actionDbRepo) findAll() ([]*Action, error) {
|
||||
var e []*Action
|
||||
|
||||
if res := r.db.Model(&Action{}).Order("updated_at desc").Find(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
265
server/repository_action_invocation.go
Normal file
265
server/repository_action_invocation.go
Normal file
|
@ -0,0 +1,265 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ActionInvocationRepository interface {
|
||||
paginate(page int, pageSize int, orderBy string, order string) ([]*ActionInvocation, error)
|
||||
count() (int64, error)
|
||||
find(id string) (*ActionInvocation, error)
|
||||
findAllByState(limit int, maxRetries int, state ...api.ActionInvocationState) ([]*ActionInvocation, error)
|
||||
create(eventId string, actionId string, state api.ActionInvocationState) (*ActionInvocation, error)
|
||||
updateState(id string, state api.ActionInvocationState) (*ActionInvocation, error)
|
||||
updateMessage(id string, message *string) (*ActionInvocation, error)
|
||||
updateRetryCount(id string, retryCount int) (*ActionInvocation, error)
|
||||
delete(id string) (int64, error)
|
||||
deleteByUpdatedAtBeforeAndStates(time time.Time, retryCount int, state ...api.ActionInvocationState) (int64, error)
|
||||
}
|
||||
|
||||
type actionInvocationDbRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func newActionInvocationDbRepo(db *gorm.DB) *actionInvocationDbRepo {
|
||||
return &actionInvocationDbRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) find(id string) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e ActionInvocation
|
||||
var res *gorm.DB
|
||||
if res = r.db.Find(&e, "id = ?", id); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorResourceNotFound
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) create(eventId string, actionId string, state api.ActionInvocationState) (*ActionInvocation, error) {
|
||||
if eventId == "" || actionId == "" || state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
e := &ActionInvocation{
|
||||
EventID: eventId,
|
||||
ActionID: actionId,
|
||||
State: state.Value(),
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Create(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) updateRetryCount(id string, retryCount int) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *ActionInvocation
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.RetryCount = retryCount
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) updateState(id string, state api.ActionInvocationState) (*ActionInvocation, error) {
|
||||
if id == "" || state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *ActionInvocation
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.State = state.Value()
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) updateMessage(id string, message *string) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *ActionInvocation
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.Message = message
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) delete(id string) (int64, error) {
|
||||
if id == "" {
|
||||
return 0, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Delete(&ActionInvocation{}, "id = ?", id); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return res.RowsAffected, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) paginate(page int, pageSize int, orderBy string, order string) ([]*ActionInvocation, error) {
|
||||
if page == 0 {
|
||||
return nil, errorValidationPageGreaterZero
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
return nil, errorValidationPageSizeGreaterZero
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var e []*ActionInvocation
|
||||
var res *gorm.DB
|
||||
|
||||
if orderBy != "" && order != "" {
|
||||
res = r.db.Order(orderBy + " " + order).Offset(offset).Limit(pageSize).Find(&e)
|
||||
} else {
|
||||
res = r.db.Offset(offset).Limit(pageSize).Find(&e)
|
||||
}
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) count() (int64, error) {
|
||||
var c int64
|
||||
var res *gorm.DB
|
||||
|
||||
if res = r.db.Model(&ActionInvocation{}).Count(&c); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) findAllByState(limit int, maxRetries int, state ...api.ActionInvocationState) ([]*ActionInvocation, error) {
|
||||
if limit <= 0 {
|
||||
return nil, errorValidationLimitGreaterZero
|
||||
}
|
||||
|
||||
var e []*ActionInvocation
|
||||
|
||||
states := translateActionInvocationState(state...)
|
||||
|
||||
if res := r.db.Model(&ActionInvocation{}).Scopes(allGetActionInvocationCriterion(states, maxRetries)).Order("created_at asc").Limit(limit).Find(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *actionInvocationDbRepo) deleteByUpdatedAtBeforeAndStates(time time.Time, maxRetries int, state ...api.ActionInvocationState) (int64, error) {
|
||||
if len(state) == 0 {
|
||||
return 0, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
states := translateActionInvocationState(state...)
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Where("retry_count >= ?", maxRetries).Where("state IN ?", states).Where("updated_at < ?", time).Delete(&ActionInvocation{}); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return res.RowsAffected, nil
|
||||
}
|
||||
|
||||
func translateActionInvocationState(state ...api.ActionInvocationState) []string {
|
||||
states := make([]string, 0)
|
||||
if len(state) > 0 {
|
||||
for _, s := range state {
|
||||
states = append(states, s.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return states
|
||||
}
|
||||
|
||||
func criterionActonInvocationMaxRetries(maxRetries int) func(db *gorm.DB) *gorm.DB {
|
||||
if maxRetries > 0 {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("retry_count < ? ", maxRetries)
|
||||
}
|
||||
}
|
||||
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func criterionActionInvocationState(states []string) func(db *gorm.DB) *gorm.DB {
|
||||
if states != nil && len(states) > 0 {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("state IN (?)", states)
|
||||
}
|
||||
}
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func allGetActionInvocationCriterion(states []string, maxRetries int) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Scopes(criterionActionInvocationState(states), criterionActonInvocationMaxRetries(maxRetries))
|
||||
}
|
||||
}
|
|
@ -12,7 +12,9 @@ type eventRepository interface {
|
|||
window(size int, skip int, orderBy string, order string) ([]*Event, error)
|
||||
windowHasNext(size int, skip int, orderBy string, order string) (bool, error)
|
||||
count(state ...api.EventState) (int64, error)
|
||||
findAllByState(limit int, state ...api.EventState) ([]*Event, error)
|
||||
create(name api.EventName, state api.EventState, payload interface{}) (*Event, error)
|
||||
updateState(id string, state api.EventState) (*Event, error)
|
||||
delete(id string) (int64, error)
|
||||
deleteByUpdatedAtBeforeAndStates(time time.Time, state ...api.EventState) (int64, error)
|
||||
}
|
||||
|
@ -81,6 +83,31 @@ func (r *eventDbRepo) create(name api.EventName, state api.EventState, payload i
|
|||
return e, nil
|
||||
}
|
||||
|
||||
func (r *eventDbRepo) updateState(id string, state api.EventState) (*Event, error) {
|
||||
if id == "" || state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Event
|
||||
|
||||
if e, err = r.find(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.State = state.Value()
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *eventDbRepo) delete(id string) (int64, error) {
|
||||
if id == "" {
|
||||
return 0, errorValidationNotBlank
|
||||
|
@ -112,8 +139,9 @@ func (r *eventDbRepo) deleteByUpdatedAtBeforeAndStates(time time.Time, state ...
|
|||
}
|
||||
|
||||
func (r *eventDbRepo) window(size int, skip int, orderBy string, order string) ([]*Event, error) {
|
||||
var e []*Event
|
||||
|
||||
if size <= 0 {
|
||||
return nil, errorValidationSizeGreaterZero
|
||||
}
|
||||
if orderBy == "" {
|
||||
orderBy = "created_at"
|
||||
}
|
||||
|
@ -121,6 +149,7 @@ func (r *eventDbRepo) window(size int, skip int, orderBy string, order string) (
|
|||
order = "asc"
|
||||
}
|
||||
|
||||
var e []*Event
|
||||
if res := r.db.Order(orderBy + " " + order).Offset(skip).Limit(size).Find(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
@ -145,9 +174,37 @@ func (r *eventDbRepo) windowHasNext(size int, skip int, orderBy string, order st
|
|||
return len(e) > 0, nil
|
||||
}
|
||||
|
||||
func (r *eventDbRepo) findAllByState(limit int, state ...api.EventState) ([]*Event, error) {
|
||||
if len(state) == 0 {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
if limit <= 0 {
|
||||
return nil, errorValidationLimitGreaterZero
|
||||
}
|
||||
|
||||
var e []*Event
|
||||
|
||||
states := translateEventState(state...)
|
||||
|
||||
if res := r.db.Model(&Event{}).Scopes(allGetEventCriterion(states)).Order("created_at asc").Limit(limit).Find(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *eventDbRepo) count(state ...api.EventState) (int64, error) {
|
||||
var c int64
|
||||
|
||||
states := translateEventState(state...)
|
||||
if res := r.db.Model(&Event{}).Scopes(allGetEventCriterion(states)).Count(&c); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func translateEventState(state ...api.EventState) []string {
|
||||
states := make([]string, 0)
|
||||
if len(state) > 0 {
|
||||
for _, s := range state {
|
||||
|
@ -155,11 +212,7 @@ func (r *eventDbRepo) count(state ...api.EventState) (int64, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if res := r.db.Model(&Event{}).Scopes(allGetEventCriterion(states)).Count(&c); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return states
|
||||
}
|
||||
|
||||
func criterionEventState(states []string) func(db *gorm.DB) *gorm.DB {
|
||||
|
|
131
server/repository_secret.go
Normal file
131
server/repository_secret.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type secretRepository interface {
|
||||
findAll() ([]*Secret, error)
|
||||
findById(id string) (*Secret, error)
|
||||
findByKey(key string) (*Secret, error)
|
||||
create(key string, value string) (*Secret, error)
|
||||
update(id string, value string) (*Secret, error)
|
||||
delete(id string) (int64, error)
|
||||
}
|
||||
|
||||
type secretDbRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func newSecretDbRepo(db *gorm.DB) *secretDbRepo {
|
||||
return &secretDbRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) findAll() ([]*Secret, error) {
|
||||
var e []*Secret
|
||||
var res *gorm.DB
|
||||
|
||||
if res = r.db.Order("key asc").Find(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) findById(id string) (*Secret, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e Secret
|
||||
var res *gorm.DB
|
||||
|
||||
if res = r.db.Find(&e, "id = ?", id); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorResourceNotFound
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) findByKey(key string) (*Secret, error) {
|
||||
if key == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e Secret
|
||||
var res *gorm.DB
|
||||
|
||||
if res = r.db.Find(&e, "key = ?", key); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorResourceNotFound
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) create(key string, value string) (*Secret, error) {
|
||||
if key == "" || value == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Secret
|
||||
|
||||
e = &Secret{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Create(&e); res.Error != nil {
|
||||
return nil, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) update(id string, value string) (*Secret, error) {
|
||||
if id == "" || value == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Secret
|
||||
|
||||
if e, err = r.findById(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.Value = value
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Save(&e); res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return e, errorDatabaseRowsExpected
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (r *secretDbRepo) delete(id string) (int64, error) {
|
||||
if id == "" {
|
||||
return 0, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var res *gorm.DB
|
||||
if res = r.db.Delete(&Secret{}, "id = ?", id); res.Error != nil {
|
||||
return 0, newServiceDatabaseError(res.Error)
|
||||
}
|
||||
return res.RowsAffected, nil
|
||||
}
|
|
@ -213,7 +213,7 @@ func (r *updateDbRepo) deleteByUpdatedAtBeforeAndStates(time time.Time, state ..
|
|||
}
|
||||
|
||||
func (r *updateDbRepo) paginate(page int, pageSize int, orderBy string, order string, searchTerm string, searchIn string, state ...api.UpdateState) ([]*Update, error) {
|
||||
if page == 0 || pageSize <= 0 {
|
||||
if page == 0 {
|
||||
return nil, errorValidationPageGreaterZero
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
|
|
|
@ -129,7 +129,7 @@ func (r *webhookDbRepo) delete(id string) (int64, error) {
|
|||
}
|
||||
|
||||
func (r *webhookDbRepo) paginate(page int, pageSize int, orderBy string, order string) ([]*Webhook, error) {
|
||||
if page == 0 || pageSize <= 0 {
|
||||
if page == 0 {
|
||||
return nil, errorValidationPageGreaterZero
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
|
|
264
server/service_action.go
Normal file
264
server/service_action.go
Normal file
|
@ -0,0 +1,264 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type actionService struct {
|
||||
repo ActionRepository
|
||||
eventService *eventService
|
||||
}
|
||||
|
||||
func newActionService(r ActionRepository, e *eventService) *actionService {
|
||||
return &actionService{
|
||||
repo: r,
|
||||
eventService: e,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *actionService) get(id string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
e, err := s.repo.find(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) create(label string, t api.ActionType, matchEvent *string, matchHost *string, matchApplication *string, matchProvider *string, payload interface{}, enabled bool) (*Action, error) {
|
||||
if label == "" || t == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
if isValid, validationErr := s.isValidPayload(t, payload); !isValid {
|
||||
return nil, newServiceError(IllegalArgument, validationErr)
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Action
|
||||
if e, err = s.repo.create(label, t, matchEvent, matchHost, matchApplication, matchProvider, payload, enabled); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
zap.L().Sugar().Info("Created action")
|
||||
return e, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *actionService) isValidPayload(t api.ActionType, payload interface{}) (bool, error) {
|
||||
if t == "" {
|
||||
return false, errorValidationNotBlank
|
||||
}
|
||||
if payload == nil {
|
||||
return false, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var err error
|
||||
if api.ActionTypeShoutrrr == t {
|
||||
var pb []byte
|
||||
if pb, err = json.Marshal(payload); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var p actionPayloadShoutrrrDto
|
||||
if err = json.Unmarshal(pb, &p); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
valid := validator.New()
|
||||
if err = valid.Struct(p); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateLabel(id string, label string) (*Action, error) {
|
||||
if id == "" || label == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateLabel(id, label); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateMatchEvent(id string, matchEvent *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateMatchEvent(id, matchEvent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateMatchApplication(id string, matchApplication *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateMatchApplication(id, matchApplication); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateMatchProvider(id string, matchProvider *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateMatchProvider(id, matchProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateMatchHost(id string, matchHost *string) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateMatchHost(id, matchHost); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateTypeAndPayload(id string, t api.ActionType, payload interface{}) (*Action, error) {
|
||||
if id == "" || t == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
if payload == nil {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isValid, validationErr := s.isValidPayload(t, payload); !isValid {
|
||||
return nil, newServiceError(IllegalArgument, validationErr)
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateTypeAndPayload(id, t, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) updateEnabled(id string, enabled bool) (*Action, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Action
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateEnabled(id, enabled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionService) delete(id string) error {
|
||||
if id == "" {
|
||||
return errorValidationNotBlank
|
||||
}
|
||||
|
||||
e, err := s.get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = s.repo.delete(e.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Deleted action '%v'", id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionService) paginate(page int, pageSize int, orderBy string, order string) ([]*Action, error) {
|
||||
return s.repo.paginate(page, pageSize, orderBy, order)
|
||||
}
|
||||
|
||||
func (s *actionService) count() (int64, error) {
|
||||
return s.repo.count()
|
||||
}
|
||||
|
||||
func (s *actionService) getAll() ([]*Action, error) {
|
||||
return s.repo.findAll()
|
||||
}
|
||||
|
||||
func (s *actionService) getByEnabled(enabled bool) ([]*Action, error) {
|
||||
return s.repo.findByEnabled(enabled)
|
||||
}
|
402
server/service_action_invocation.go
Normal file
402
server/service_action_invocation.go
Normal file
|
@ -0,0 +1,402 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"git.myservermanager.com/varakh/upda/util"
|
||||
"github.com/containrrr/shoutrrr"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type actionInvocationService struct {
|
||||
repo ActionInvocationRepository
|
||||
actionService *actionService
|
||||
eventService *eventService
|
||||
secretService *secretService
|
||||
}
|
||||
|
||||
func newActionInvocationService(r ActionInvocationRepository, a *actionService, e *eventService, s *secretService) *actionInvocationService {
|
||||
return &actionInvocationService{
|
||||
repo: r,
|
||||
actionService: a,
|
||||
eventService: e,
|
||||
secretService: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) enqueue(batchSize int) error {
|
||||
if batchSize <= 0 {
|
||||
return newServiceError(General, errors.New("cannot enqueue actions from events with invalid configured batch size"))
|
||||
}
|
||||
|
||||
var events []*Event
|
||||
var err error
|
||||
|
||||
if events, err = s.eventService.getByState(batchSize, api.EventStateCreated); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var actions []*Action
|
||||
if actions, err = s.actionService.getByEnabled(true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
if err = s.enqueueFromEvent(event, actions); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not enqueue action for event '%s' (%s). Reason: %s", event.Name, event.ID, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) enqueueFromEvent(event *Event, actions []*Action) error {
|
||||
if event == nil || actions == nil {
|
||||
return newServiceError(IllegalArgument, errorValidationNotEmpty)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// match requires event payload
|
||||
var eventPayload *eventPayloadInformationDto
|
||||
if eventPayload, err = s.eventService.extractPayloadInfo(event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var filteredActions []*Action
|
||||
for _, action := range actions {
|
||||
matchesEvent := action.MatchEvent == nil || *action.MatchEvent == event.Name
|
||||
matchesHost := action.MatchHost == nil || *action.MatchHost == eventPayload.Host
|
||||
matchesApplication := action.MatchApplication == nil || *action.MatchApplication == eventPayload.Application
|
||||
matchesProvider := action.MatchProvider == nil || *action.MatchProvider == eventPayload.Provider
|
||||
|
||||
if matchesEvent && matchesHost && matchesApplication && matchesProvider {
|
||||
filteredActions = append(filteredActions, action)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredActions) == 0 {
|
||||
zap.L().Sugar().Debugf("No actions found which match event '%s', nothing to enqueue", event.Name)
|
||||
}
|
||||
|
||||
for _, action := range filteredActions {
|
||||
if _, err = s.create(event, action, api.ActionInvocationStateCreated); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not enqueue action '%s' (%v). Reason: %s", action.Label, action.ID, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// mark event as enqueued
|
||||
if _, err = s.eventService.updateState(event.ID.String(), api.EventStateEnqueued); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) invoke(batchSize int, maxRetries int) error {
|
||||
if batchSize <= 0 {
|
||||
return newServiceError(General, errors.New("cannot invoke actions with invalid configured batch size"))
|
||||
}
|
||||
if maxRetries <= 0 {
|
||||
return newServiceError(General, errors.New("cannot invoke actions with invalid configured max retries"))
|
||||
}
|
||||
|
||||
var err error
|
||||
var actionInvocations []*ActionInvocation
|
||||
|
||||
if actionInvocations, err = s.getByState(batchSize, maxRetries, api.ActionInvocationStateCreated, api.ActionInvocationStateRetrying); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(actionInvocations) == 0 {
|
||||
zap.L().Sugar().Debugf("No action invocations found to process")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, actionInvocation := range actionInvocations {
|
||||
if _, err = s.updateState(actionInvocation.ID.String(), api.ActionInvocationStateRunning); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not mark action invocation '%v' as running. Reason: %s", actionInvocation.ID, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
zap.L().Sugar().Debugf("Invoking action '%v' for event '%v'", actionInvocation.ActionID, actionInvocation.EventID)
|
||||
|
||||
var event *Event
|
||||
if event, err = s.eventService.get(actionInvocation.EventID); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not find event '%v' for action '%v' and action invocation '%v'. Reason: %s", actionInvocation.EventID, actionInvocation.ActionID, actionInvocation.ID, err.Error())
|
||||
// with cascade, cannot happen
|
||||
continue
|
||||
}
|
||||
|
||||
var eventPayload *eventPayloadInformationDto
|
||||
if eventPayload, err = s.eventService.extractPayloadInfo(event); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not extract event's '%v' information for action '%v' and action invocation '%v'. Reason: %s", actionInvocation.EventID, actionInvocation.ActionID, actionInvocation.ID, err.Error())
|
||||
// with layout of attached payload, cannot happen
|
||||
continue
|
||||
}
|
||||
|
||||
var action *Action
|
||||
if action, err = s.actionService.get(actionInvocation.ActionID); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not find action '%v' for action invocation '%v'. Reason: %s", actionInvocation.ActionID, actionInvocation.ID, err.Error())
|
||||
// with cascade, cannot happen
|
||||
continue
|
||||
}
|
||||
|
||||
if err = s.execute(action, eventPayload); err != nil {
|
||||
var cause error
|
||||
cause = err
|
||||
|
||||
zap.L().Sugar().Errorf("Could not invoke action '%s' (%v) for action invocation '%v'. Reason: %s", action.Label, action.ID, actionInvocation.ID, err.Error())
|
||||
|
||||
var newState api.ActionInvocationState
|
||||
newRetryCount := actionInvocation.RetryCount + 1
|
||||
newState = api.ActionInvocationStateRetrying
|
||||
|
||||
if newRetryCount >= maxRetries {
|
||||
zap.L().Sugar().Infof("Action invocation '%v' exceeded max retry count of '%d'. Not trying again.", actionInvocation.ID, newRetryCount)
|
||||
newState = api.ActionInvocationStateError
|
||||
}
|
||||
|
||||
if _, err = s.updateState(actionInvocation.ID.String(), newState); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not mark action invocation '%v' as '%v'. Reason: %s", actionInvocation.ID, newState, err.Error())
|
||||
}
|
||||
|
||||
if _, err = s.updateRetryCount(actionInvocation.ID.String(), newRetryCount); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not update action invocation '%v' retry count to '%d'. Reason: %s", actionInvocation.ID, newRetryCount, err.Error())
|
||||
}
|
||||
|
||||
msg := cause.Error()
|
||||
if _, err = s.updateMessage(actionInvocation.ID.String(), &msg); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not update action invocation '%v' message. Reason: %s", actionInvocation.ID, err.Error())
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
zap.L().Sugar().Debugf("Processed action invocation '%v' for event '%s' (%v) and action '%s' (%v)", actionInvocation.ID, event.Name, event.ID, action.Label, action.ID)
|
||||
if _, err = s.updateState(actionInvocation.ID.String(), api.ActionInvocationStateSuccess); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not mark action invocation '%v' as success. Reason: %s", actionInvocation.ID, err.Error())
|
||||
}
|
||||
if _, err = s.updateMessage(actionInvocation.ID.String(), nil); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not update action invocation '%v' message. Reason: %s", actionInvocation.ID, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) execute(action *Action, eventPayloadInfo *eventPayloadInformationDto) error {
|
||||
if action == nil || eventPayloadInfo == nil {
|
||||
return errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var err error
|
||||
var bytes []byte
|
||||
|
||||
if bytes, err = action.Payload.MarshalJSON(); err != nil {
|
||||
return newServiceError(General, err)
|
||||
}
|
||||
|
||||
switch action.Type {
|
||||
case api.ActionTypeShoutrrr.Value():
|
||||
var payload actionPayloadShoutrrrDto
|
||||
if payload, err = util.UnmarshalGenericJSON[actionPayloadShoutrrrDto](bytes); err != nil {
|
||||
return newServiceError(General, err)
|
||||
}
|
||||
|
||||
body := s.replaceVars(payload.Body, eventPayloadInfo)
|
||||
body = s.replaceSecrets(body)
|
||||
|
||||
for _, url := range payload.Urls {
|
||||
url = s.replaceSecrets(url)
|
||||
url = s.replaceVars(url, eventPayloadInfo)
|
||||
if err = shoutrrr.Send(url, body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
return newServiceError(General, errors.New("no matching action type found for invocation"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) replaceSecrets(str string) string {
|
||||
if str == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
var matches [][]string
|
||||
|
||||
matches = util.ExtractBetween(str, "<SECRET>", "</SECRET>")
|
||||
var err error
|
||||
|
||||
for _, match := range matches {
|
||||
var val string
|
||||
if val, err = s.secretService.getValueByKey(match[1]); err != nil {
|
||||
zap.L().Sugar().Warnf("Could not inject secret '%s'. Reason: %s", match[1], err.Error())
|
||||
continue
|
||||
}
|
||||
str = strings.ReplaceAll(str, match[0], val)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) replaceVars(str string, eventPayloadInfo *eventPayloadInformationDto) string {
|
||||
if str == "" || eventPayloadInfo == nil {
|
||||
return str
|
||||
}
|
||||
|
||||
str = strings.ReplaceAll(str, "<VAR>APPLICATION</VAR>", eventPayloadInfo.Application)
|
||||
str = strings.ReplaceAll(str, "<VAR>PROVIDER</VAR>", eventPayloadInfo.Provider)
|
||||
str = strings.ReplaceAll(str, "<VAR>HOST</VAR>", eventPayloadInfo.Host)
|
||||
str = strings.ReplaceAll(str, "<VAR>VERSION</VAR>", eventPayloadInfo.Version)
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) paginate(page int, pageSize int, orderBy string, order string) ([]*ActionInvocation, error) {
|
||||
return s.repo.paginate(page, pageSize, orderBy, order)
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) get(id string) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
e, err := s.repo.find(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) getByState(limit int, maxRetries int, state ...api.ActionInvocationState) ([]*ActionInvocation, error) {
|
||||
if len(state) == 0 {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
if limit <= 0 {
|
||||
return nil, errorValidationLimitGreaterZero
|
||||
}
|
||||
if maxRetries <= 0 {
|
||||
return nil, errorValidationMaxRetriesGreaterZero
|
||||
}
|
||||
|
||||
return s.repo.findAllByState(limit, maxRetries, state...)
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) count() (int64, error) {
|
||||
return s.repo.count()
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) delete(id string) error {
|
||||
if id == "" {
|
||||
return errorValidationNotBlank
|
||||
}
|
||||
|
||||
e, err := s.get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = s.repo.delete(e.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Deleted action '%v'", id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) updateState(id string, state api.ActionInvocationState) (*ActionInvocation, error) {
|
||||
if id == "" || state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *ActionInvocation
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateState(id, state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action invocation '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) updateMessage(id string, message *string) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *ActionInvocation
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateMessage(id, message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action invocation '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) updateRetryCount(id string, retryCount int) (*ActionInvocation, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *ActionInvocation
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateRetryCount(id, retryCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified action invocation '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) create(event *Event, action *Action, state api.ActionInvocationState) (*ActionInvocation, error) {
|
||||
if state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
if action == nil || event == nil {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *ActionInvocation
|
||||
if e, err = s.repo.create(event.ID.String(), action.ID.String(), state); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
zap.L().Sugar().Info("Created action invocation")
|
||||
return e, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *actionInvocationService) cleanStale(time time.Time, maxRetries int, state ...api.ActionInvocationState) (int64, error) {
|
||||
if len(state) == 0 {
|
||||
return 0, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
return s.repo.deleteByUpdatedAtBeforeAndStates(time, maxRetries, state...)
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.myservermanager.com/varakh/upda/api"
|
||||
"git.myservermanager.com/varakh/upda/util"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
@ -85,60 +87,6 @@ func (s *eventService) createUpdateDeleted(e *Update) *Event {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *eventService) createWebhookCreated(e *Webhook) *Event {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.createWithWarnOnly(api.EventNameWebhookCreated, &api.EventPayloadWebhookCreatedDto{
|
||||
ID: e.ID,
|
||||
Label: e.Label,
|
||||
Type: e.Type,
|
||||
IgnoreHost: e.IgnoreHost,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *eventService) createWebhookUpdated(old *Webhook, new *Webhook) *Event {
|
||||
if old == nil || new == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var eventName api.EventName
|
||||
|
||||
if old.Label == new.Label {
|
||||
eventName = api.EventNameWebhookUpdatedIgnoreHost
|
||||
} else {
|
||||
eventName = api.EventNameWebhookUpdatedLabel
|
||||
}
|
||||
|
||||
s.createWithWarnOnly(eventName, &api.EventPayloadWebhookUpdatedDto{
|
||||
ID: new.ID,
|
||||
LabelPrior: old.Label,
|
||||
Label: new.Label,
|
||||
IgnoreHostPrior: old.IgnoreHost,
|
||||
IgnoreHost: new.IgnoreHost,
|
||||
Type: new.Type,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *eventService) createWebhookDeleted(e *Webhook) *Event {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.createWithWarnOnly(api.EventNameWebhookDeleted, &api.EventPayloadWebhookDeletedDto{
|
||||
Label: e.Label,
|
||||
Type: e.Type,
|
||||
IgnoreHost: e.IgnoreHost,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *eventService) createWithWarnOnly(name api.EventName, payload interface{}) *Event {
|
||||
var e *Event
|
||||
var err error
|
||||
|
@ -212,3 +160,88 @@ func (s *eventService) windowHasNext(size int, skip int, orderBy string, order s
|
|||
func (s *eventService) count(state ...api.EventState) (int64, error) {
|
||||
return s.repo.count(state...)
|
||||
}
|
||||
|
||||
func (s *eventService) getByState(limit int, state ...api.EventState) ([]*Event, error) {
|
||||
if len(state) == 0 {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
if limit <= 0 {
|
||||
return nil, errorValidationLimitGreaterZero
|
||||
}
|
||||
|
||||
return s.repo.findAllByState(limit, state...)
|
||||
}
|
||||
|
||||
func (s *eventService) updateState(id string, state api.EventState) (*Event, error) {
|
||||
if id == "" || state == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Event
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.updateState(id, state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified event '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *eventService) extractPayloadInfo(event *Event) (*eventPayloadInformationDto, error) {
|
||||
if event == nil {
|
||||
return nil, errorValidationNotEmpty
|
||||
}
|
||||
|
||||
var err error
|
||||
var bytes []byte
|
||||
|
||||
if bytes, err = event.Payload.MarshalJSON(); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
|
||||
switch event.Name {
|
||||
case api.EventNameUpdateCreated.Value():
|
||||
var p api.EventPayloadUpdateCreatedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateCreatedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
case api.EventNameUpdateDeleted.Value():
|
||||
var p api.EventPayloadUpdateDeletedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateDeletedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
case api.EventNameUpdateUpdatedApproved.Value():
|
||||
var p api.EventPayloadUpdateUpdatedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
case api.EventNameUpdateUpdatedPending.Value():
|
||||
var p api.EventPayloadUpdateUpdatedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
case api.EventNameUpdateUpdatedIgnored.Value():
|
||||
var p api.EventPayloadUpdateUpdatedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
case api.EventNameUpdateUpdated.Value():
|
||||
var p api.EventPayloadUpdateUpdatedDto
|
||||
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
|
||||
return nil, newServiceError(General, err)
|
||||
}
|
||||
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version}, nil
|
||||
}
|
||||
|
||||
return nil, newServiceError(General, errors.New("no matching event found"))
|
||||
}
|
||||
|
|
|
@ -53,10 +53,10 @@ func (s *prometheusService) init() {
|
|||
err = s.registerGaugeNoLabels(metricUpdatesPending, metricUpdatesPendingHelp)
|
||||
err = s.registerGaugeNoLabels(metricUpdatesIgnored, metricUpdatesIgnoredHelp)
|
||||
err = s.registerGaugeNoLabels(metricUpdatesApproved, metricUpdatesApprovedHelp)
|
||||
err = s.registerGauge(metricUpdates, metricUpdatesHelp, []string{"application", "provider", "host"})
|
||||
|
||||
err = s.registerGaugeNoLabels(metricWebhooks, metricWebhooksHelp)
|
||||
err = s.registerGaugeNoLabels(metricEvents, metricEventsHelp)
|
||||
err = s.registerGaugeNoLabels(metricActions, metricActionsHelp)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Sugar().Fatalf("Cannot initialize service. Reason: %v", err)
|
||||
|
|
108
server/service_secret.go
Normal file
108
server/service_secret.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type secretService struct {
|
||||
repo secretRepository
|
||||
}
|
||||
|
||||
func newSecretService(r secretRepository) *secretService {
|
||||
return &secretService{
|
||||
repo: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secretService) get(id string) (*Secret, error) {
|
||||
if id == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
return s.repo.findById(id)
|
||||
}
|
||||
|
||||
func (s *secretService) getValueByKey(key string) (string, error) {
|
||||
if key == "" {
|
||||
return "", errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
var e *Secret
|
||||
|
||||
if e, err = s.repo.findByKey(key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return e.Value, nil
|
||||
}
|
||||
|
||||
func (s *secretService) getAll() ([]*Secret, error) {
|
||||
return s.repo.findAll()
|
||||
}
|
||||
|
||||
func (s *secretService) upsert(key string, value string) (*Secret, error) {
|
||||
if key == "" || value == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Secret
|
||||
var err error
|
||||
|
||||
e, err = s.repo.findByKey(key)
|
||||
|
||||
if err != nil && !errors.Is(err, errorResourceNotFound) {
|
||||
return nil, err
|
||||
} else if err != nil && errors.Is(err, errorResourceNotFound) {
|
||||
if e, err = s.repo.create(key, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zap.L().Sugar().Infof("Created secret '%s'", e.Key)
|
||||
} else {
|
||||
if e, err = s.repo.update(e.ID.String(), value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zap.L().Sugar().Infof("Updated secret '%s'", e.Key)
|
||||
}
|
||||
|
||||
return e, err
|
||||
}
|
||||
|
||||
func (s *secretService) updateValue(id string, value string) (*Secret, error) {
|
||||
if id == "" || value == "" {
|
||||
return nil, errorValidationNotBlank
|
||||
}
|
||||
|
||||
var e *Secret
|
||||
var err error
|
||||
|
||||
if e, err = s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, err = s.repo.update(id, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Modified secret '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *secretService) delete(id string) error {
|
||||
if id == "" {
|
||||
return errorValidationNotBlank
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, err = s.get(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = s.repo.delete(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Deleted secret '%v'", id)
|
||||
return nil
|
||||
}
|
|
@ -10,25 +10,34 @@ import (
|
|||
)
|
||||
|
||||
type taskService struct {
|
||||
updateService *updateService
|
||||
eventService *eventService
|
||||
webhookService *webhookService
|
||||
lockService lockService
|
||||
prometheusService *prometheusService
|
||||
appConfig *appConfig
|
||||
taskConfig *taskConfig
|
||||
lockConfig *lockConfig
|
||||
prometheusConfig *prometheusConfig
|
||||
scheduler *gocron.Scheduler
|
||||
updateService *updateService
|
||||
eventService *eventService
|
||||
actionService *actionService
|
||||
actionInvocationService *actionInvocationService
|
||||
webhookService *webhookService
|
||||
lockService lockService
|
||||
prometheusService *prometheusService
|
||||
appConfig *appConfig
|
||||
taskConfig *taskConfig
|
||||
lockConfig *lockConfig
|
||||
prometheusConfig *prometheusConfig
|
||||
scheduler *gocron.Scheduler
|
||||
}
|
||||
|
||||
const (
|
||||
taskLockNameUpdatesCleanStale = "updates_clean_stale"
|
||||
taskLockNameEventsCleanStale = "events_clean_stale"
|
||||
taskLockNameActionsEnqueue = "actions_enqueue"
|
||||
taskLockNameActionsInvoke = "actions_invoke"
|
||||
taskLockNameActionsCleanStale = "actions_clean_stale"
|
||||
taskLockNamePrometheusUpdate = "prometheus_update"
|
||||
)
|
||||
|
||||
func newTaskService(u *updateService, e *eventService, w *webhookService, l lockService, p *prometheusService, ac *appConfig, tc *taskConfig, lc *lockConfig, pc *prometheusConfig) *taskService {
|
||||
var (
|
||||
initialTasksStartDelay = time.Now().Add(10 * time.Second)
|
||||
)
|
||||
|
||||
func newTaskService(u *updateService, e *eventService, w *webhookService, a *actionService, ai *actionInvocationService, l lockService, p *prometheusService, ac *appConfig, tc *taskConfig, lc *lockConfig, pc *prometheusConfig) *taskService {
|
||||
location, err := time.LoadLocation(ac.timeZone)
|
||||
|
||||
if err != nil {
|
||||
|
@ -57,22 +66,27 @@ func newTaskService(u *updateService, e *eventService, w *webhookService, l lock
|
|||
}
|
||||
|
||||
return &taskService{
|
||||
updateService: u,
|
||||
eventService: e,
|
||||
webhookService: w,
|
||||
lockService: l,
|
||||
prometheusService: p,
|
||||
appConfig: ac,
|
||||
taskConfig: tc,
|
||||
lockConfig: lc,
|
||||
prometheusConfig: pc,
|
||||
scheduler: scheduler,
|
||||
updateService: u,
|
||||
eventService: e,
|
||||
actionService: a,
|
||||
actionInvocationService: ai,
|
||||
webhookService: w,
|
||||
lockService: l,
|
||||
prometheusService: p,
|
||||
appConfig: ac,
|
||||
taskConfig: tc,
|
||||
lockConfig: lc,
|
||||
prometheusConfig: pc,
|
||||
scheduler: scheduler,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *taskService) init() {
|
||||
s.configureCleanupStaleUpdatesTask()
|
||||
s.configureCleanupStaleEventsTask()
|
||||
s.configureActionsEnqueueTask()
|
||||
s.configureActionsInvokeTask()
|
||||
s.configureCleanupStaleActionsTask()
|
||||
s.configurePrometheusRefreshTask()
|
||||
}
|
||||
|
||||
|
@ -92,9 +106,8 @@ func (s *taskService) configureCleanupStaleUpdatesTask() {
|
|||
if !s.taskConfig.updateCleanStaleEnabled {
|
||||
return
|
||||
}
|
||||
initialDelay := time.Now().Add(10 * time.Second)
|
||||
_, err := s.scheduler.Every(s.taskConfig.updateCleanStaleInterval).
|
||||
StartAt(initialDelay).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNameUpdatesCleanStale
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
|
@ -127,7 +140,7 @@ func (s *taskService) configureCleanupStaleUpdatesTask() {
|
|||
if c > 0 {
|
||||
zap.L().Sugar().Infof("Cleaned up '%d' stale updates", c)
|
||||
} else {
|
||||
zap.L().Info("No stale updates found to clean up")
|
||||
zap.L().Debug("No stale updates found to clean up")
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -141,9 +154,8 @@ func (s *taskService) configureCleanupStaleEventsTask() {
|
|||
return
|
||||
}
|
||||
|
||||
initialDelay := time.Now().Add(5 * time.Second)
|
||||
_, err := s.scheduler.Every(s.taskConfig.eventCleanStaleInterval).
|
||||
StartAt(initialDelay).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNameEventsCleanStale
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
|
@ -168,7 +180,7 @@ func (s *taskService) configureCleanupStaleEventsTask() {
|
|||
var err error
|
||||
var c int64
|
||||
|
||||
if c, err = s.eventService.cleanStale(t, api.EventStateCreated); err != nil {
|
||||
if c, err = s.eventService.cleanStale(t, api.EventStateCreated, api.EventStateEnqueued); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not clean up stale events older than %s (%s). Reason: %s", s.taskConfig.eventCleanStaleMaxAge, t, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -176,7 +188,7 @@ func (s *taskService) configureCleanupStaleEventsTask() {
|
|||
if c > 0 {
|
||||
zap.L().Sugar().Infof("Cleaned up '%d' stale events", c)
|
||||
} else {
|
||||
zap.L().Info("No stale events found to clean up")
|
||||
zap.L().Debug("No stale events found to clean up")
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -185,14 +197,137 @@ func (s *taskService) configureCleanupStaleEventsTask() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *taskService) configureActionsEnqueueTask() {
|
||||
if !s.taskConfig.actionsEnqueueEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := s.scheduler.Every(s.taskConfig.actionsEnqueueInterval).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNameActionsEnqueue
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
if !s.lockConfig.redisEnabled {
|
||||
// skip execution if lock already exists, wait otherwise
|
||||
if lockExists := s.lockService.exists(resource); lockExists {
|
||||
zap.L().Sugar().Debugf("Skipping task execution because task lock '%s' exists", resource)
|
||||
return
|
||||
}
|
||||
_ = s.lockService.tryLock(resource)
|
||||
defer func(lockService lockService, resource string) {
|
||||
err := lockService.release(resource)
|
||||
if err != nil {
|
||||
zap.L().Sugar().Warnf("Could not release task lock '%s'", resource)
|
||||
}
|
||||
}(s.lockService, resource)
|
||||
}
|
||||
|
||||
if err := s.actionInvocationService.enqueue(s.taskConfig.actionsEnqueueBatchSize); err != nil {
|
||||
zap.L().Sugar().Errorf("Could enqueue actions. Reason: %s", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
zap.L().Sugar().Fatalf("Could not create task for enqueueing actions. Reason: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *taskService) configureActionsInvokeTask() {
|
||||
if !s.taskConfig.actionsInvokeEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := s.scheduler.Every(s.taskConfig.actionsInvokeInterval).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNameActionsInvoke
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
if !s.lockConfig.redisEnabled {
|
||||
// skip execution if lock already exists, wait otherwise
|
||||
if lockExists := s.lockService.exists(resource); lockExists {
|
||||
zap.L().Sugar().Debugf("Skipping task execution because task lock '%s' exists", resource)
|
||||
return
|
||||
}
|
||||
_ = s.lockService.tryLock(resource)
|
||||
defer func(lockService lockService, resource string) {
|
||||
err := lockService.release(resource)
|
||||
if err != nil {
|
||||
zap.L().Sugar().Warnf("Could not release task lock '%s'", resource)
|
||||
}
|
||||
}(s.lockService, resource)
|
||||
}
|
||||
|
||||
if err := s.actionInvocationService.invoke(s.taskConfig.actionsInvokeBatchSize, s.taskConfig.actionsInvokeMaxRetries); err != nil {
|
||||
zap.L().Sugar().Errorf("Could invoke actions. Reason: %s", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
zap.L().Sugar().Fatalf("Could not create task for invoking actions. Reason: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *taskService) configureCleanupStaleActionsTask() {
|
||||
if !s.taskConfig.actionsCleanStaleEnabled {
|
||||
return
|
||||
}
|
||||
_, err := s.scheduler.Every(s.taskConfig.actionsCleanStaleInterval).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNameActionsCleanStale
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
if !s.lockConfig.redisEnabled {
|
||||
// skip execution if lock already exists, wait otherwise
|
||||
if lockExists := s.lockService.exists(resource); lockExists {
|
||||
zap.L().Sugar().Debugf("Skipping task execution because task lock '%s' exists", resource)
|
||||
return
|
||||
}
|
||||
_ = s.lockService.tryLock(resource)
|
||||
defer func(lockService lockService, resource string) {
|
||||
err := lockService.release(resource)
|
||||
if err != nil {
|
||||
zap.L().Sugar().Warnf("Could not release task lock '%s'", resource)
|
||||
}
|
||||
}(s.lockService, resource)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
t = t.Add(-s.taskConfig.actionsCleanStaleMaxAge)
|
||||
|
||||
var cError int64
|
||||
var err error
|
||||
|
||||
if cError, err = s.actionInvocationService.cleanStale(t, s.taskConfig.actionsInvokeMaxRetries, api.ActionInvocationStateError); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not clean up error stale actions older than %s (%s). Reason: %s", s.taskConfig.actionsCleanStaleMaxAge, t, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var cSuccess int64
|
||||
if cSuccess, err = s.actionInvocationService.cleanStale(t, 0, api.ActionInvocationStateSuccess); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not clean up success stale actions older than %s (%s). Reason: %s", s.taskConfig.actionsCleanStaleMaxAge, t, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c := cError + cSuccess
|
||||
if c > 0 {
|
||||
zap.L().Sugar().Infof("Cleaned up '%d' stale actions", c)
|
||||
} else {
|
||||
zap.L().Debug("No stale actions found to clean up")
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
zap.L().Sugar().Fatalf("Could not create task for cleaning stale actions. Reason: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *taskService) configurePrometheusRefreshTask() {
|
||||
if !s.prometheusConfig.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
initialDelay := time.Now().Add(10 * time.Second)
|
||||
_, err := s.scheduler.Every(s.taskConfig.prometheusRefreshInterval).
|
||||
StartAt(initialDelay).
|
||||
StartAt(initialTasksStartDelay).
|
||||
Do(func() {
|
||||
resource := taskLockNamePrometheusUpdate
|
||||
// distributed lock handled via gocron-redis-lock for tasks
|
||||
|
@ -223,20 +358,12 @@ func (s *taskService) configurePrometheusRefreshTask() {
|
|||
var ackTotal int64
|
||||
|
||||
for _, update := range updates {
|
||||
var updateState float64
|
||||
if api.UpdateStatePending.Value() == update.State {
|
||||
pendingTotal += 1
|
||||
updateState = 0
|
||||
} else if api.UpdateStateIgnored.Value() == update.State {
|
||||
ignoredTotal += 1
|
||||
updateState = 2
|
||||
} else if api.UpdateStateApproved.Value() == update.State {
|
||||
ackTotal += 1
|
||||
updateState = 1
|
||||
}
|
||||
|
||||
if updatesError = s.prometheusService.setGauge(metricUpdates, []string{update.Application, update.Provider, update.Host}, updateState); updatesError != nil {
|
||||
zap.L().Sugar().Errorf("Could not refresh updates prometheus metric. Reason: %s", updatesError.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,6 +392,14 @@ func (s *taskService) configurePrometheusRefreshTask() {
|
|||
if eventsError = s.prometheusService.setGaugeNoLabels(metricEvents, float64(eventsTotal)); eventsError != nil {
|
||||
zap.L().Sugar().Errorf("Could not refresh events prometheus metric. Reason: %s", eventsError.Error())
|
||||
}
|
||||
|
||||
// actions
|
||||
var actionsTotal int64
|
||||
var actionsError error
|
||||
actionsTotal, actionsError = s.actionService.count()
|
||||
if actionsError = s.prometheusService.setGaugeNoLabels(metricActions, float64(actionsTotal)); actionsError != nil {
|
||||
zap.L().Sugar().Errorf("Could not refresh actions prometheus metric. Reason: %s", actionsError.Error())
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -8,16 +8,14 @@ import (
|
|||
)
|
||||
|
||||
type updateService struct {
|
||||
repo updateRepository
|
||||
eventService *eventService
|
||||
prometheusService *prometheusService
|
||||
repo updateRepository
|
||||
eventService *eventService
|
||||
}
|
||||
|
||||
func newUpdateService(r updateRepository, e *eventService, p *prometheusService) *updateService {
|
||||
func newUpdateService(r updateRepository, e *eventService) *updateService {
|
||||
return &updateService{
|
||||
repo: r,
|
||||
eventService: e,
|
||||
prometheusService: p,
|
||||
repo: r,
|
||||
eventService: e,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,10 +116,6 @@ func (s *updateService) delete(id string) error {
|
|||
|
||||
s.eventService.createUpdateDeleted(e)
|
||||
|
||||
if err = s.prometheusService.setGauge(metricUpdates, []string{e.Application, e.Provider, e.Host}, -1); err != nil {
|
||||
zap.L().Sugar().Errorf("Could not refresh updates prometheus metric for deleted update '%v'. Reason: %v", e.ID, err)
|
||||
}
|
||||
|
||||
zap.L().Sugar().Infof("Deleted update '%v'", id)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,14 +10,12 @@ import (
|
|||
type webhookService struct {
|
||||
repo WebhookRepository
|
||||
webhookConfig *webhookConfig
|
||||
eventService *eventService
|
||||
}
|
||||
|
||||
func newWebhookService(r WebhookRepository, c *webhookConfig, e *eventService) *webhookService {
|
||||
func newWebhookService(r WebhookRepository, c *webhookConfig) *webhookService {
|
||||
return &webhookService{
|
||||
repo: r,
|
||||
webhookConfig: c,
|
||||
eventService: e,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,16 +40,15 @@ func (s *webhookService) create(label string, t api.WebhookType, ignoreHost bool
|
|||
|
||||
var err error
|
||||
var token string
|
||||
var e *Webhook
|
||||
|
||||
if token, err = util.GenerateSecureRandomString(s.webhookConfig.tokenLength); err != nil {
|
||||
return nil, newServiceError(General, fmt.Errorf("token generation failed: %w", err))
|
||||
}
|
||||
|
||||
var e *Webhook
|
||||
if e, err = s.repo.create(label, t, token, ignoreHost); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
s.eventService.createWebhookCreated(e)
|
||||
zap.L().Sugar().Info("Created webhook")
|
||||
return e, nil
|
||||
}
|
||||
|
@ -69,12 +66,10 @@ func (s *webhookService) updateLabel(id string, label string) (*Webhook, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
old := e
|
||||
if e, err = s.repo.updateLabel(id, label); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.eventService.createWebhookUpdated(old, e)
|
||||
zap.L().Sugar().Infof("Modified webhook '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
@ -91,12 +86,10 @@ func (s *webhookService) updateIgnoreHost(id string, ignoreHost bool) (*Webhook,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
old := e
|
||||
if e, err = s.repo.updateIgnoreHost(id, ignoreHost); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.eventService.createWebhookUpdated(old, e)
|
||||
zap.L().Sugar().Infof("Modified webhook '%v'", id)
|
||||
return e, nil
|
||||
}
|
||||
|
@ -115,7 +108,6 @@ func (s *webhookService) delete(id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
s.eventService.createWebhookDeleted(e)
|
||||
zap.L().Sugar().Infof("Deleted webhook '%v'", id)
|
||||
|
||||
return nil
|
||||
|
|
84
util/encryption.go
Normal file
84
util/encryption.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ConvertToBase64(input []byte) (string, error) {
|
||||
if input == nil {
|
||||
return "", errors.New("cannot convert to base64 with nil input")
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(input), nil
|
||||
}
|
||||
|
||||
func ConvertFromBase64(input string) ([]byte, error) {
|
||||
if input == "" {
|
||||
return nil, errors.New("cannot convert from base64 with blank input")
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(input)
|
||||
}
|
||||
|
||||
// EncryptAndEncode encrypts an input with a secret key and returns the base64 encoded and encrypted string
|
||||
func EncryptAndEncode(input string, secretKey string) (string, error) {
|
||||
var err error
|
||||
var block cipher.Block
|
||||
if block, err = aes.NewCipher([]byte(secretKey)); err != nil {
|
||||
return "", fmt.Errorf("cannot create cipher for encrypting: %w", err)
|
||||
}
|
||||
|
||||
var gcm cipher.AEAD
|
||||
if gcm, err = cipher.NewGCM(block); err != nil {
|
||||
return "", fmt.Errorf("cannot create gcm for encrypting: %w", err)
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
|
||||
if _, err = rand.Read(nonce); err != nil {
|
||||
return "", fmt.Errorf("cannot read nonce for encrypting: %w", err)
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, []byte(input), nil)
|
||||
|
||||
var encoded string
|
||||
if encoded, err = ConvertToBase64(ciphertext); err != nil {
|
||||
return "", fmt.Errorf("cannot encode encrypted input: %w", err)
|
||||
}
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// DecryptAndDecode decodes and decrypts a given base64 string and returns the decrypted plain text
|
||||
func DecryptAndDecode(base64Encoded string, secretKey string) (string, error) {
|
||||
var decoded []byte
|
||||
var err error
|
||||
if decoded, err = ConvertFromBase64(base64Encoded); err != nil {
|
||||
return "", fmt.Errorf("cannot decode encoded input: %w", err)
|
||||
}
|
||||
|
||||
decodedCipherText := string(decoded)
|
||||
|
||||
var block cipher.Block
|
||||
if block, err = aes.NewCipher([]byte(secretKey)); err != nil {
|
||||
return "", fmt.Errorf("cannot create cipher for decrypting: %w", err)
|
||||
}
|
||||
|
||||
var gcm cipher.AEAD
|
||||
if gcm, err = cipher.NewGCM(block); err != nil {
|
||||
return "", fmt.Errorf("cannot create gcm for decrypting: %w", err)
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
nonce, decodedCipherText := decodedCipherText[:nonceSize], decodedCipherText[nonceSize:]
|
||||
|
||||
var plaintext []byte
|
||||
if plaintext, err = gcm.Open(nil, []byte(nonce), []byte(decodedCipherText), nil); err != nil {
|
||||
return "", fmt.Errorf("cannot decrypt: %w", err)
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
37
util/encryption_test.go
Normal file
37
util/encryption_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncryptsAndDecrypts(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
testSecret := "mysecretpassword"
|
||||
testText := "the super secret text"
|
||||
|
||||
encrypted, err := EncryptAndEncode(testText, testSecret)
|
||||
a.Nil(err)
|
||||
a.NotEmpty(encrypted)
|
||||
a.NotEqual(testText, encrypted)
|
||||
|
||||
decrypted, err := DecryptAndDecode(encrypted, testSecret)
|
||||
a.Nil(err)
|
||||
a.Equal(testText, decrypted)
|
||||
}
|
||||
|
||||
func TestEncodeAndDecode(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
s := "my to be encoded value"
|
||||
|
||||
encoded, err := ConvertToBase64([]byte(s))
|
||||
a.Nil(err)
|
||||
a.NotEmpty(encoded)
|
||||
|
||||
decoded, err := ConvertFromBase64(encoded)
|
||||
a.Nil(err)
|
||||
a.Equal(s, string(decoded))
|
||||
|
||||
}
|
8
util/json.go
Normal file
8
util/json.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package util
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// UnmarshalGenericJSON unmarshal JSON into given generic type T
|
||||
func UnmarshalGenericJSON[T any](b []byte) (v T, err error) {
|
||||
return v, json.Unmarshal(b, &v)
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// FindInSlice finds value in a slice
|
||||
func FindInSlice(slice []string, val string) bool {
|
||||
for _, item := range slice {
|
||||
if item == val {
|
||||
|
@ -15,6 +16,7 @@ func FindInSlice(slice []string, val string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// ValuesString concatenate all values of a map split by comma
|
||||
func ValuesString(m map[string]string) string {
|
||||
values := make([]string, 0, len(m))
|
||||
for _, v := range m {
|
||||
|
@ -26,6 +28,7 @@ func ValuesString(m map[string]string) string {
|
|||
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
|
||||
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
|
||||
|
||||
// ToSnakeCase converts string to snake case
|
||||
func ToSnakeCase(str string) string {
|
||||
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
|
||||
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
|
||||
|
@ -38,6 +41,7 @@ const (
|
|||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
)
|
||||
|
||||
// RandomString generates a random string of length n
|
||||
func RandomString(n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
|
@ -51,3 +55,13 @@ func RandomString(n int) string {
|
|||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ExtractBetween extracts all occurrences of a string within left and right delimiter, first inner array item is with delimiters, second one without
|
||||
func ExtractBetween(str string, leftDelimiter string, rightDelimiter string) [][]string {
|
||||
if str == "" || leftDelimiter == "" || rightDelimiter == "" {
|
||||
return make([][]string, 0)
|
||||
}
|
||||
|
||||
rx := regexp.MustCompile(`(?s)` + regexp.QuoteMeta(leftDelimiter) + `(.*?)` + regexp.QuoteMeta(rightDelimiter))
|
||||
return rx.FindAllStringSubmatch(str, -1)
|
||||
}
|
||||
|
|
|
@ -35,3 +35,26 @@ func TestExtractValuesFromString(t *testing.T) {
|
|||
a.Contains(valuesString, "val1")
|
||||
a.Contains(valuesString, "val2")
|
||||
}
|
||||
|
||||
func TestExtractBetweenEmpty(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
a.Equal(0, len(ExtractBetween("", "", "")))
|
||||
a.Equal(0, len(ExtractBetween("test", "", "")))
|
||||
a.Equal(0, len(ExtractBetween("test", "test", "")))
|
||||
}
|
||||
|
||||
func TestExtractBetweenVars(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
left := "<VAR>"
|
||||
right := "</VAR>"
|
||||
|
||||
//str := "A new update arrived on <VAR>HOST</VAR> for <VAR>APPLICATION</VAR>. Its version is <VAR>VERSION</VAR>"
|
||||
str := "<VAR>MY_VAR</VAR> A new update arrived on <VA>HOST</VAR> for for <VAR>APPLICATION</VAR> (<VAR>MY_SECOND_VAR</VAR>) ... some random other tag <SECRET>MY_SECRET</SECRET>"
|
||||
matches := ExtractBetween(str, left, right)
|
||||
|
||||
a.Equal(true, len(matches) > 0)
|
||||
a.Equal("MY_VAR", matches[0][1])
|
||||
a.Equal("APPLICATION", matches[1][1])
|
||||
a.Equal("MY_SECOND_VAR", matches[2][1])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue