diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml index d812124..0b7076b 100644 --- a/.github/actions/setup-tools/action.yml +++ b/.github/actions/setup-tools/action.yml @@ -5,10 +5,10 @@ runs: using: composite steps: - name: Install asdf - uses: asdf-vm/actions/setup@v1 + uses: asdf-vm/actions/setup@v3 - name: Cache tools - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /home/runner/.asdf key: ${{ runner.os }}-${{ hashFiles('**/.tool-versions') }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index a0d77df..800b1b6 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,8 +6,8 @@ jobs: pre-commit: runs-on: ubuntu-22.04 steps: - - uses: hmarr/debug-action@v2 - - uses: actions/checkout@v3 + - uses: hmarr/debug-action@v3 + - uses: actions/checkout@v4 - name: Setup Tools uses: ./.github/actions/setup-tools @@ -18,7 +18,7 @@ jobs: commitlint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v5 diff --git a/.github/workflows/publish-aggregator-image.yml b/.github/workflows/publish-aggregator-image.yml new file mode 100644 index 0000000..9d72a29 --- /dev/null +++ b/.github/workflows/publish-aggregator-image.yml @@ -0,0 +1,51 @@ +name: Create and publish SSO Aggregator + +on: + push: + branches: + - main + - dev + paths: + - aggregator/** + +env: + GITHUB_REGISTRY: ghcr.io + IMAGE_NAME: bcgov/sso-aggregator + +jobs: + build-and-push-image: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + + steps: + - uses: hmarr/debug-action@v3 + - uses: actions/checkout@v4 + + - name: Setup Tools + uses: ./.github/actions/setup-tools + + - name: Log in to the GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,format=long + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: aggregator + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-aggregator.yml b/.github/workflows/publish-aggregator.yml deleted file mode 100644 index 8fe8e2d..0000000 --- a/.github/workflows/publish-aggregator.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Create and publish SSO Aggregator - -on: - push: - branches: - - main - - dev - paths: - - aggregator/** - - helm/aggregator/** - - .github/workflows/publish-aggregator.yml - -env: - GITHUB_REGISTRY: ghcr.io - IMAGE_NAME: bcgov/sso-aggregator - -jobs: - build-and-push-image: - runs-on: ubuntu-22.04 - permissions: - contents: read - packages: write - - steps: - - uses: hmarr/debug-action@v2 - - uses: actions/checkout@v3 - - - name: Setup Tools - uses: ./.github/actions/setup-tools - - - name: Log in to the GitHub Container registry - uses: docker/login-action@v2 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=sha,format=long - - - name: Build and push Docker image - uses: docker/build-push-action@v3 - with: - context: aggregator - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Set env to Sandbox - if: (github.ref == 'refs/heads/dev' && github.event_name == 'push') - run: | - cat >> $GITHUB_ENV <> $GITHUB_ENV <> $GITHUB_ENV <> $GITHUB_ENV <> $GITHUB_ENV <> $GITHUB_ENV < ## Deployment -It continuously deploys the resources in the sandbox and the prod environment based on the repository branch (dev, main) that has the new changes. +It continuously deploys the resources in the sandbox and the prod environment based on the repository branch (pr's to dev deploys sandbox, pr's to main deploys prod) that has the new changes. GitHub CD pipeline scripts are triggered based on the directory that has changed; there is a recommended deployment order when deploying the resources for the very first time: 1. `Loki`: deploys the `MinIO` and `Loki` resources, `read`, `write`, and `gateway`. 1. `Aggregator`: deploys the `Aggregator` and `Compactor` with the `Postgres DB`. 1. `Grafana`: deploys the `Grafana` dashboard with the two `datasources` configured above. - - - requires two environment variables `AGGREGATION_DB_USER` and `AGGREGATION_DB_PASS` to access the aggregated database after the `Go servers` are deployed. - 1. `Promtail`: deploys the `Promtail` in multiple namespaces to collect the Keycloak disk logs. ## GitHub secrets @@ -62,9 +66,6 @@ The following secrets are set in the GitHub secrets of the repository and can be - please find the integration `#4492 SSO Dashboard` via [CSS app](https://bcgov.github.io/sso-requests) - `SANDBOX_MINIO_USER`: the username of the initial MinIO admin account. - `SANDBOX_MINIO_PASS`: the password of the initial MinIO admin account. -- `SANDBOX_AGGREGATION_DB_USER`: the username of the `Aggregator` postgres database. -- `SANDBOX_AGGREGATION_DB_PASS`: the password of the `Aggregator` postgres database. - - please the find the secret in [Aggregator Secret](https://console.apps.gold.devops.gov.bc.ca/k8s/ns/c6af30-prod/secrets/sso-aggregator-patroni-appusers) ### Production @@ -77,6 +78,3 @@ The following secrets are set in the GitHub secrets of the repository and can be - please find the integration `#4492 SSO Dashboard` via [CSS app](https://bcgov.github.io/sso-requests) - `PROD_MINIO_USER`: the username of the initial MinIO admin account. - `PROD_MINIO_PASS`: the password of the initial MinIO admin account. -- `PROD_AGGREGATION_DB_USER`: the username of the `Aggregator` postgres database. -- `PROD_AGGREGATION_DB_PASS`: the password of the `Aggregator` postgres database. - - please the find the secret in [Aggregator Secret](https://console.apps.gold.devops.gov.bc.ca/k8s/ns/eb75ad-prod/secrets/sso-aggregator-patroni-appusers) diff --git a/aggregator/.env.example b/aggregator/.env.example index 1e38519..443f06a 100644 --- a/aggregator/.env.example +++ b/aggregator/.env.example @@ -5,3 +5,19 @@ DB_USERNAME= DB_PASSWORD= RETENTION_PERIOD= TZ=America/Vancouver +RC_WEBHOOK= + +DEV_KEYCLOAK_URL= +DEV_KEYCLOAK_CLIENT_ID= +DEV_KEYCLOAK_USERNAME= +DEV_KEYCLOAK_PASSWORD= + +TEST_KEYCLOAK_URL= +TEST_KEYCLOAK_CLIENT_ID= +TEST_KEYCLOAK_USERNAME= +TEST_KEYCLOAK_PASSWORD= + +PROD_KEYCLOAK_URL= +PROD_KEYCLOAK_CLIENT_ID= +PROD_KEYCLOAK_USERNAME= +PROD_KEYCLOAK_PASSWORD= diff --git a/aggregator/.gitignore b/aggregator/.gitignore index c055b22..b0d25a7 100644 --- a/aggregator/.gitignore +++ b/aggregator/.gitignore @@ -2,3 +2,4 @@ config.json build __pycache__ .env +venv diff --git a/aggregator/Dockerfile b/aggregator/Dockerfile index b2f53c3..a1eae48 100644 --- a/aggregator/Dockerfile +++ b/aggregator/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19.4-bullseye as builder +FROM golang:1.21-bullseye as builder WORKDIR /app diff --git a/aggregator/README.md b/aggregator/README.md index cb5b623..190560d 100644 --- a/aggregator/README.md +++ b/aggregator/README.md @@ -7,7 +7,10 @@ In order to avoid the custom codebase parsing the requests, it relies on `Grafan ## Compactor -A lightweight Go server running a job scheduling to delete the old aggregated data upserted by the aggregators. +A lightweight Go server running scheduled jobs. There are two cronjobs it controls: + +1. Deleting old client events. +2. Collecting client session counts. ## Environment Variables @@ -18,6 +21,19 @@ A lightweight Go server running a job scheduling to delete the old aggregated da - `DB_PASSWORD`: the password to be used for password authentication. - `RETENTION_PERIOD`: the duration of time to keep the aggregated data. - please see [Postgres Interval Input](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT) for the unit convention. +- `RC_WEBHOOK`: The url for the rocketchat webhook to use when notifying from the compactor +- `DEV_KEYCLOAK_URL`: The development keycloak base URL +- `DEV_KEYCLOAK_CLIENT_ID`: The development keycloak client id +- `DEV_KEYCLOAK_USERNAME`: The development keycloak username +- `DEV_KEYCLOAK_PASSWORD`: The development keycloak passowrd +- `TEST_KEYCLOAK_URL`: The test keycloak base URL +- `TEST_KEYCLOAK_CLIENT_ID`: The test keycloak client id +- `TEST_KEYCLOAK_USERNAME`: The test keycloak username +- `TEST_KEYCLOAK_PASSWORD`: The test keycloak passowrd +- `PROD_KEYCLOAK_URL`: The prod keycloak base URL +- `PROD_KEYCLOAK_CLIENT_ID`: The prod keycloak client id +- `PROD_KEYCLOAK_USERNAME`: The prod keycloak username +- `PROD_KEYCLOAK_PASSWORD`: The prod keycloak passowrd ## Local development setup diff --git a/aggregator/alembic/versions/5bfc2a71a71f_create_sessions_table.py b/aggregator/alembic/versions/5bfc2a71a71f_create_sessions_table.py new file mode 100644 index 0000000..8aba4f2 --- /dev/null +++ b/aggregator/alembic/versions/5bfc2a71a71f_create_sessions_table.py @@ -0,0 +1,37 @@ +"""create sessions table + +Revision ID: 5bfc2a71a71f +Revises: 3999a4f6f9c0 +Create Date: 2024-05-15 17:05:50.453140 + +""" +from alembic import op +import sqlalchemy as sa +from datetime import datetime + +# revision identifiers, used by Alembic. +revision = '5bfc2a71a71f' +down_revision = '3999a4f6f9c0' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('client_sessions', + sa.Column('environment', sa.String(length=255), nullable=False), + sa.Column('realm_id', sa.String(length=255), nullable=False), + sa.Column('client_id', sa.String(length=255), nullable=False), + sa.Column('active_sessions', sa.Integer(), nullable=False), + sa.Column('offline_sessions', sa.Integer(), nullable=False), + sa.Column('date', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.func.current_timestamp()), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('client_sessions') + # ### end Alembic commands ### diff --git a/aggregator/alembic/versions/7c002818cb32_allow_null_in_offline_sessions.py b/aggregator/alembic/versions/7c002818cb32_allow_null_in_offline_sessions.py new file mode 100644 index 0000000..ffab95e --- /dev/null +++ b/aggregator/alembic/versions/7c002818cb32_allow_null_in_offline_sessions.py @@ -0,0 +1,24 @@ +"""allow null in offline sessions + +Revision ID: 7c002818cb32 +Revises: 5bfc2a71a71f +Create Date: 2024-07-29 16:29:07.464223 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7c002818cb32' +down_revision = '5bfc2a71a71f' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.alter_column('client_sessions','offline_sessions', existing_type=sa.INTEGER(), nullable=True) + + +def downgrade() -> None: + op.alter_column('client_sessions','offline_sessions', existing_type=sa.INTEGER(), nullable=False) diff --git a/aggregator/compactor.go b/aggregator/compactor.go index 6defbc1..e81e44f 100644 --- a/aggregator/compactor.go +++ b/aggregator/compactor.go @@ -8,6 +8,8 @@ import ( func main() { log.Printf("cronjob starts...") - - model.RunCronJob() + model.RunEventsJob() + model.RunSessionsJob() + defer model.GetDB().Close() + select {} } diff --git a/aggregator/go.mod b/aggregator/go.mod index bf46e21..89a08ed 100644 --- a/aggregator/go.mod +++ b/aggregator/go.mod @@ -1,10 +1,11 @@ module sso-dashboard.bcgov.com/aggregator -go 1.19 +go 1.21.0 require ( github.com/go-co-op/gocron v1.18.0 github.com/go-pg/pg v8.0.7+incompatible + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/gorilla/mux v1.8.0 github.com/grafana/dskit v0.0.0-20221212120341-3e308a49441b github.com/grafana/loki v1.6.2-0.20221216202714-209b281593b2 @@ -50,6 +51,7 @@ require ( github.com/go-openapi/swag v0.21.1 // indirect github.com/go-openapi/validate v0.21.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/gogo/googleapis v1.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/status v1.1.1 // indirect @@ -113,7 +115,7 @@ require ( github.com/sony/gobreaker v0.5.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect + github.com/stretchr/testify v1.8.2 // indirect github.com/thanos-io/thanos v0.28.0 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect @@ -132,17 +134,17 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect - go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect - golang.org/x/crypto v0.4.0 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/net v0.3.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.1.0 // indirect - golang.org/x/tools v0.2.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect google.golang.org/grpc v1.50.1 // indirect diff --git a/aggregator/go.sum b/aggregator/go.sum index f08f31d..9d35d5a 100644 --- a/aggregator/go.sum +++ b/aggregator/go.sum @@ -42,7 +42,9 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= @@ -58,14 +60,23 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= +github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -81,6 +92,7 @@ github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VM github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= +github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -92,7 +104,9 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo= +github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -120,6 +134,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -143,6 +158,7 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc h1:PYXxkRUBGUMa5xgMVMDl62vEklZvKpVaxQeN9ie7Hfk= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -160,17 +176,23 @@ github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgz github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/digitalocean/godo v1.88.0 h1:SAEdw63xOMmzlwCeCWjLH1GcyDPUjbSAR1Bh7VELxzc= +github.com/digitalocean/godo v1.88.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= +github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -181,8 +203,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/go-control-plane v0.10.3 h1:xdCVXxEe0Y3FQith+0cj2irwZudqGYvecuLB1HtdexY= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.13 h1:TvDcILLkjuZV3ER58VkBmncKsLUBqBDxra/XctCzuMM= +github.com/envoyproxy/protoc-gen-validate v0.6.13/go.mod h1:qEySVqXrEugbHKvmhI8ZqtQi75/RHSSRNpffvB4I6Bw= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -195,6 +219,7 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-co-op/gocron v1.18.0 h1:SxTyJ5xnSN4byCq7b10LmmszFdxQlSQJod8s3gbnXxA= github.com/go-co-op/gocron v1.18.0/go.mod h1:sD/a0Aadtw5CpflUJ/lpP9Vfdk979Wl1Sg33HPHg0FY= @@ -249,10 +274,12 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= +github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -294,6 +321,9 @@ github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRs github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -335,6 +365,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -350,9 +381,12 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -386,10 +420,12 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k= +github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/dskit v0.0.0-20221212120341-3e308a49441b h1:3Di+jzpE0CHlzlYtjDq9xL5xinR4FUQ7GoQ44JkfQLc= github.com/grafana/dskit v0.0.0-20221212120341-3e308a49441b/go.mod h1:rJRGBDtyQNA3OFh7WecUILvxkgGrdIuA4f9wgZOn3V0= github.com/grafana/gomemcache v0.0.0-20221213170046-b5da8a745d41 h1:YxVdHh0Erfya/wb4mzy/JkTxtmefBICE6gAwSkS+61I= @@ -410,6 +446,7 @@ github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86+ github.com/hashicorp/consul/sdk v0.11.0 h1:HRzj8YSCln2yGgCumN5CL8lYlD3gBurnervJRJAZyC4= github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= +github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -434,6 +471,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -454,9 +492,11 @@ github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/nomad/api v0.0.0-20221102143410-8a95f1239005 h1:jKwXhVS4F7qk0g8laz+Anz0g/6yaSJ3HqmSAuSNLUcA= +github.com/hashicorp/nomad/api v0.0.0-20221102143410-8a95f1239005/go.mod h1:vgJmrz4Bz9E1cR/uy70oP9udUJKFRkcEYHlHTp4nFwI= github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hetznercloud/hcloud-go v1.35.3 h1:WCmFAhLRooih2QHAsbCbEdpIHnshQQmrPqsr3rHE1Ow= +github.com/hetznercloud/hcloud-go v1.35.3/go.mod h1:mepQwR6va27S3UQthaEPGS86jtzSY9xWL1e9dyxXpgA= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -466,6 +506,7 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/ionos-cloud/sdk-go/v6 v6.1.3 h1:vb6yqdpiqaytvreM0bsn2pXw+1YDvEk2RKSmBAQvgDQ= +github.com/ionos-cloud/sdk-go/v6 v6.1.3/go.mod h1:Ox3W0iiEz0GHnfY9e5LmAxwklsxguuNFEUSu0gVRTME= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 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= @@ -501,6 +542,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= +github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -509,6 +551,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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= @@ -516,6 +559,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/linode/linodego v1.9.3 h1:+lxNZw4avRxhCqGjwfPgQ2PvMT+vOL0OMsTdzixR7hQ= +github.com/linode/linodego v1.9.3/go.mod h1:h6AuFR/JpqwwM/vkj7s8KV3iGN8/jxn+zc437F8SZ8w= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -571,18 +615,24 @@ 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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= @@ -593,6 +643,7 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk= +github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -654,10 +705,12 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 h1:0roa6gXKgyta64uqh52AQG3wzZXH21unn+ltzQSXML0= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sercand/kuberesolver v2.4.0+incompatible h1:WE2OlRf6wjLxHwNkkFLQGaZcVLEXjMjBPjjEU5vksH8= @@ -678,11 +731,13 @@ github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -696,8 +751,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/thanos-io/thanos v0.28.0 h1:g0LByBE0ANA30/t/a2C/mceYhO3VtIPQFoxCsqrYM9I= github.com/thanos-io/thanos v0.28.0/go.mod h1:pqjpOBxOCME9Yn1QztV8bP9C4rkhWvWtyyavdBZ8lDk= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= @@ -710,12 +765,15 @@ github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= +github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/weaveworks/common v0.0.0-20221201103051-7c2720a9024d h1:9Z/HiqeGN+LOnmotAMpFEQjuXZ4AGAVFG0rC1laP5Go= github.com/weaveworks/common v0.0.0-20221201103051-7c2720a9024d/go.mod h1:Fnq3+U51tMkPRMC6Wr7zKGUeFFYX4YjNrNK50iU0fcE= github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M= github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA= github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA= +github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= @@ -730,7 +788,9 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg= @@ -779,8 +839,8 @@ go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -795,8 +855,8 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -840,8 +900,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -886,6 +946,7 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -896,8 +957,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1019,12 +1080,13 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1034,8 +1096,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1106,8 +1168,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= @@ -1306,11 +1368,15 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/telebot.v3 v3.0.0/go.mod h1:7rExV8/0mDDNu9epSrDm/8j22KLaActH1Tbee6YjzWg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1337,12 +1403,18 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw= inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8= k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= +k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= +k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= +k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.80.0 h1:lyJt0TWMPaGoODa8B8bUuxgHS3W/m/bNr2cca3brA/g= +k8s.io/klog/v2 v2.80.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mellium.im/sasl v0.3.0 h1:0qoaTCTo5Py7u/g0cBIQZcMOgG/5LM71nshbXwznBh8= mellium.im/sasl v0.3.0/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -1350,6 +1422,9 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/aggregator/keycloak/tokenManager.go b/aggregator/keycloak/tokenManager.go new file mode 100644 index 0000000..4ca813c --- /dev/null +++ b/aggregator/keycloak/tokenManager.go @@ -0,0 +1,218 @@ +package keycloak + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +type TokenStrategy interface { + GetTokens(data url.Values, authUrl string) (string, string, error) + IsTokenExpired(token string) (bool, error) +} + +type RequestHandler struct { + ApiBaseUrl string + AuthBaseUrl string + + AccessToken string + RefreshToken string + + password string + username string + clientId string + + tokenStrategy TokenStrategy +} + +func NewRequestHandler( + tokenStrategy TokenStrategy, + ApiUrl string, + AuthUrl string, + password string, + username string, + clientId string, +) *RequestHandler { + return &RequestHandler{ + tokenStrategy: tokenStrategy, + ApiBaseUrl: ApiUrl, + AuthBaseUrl: AuthUrl, + password: password, + username: username, + clientId: clientId, + } +} + +/* +Method to make a post request for a new token or to refresh a token. Pass in the relevant data for the request type, +e.g grant_type=password/client_credentials for a new token, grant_type=refresh_token for a refresh token. +*/ +func (tm *RequestHandler) GetTokens(data url.Values, authUrl string) (string, string, error) { + req, err := http.NewRequest("POST", authUrl+"/realms/master/protocol/openid-connect/token", strings.NewReader(data.Encode())) + + if err != nil { + log.Printf("Error occurred creating request: %v", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + log.Printf("Error occurred sending request to API endpoint: %v", err) + return "", "", err + } + + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + log.Printf("non 200 status code returned from token request: %v", resp.Status) + return "", "", errors.New("non 200 status code returned from token request") + } + + body, err := io.ReadAll(resp.Body) + + if err != nil { + log.Printf("Error reading response body: %v", err) + return "", "", err + } + + type TokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + } + var tokenResponse TokenResponse + + if err := json.Unmarshal(body, &tokenResponse); err != nil { + log.Printf("Error parsing JSON response: %v", err) + return "", "", err + } + + return tokenResponse.AccessToken, tokenResponse.RefreshToken, nil +} + +// Method to get the token, refreshing if necessary +func (rm *RequestHandler) GetToken() (string, string, error) { + if rm.AccessToken == "" { + formData := url.Values{ + "grant_type": {"password"}, + "client_id": {rm.clientId}, + "password": {rm.password}, + "username": {rm.username}, + } + + accessToken, refreshToken, err := rm.tokenStrategy.GetTokens(formData, rm.AuthBaseUrl) + return accessToken, refreshToken, err + } + + // Check expiry and refresh if necessary + accessTokenExpired, err := rm.IsTokenExpired(rm.AccessToken) + + if err != nil { + return "", "", err + } + + if !accessTokenExpired { + return rm.AccessToken, rm.RefreshToken, nil + } + + refreshTokenExpired, err := rm.IsTokenExpired(rm.RefreshToken) + + var formData url.Values + // If there is an error or expiry, must get a new set of tokens. Otherwise refresh + if refreshTokenExpired || err != nil { + formData = url.Values{ + "grant_type": {"password"}, + "client_id": {rm.clientId}, + "password": {rm.password}, + "username": {rm.username}, + } + } else { + formData = url.Values{ + "grant_type": {"refresh_token"}, + "client_id": {rm.clientId}, + "refresh_token": {rm.RefreshToken}, + } + } + + accessToken, refreshToken, err := rm.tokenStrategy.GetTokens(formData, rm.AuthBaseUrl) + + if err != nil { + return "", "", err + } + + return accessToken, refreshToken, nil +} + +// Checks if access token is expired +func (tm *RequestHandler) IsTokenExpired(token string) (bool, error) { + if token == "" { + return true, nil + } + + parsedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{}) + if err != nil { + log.Printf("Error parsing token: %v", err) + } + + if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok { + if exp, ok := claims["exp"].(float64); ok { + now := time.Now().UTC() + expire := time.Unix(int64(exp), 0).UTC() + return expire.Before(now), nil + } else { + fmt.Println("exp claim not found or not a float64") + return false, errors.New("cannot read exp claim") + } + } else { + log.Printf("Invalid token claims") + return false, errors.New("Invalid token claims") + } +} + +/* +Method to perform an HTTP request with token management. Returns the body or an error if network failure or non-200 status code. +*/ +func (rm *RequestHandler) DoRequest(req *http.Request) ([]byte, error) { + accessToken, refreshToken, err := rm.GetToken() + + if err != nil { + log.Printf("Error in dorequest") + return nil, err + } + + rm.AccessToken = accessToken + rm.RefreshToken = refreshToken + + req.Header.Set("Authorization", "Bearer "+accessToken) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + log.Printf("non 200 status code returned from realm request: %v", resp.Status) + return nil, errors.New("non 200 status code returned from realm request") + } + + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + + if err != nil { + log.Printf("Error reading response body: %v", err) + return nil, err + } + + return body, err +} diff --git a/aggregator/keycloak/token_manager_test.go b/aggregator/keycloak/token_manager_test.go new file mode 100644 index 0000000..e388509 --- /dev/null +++ b/aggregator/keycloak/token_manager_test.go @@ -0,0 +1,84 @@ +package keycloak + +import ( + "net/http" + "net/url" + "testing" + "time" +) + +type MockTokenProvider struct { + TokenRefreshed bool + NewTokenRequested bool +} + +func (tm *MockTokenProvider) ResetMock() { + tm.TokenRefreshed = false + tm.NewTokenRequested = false +} + +func (m *MockTokenProvider) GetTokens(data url.Values, url string) (string, string, error) { + grantType := data.Get("grant_type") + + if grantType == "refresh_token" { + m.TokenRefreshed = true + } + + if grantType == "password" { + m.NewTokenRequested = true + } + + return "", "", nil +} + +func (m *MockTokenProvider) IsTokenExpired(token string) (bool, error) { + return false, nil +} + +func TestTokenManagerHandler(t *testing.T) { + mockTokenProvider := &MockTokenProvider{} + handler := NewRequestHandler(mockTokenProvider, "", "", "", "", "") + + req, _ := http.NewRequest("GET", "", nil) + + // Make a request with expired access token and valid refresh token, expect refresh callout + handler.AccessToken = GenerateJWT(time.Now().Add(-time.Hour).Unix()) + handler.RefreshToken = GenerateJWT(time.Now().Add(time.Hour).Unix()) + handler.DoRequest(req) + + if !mockTokenProvider.TokenRefreshed { + t.Errorf("expected RefreshToken to be called, but it was not") + } + + // Make a request with expired access token and an expired refresh token, expect new token requested. + mockTokenProvider.ResetMock() + handler.AccessToken = "" + handler.RefreshToken = GenerateJWT(time.Now().Add(-time.Hour).Unix()) + handler.DoRequest(req) + + if !mockTokenProvider.NewTokenRequested { + t.Errorf("expected a new token to be requested, but it was not") + } + if mockTokenProvider.TokenRefreshed { + t.Errorf("expected only a new token to be requested, but a refresh callout was made") + } + + // Make a request with a valid access token, expect no refresh callout + mockTokenProvider.ResetMock() + handler.AccessToken = GenerateJWT(time.Now().Add(time.Hour).Unix()) + handler.DoRequest(req) + + if mockTokenProvider.TokenRefreshed || mockTokenProvider.NewTokenRequested { + t.Errorf("expected existing token to be used, but a new token was requested") + } + + // Make a request with no tokens, expect new token callout + mockTokenProvider.ResetMock() + handler.AccessToken = "" + handler.RefreshToken = "" + handler.DoRequest(req) + + if !mockTokenProvider.NewTokenRequested { + t.Errorf("expected a new token to be requested, but it was not") + } +} diff --git a/aggregator/keycloak/util.go b/aggregator/keycloak/util.go new file mode 100644 index 0000000..4fb9211 --- /dev/null +++ b/aggregator/keycloak/util.go @@ -0,0 +1,25 @@ +package keycloak + +import ( + "time" + + "github.com/golang-jwt/jwt/v5" +) + +/* Generate a JWT with a given expiration time. Useful for test cases. */ +func GenerateJWT(exp int64) string { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "exp": exp, + "iat": time.Now().Unix(), + "sub": "1234567890", + }) + + secretKey := []byte("secret") + tokenString, err := token.SignedString(secretKey) + + if err != nil { + return "" + } + + return tokenString +} diff --git a/aggregator/model/client_events.go b/aggregator/model/client_events.go index e3f180b..eec323c 100644 --- a/aggregator/model/client_events.go +++ b/aggregator/model/client_events.go @@ -21,12 +21,10 @@ type ClientEvent struct { func UpsertClientEvent(environment string, realmID string, clientID string, eventType string, date time.Time) error { query := "INSERT INTO client_events (environment, realm_id, client_id, event_type, date, count) VALUES(?,?,?,?,?,1) ON CONFLICT (environment, realm_id, client_id, event_type, date) DO UPDATE SET count = client_events.count + 1" _, err := pgdb.Query(nil, query, environment, realmID, clientID, eventType, date) - if err != nil { log.Println(err) return err } - return nil } @@ -41,15 +39,14 @@ func deleteOldClientEvents() error { log.Println(err) return err } - return nil } -func RunCronJob() { +func RunEventsJob() { loc := config.LoadTimeLocation() cron := gocron.NewScheduler(loc) cron.Every(1).Day().At("02:00").Do(func() { deleteOldClientEvents() }) - cron.StartBlocking() + cron.StartAsync() } diff --git a/aggregator/model/client_sessions.go b/aggregator/model/client_sessions.go new file mode 100644 index 0000000..698a59e --- /dev/null +++ b/aggregator/model/client_sessions.go @@ -0,0 +1,148 @@ +package model + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + + "github.com/go-co-op/gocron" + "sso-dashboard.bcgov.com/aggregator/config" + "sso-dashboard.bcgov.com/aggregator/keycloak" + "sso-dashboard.bcgov.com/aggregator/utils" + "sso-dashboard.bcgov.com/aggregator/webhooks" +) + +type RealmInfo struct { + Realm string `json:"realm"` +} + +type SessionStats struct { + ActiveSessions string `json:"active"` + ClientId string `json:"clientId"` + OfflineSessions string `json:"offline"` +} + +var RealmErrorMessage = "Error getting realms for env %s: " +var ClientErrorMessage = "Error getting client stats for env %s: " + +func GetRealms(rm *keycloak.RequestHandler) ([]string, error) { + req, err := http.NewRequest("GET", rm.ApiBaseUrl+"/admin/realms", nil) + if err != nil { + log.Printf("Error occurred creating request: %v", err) + return nil, err + } + + body, err := rm.DoRequest(req) + + if err != nil { + log.Printf("Error reading response body: %v", err) + return nil, err + } + + var realms []RealmInfo + if err := json.Unmarshal([]byte(body), &realms); err != nil { + log.Printf("Error unmarshalling response body: %v", err) + return nil, err + } + + var realmNames []string + + for _, realm := range realms { + realmNames = append(realmNames, realm.Realm) + } + + return realmNames, nil +} + +func GetClientStats(rm *keycloak.RequestHandler, realms []string, env string) bool { + errOccured := false + + handleError := func(message string) { + log.Print(message) + errOccured = true + } + + for _, realm := range realms { + req, err := http.NewRequest("GET", rm.ApiBaseUrl+"/admin/realms/"+realm+"/client-session-stats", nil) + if err != nil { + handleError(fmt.Sprintf("Error occurred creating request: %v", err)) + continue + } + + body, err := rm.DoRequest(req) + + if err != nil { + handleError(fmt.Sprintf("Error reading response body: %v", err)) + continue + } + + var responseObjects []SessionStats + err = json.Unmarshal(body, &responseObjects) + if err != nil { + handleError(fmt.Sprintf("Error unmarshaling JSON response: %v", err)) + continue + } + + for _, obj := range responseObjects { + err := InsertActiveSessions(env, realm, obj.ClientId, obj.ActiveSessions, obj.OfflineSessions) + if err != nil { + handleError(fmt.Sprintf("Error inserting active sessions: %v", err)) + continue + } + } + } + + return errOccured +} + +func InsertActiveSessions(environment string, realmID string, clientID string, activeSessions string, offlineSessions string) error { + query := "INSERT INTO client_sessions (environment, realm_id, client_id, active_sessions, offline_sessions) VALUES(?,?,?,?,?);" + _, err := pgdb.Query(nil, query, environment, realmID, clientID, activeSessions, offlineSessions) + if err != nil { + log.Println(err) + return err + } + return nil +} + +func ActiveSessions(env string, baseUrl string, clientId string, username string, password string, notifier webhooks.RocketChatNotifier) { + log.Println("Getting active sessions for " + env + " environment") + + rm := keycloak.NewRequestHandler(&keycloak.RequestHandler{}, baseUrl, baseUrl, password, username, clientId) + + realms, err := GetRealms(rm) + if err != nil { + log.Println(fmt.Sprintf(RealmErrorMessage, env), err) + notifier.NotifyRocketChat("Session Data Failure", fmt.Sprintf(RealmErrorMessage, env), err.Error()) + return + } + + hasError := GetClientStats(rm, realms, strings.ToLower(env)) + if hasError { + notifier.NotifyRocketChat("Session Data Failure", fmt.Sprintf(ClientErrorMessage, env), "One or more realm's client stats failed to be retrieved. See logs for details.") + return + } + notifier.NotifyRocketChat("Session Data Loaded Successfully", env, fmt.Sprintf("Session data for environment %s has been loaded successfully", env)) +} + +func AllActiveSessions() { + for _, env := range []string{"DEV", "TEST", "PROD"} { + env := env + baseUrl := utils.GetEnv(env+"_KEYCLOAK_URL", "") + clientId := utils.GetEnv(env+"_KEYCLOAK_CLIENT_ID", "") + username := utils.GetEnv(env+"_KEYCLOAK_USERNAME", "") + password := utils.GetEnv(env+"_KEYCLOAK_PASSWORD", "") + go func() { + ActiveSessions(env, baseUrl, clientId, username, password, &webhooks.RocketChat{}) + }() + } +} + +func RunSessionsJob() { + loc := config.LoadTimeLocation() + cron := gocron.NewScheduler(loc) + cron.Every(1).Hour().Do(AllActiveSessions) + cron.StartAsync() +} diff --git a/aggregator/model/client_sessions_test.go b/aggregator/model/client_sessions_test.go new file mode 100644 index 0000000..771a9b1 --- /dev/null +++ b/aggregator/model/client_sessions_test.go @@ -0,0 +1,183 @@ +package model + +import ( + "fmt" + "log" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "sso-dashboard.bcgov.com/aggregator/keycloak" +) + +type MockRocketChat struct { + Messages [][]string +} + +func (m *MockRocketChat) NotifyRocketChat(text string, title string, body string) { + message := []string{text, title, body} + m.Messages = append(m.Messages, message) +} + +func (m *MockRocketChat) ResetMock() { + m.Messages = [][]string{} +} + +func TestTokenFailure(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/realms/master/protocol/openid-connect/token" { + // Fail all token requests + w.WriteHeader(http.StatusForbidden) + } + })) + defer server.Close() + + mock := &MockRocketChat{} + ActiveSessions("DEV", server.URL, "client", "user", "pass", mock) + + if len(mock.Messages) != 1 { + t.Errorf("Expected 1 message to be sent to RocketChat") + } + rcTitle := mock.Messages[0][1] + rcError := mock.Messages[0][2] + if rcTitle != fmt.Sprintf(RealmErrorMessage, "DEV") { + t.Errorf("Expected rocket chat message to include the environment name") + } + if rcError != "non 200 status code returned from token request" { + log.Print(rcError) + t.Errorf("Expected rocket chat message to include the error message") + } +} + +func TestRealmNotifications(t *testing.T) { + var realmResponse string + var realmStatusCode int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + validToken := keycloak.GenerateJWT(time.Now().Add(time.Hour).Unix()) + if r.URL.Path == "/realms/master/protocol/openid-connect/token" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte(fmt.Sprintf(`{"access_token":"%s", "refresh_token":"%s"}`, validToken, validToken))) + } + if r.URL.Path == "/admin/realms" { + w.WriteHeader(realmStatusCode) + w.Write([]byte(realmResponse)) + } + })) + defer server.Close() + + mock := &MockRocketChat{} + + // Test when realm response is successful. Should send a successful message to RC + realmResponse = `[]` + realmStatusCode = http.StatusOK + ActiveSessions("DEV", server.URL, "client", "user", "pass", mock) + + if len(mock.Messages) != 1 { + t.Errorf("Expected 1 message to be sent to RocketChat") + } + rcText := mock.Messages[0][0] + rcTitle := mock.Messages[0][1] + + if rcText != "Session Data Loaded Successfully" { + t.Errorf("Expected successfuly rocket chat message") + } + if rcTitle != "DEV" { + t.Errorf("Expected rocket chat message to include the environment name") + } + + // When realm response is not ok, should notify rc + mock.ResetMock() + realmResponse = `[]` + realmStatusCode = http.StatusForbidden + + ActiveSessions("TEST", server.URL, "client", "user", "pass", mock) + + if len(mock.Messages) != 1 { + t.Errorf("Expected 1 message to be sent to RocketChat") + } + rcTitle = mock.Messages[0][1] + if rcTitle != fmt.Sprintf(RealmErrorMessage, "TEST") { + print(rcTitle) + t.Errorf("Expected rocket chat message to include the environment name") + } +} + +func TestClientNotifications(t *testing.T) { + var clientResponse string + var clientStatusCode int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + validToken := keycloak.GenerateJWT(time.Now().Add(time.Hour).Unix()) + if r.URL.Path == "/realms/master/protocol/openid-connect/token" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte(fmt.Sprintf(`{"access_token":"%s", "refresh_token":"%s"}`, validToken, validToken))) + } + if r.URL.Path == "/admin/realms" { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"realm": "realm 1"}, {"realm": "realm 2"}]`)) + } + if strings.HasSuffix(r.URL.Path, "/client-session-stats") { + w.WriteHeader(clientStatusCode) + w.Write([]byte(clientResponse)) + } + })) + defer server.Close() + mock := &MockRocketChat{} + + // Test when realm response is successful. Should send a successful message to RC + clientResponse = `[]` + clientStatusCode = http.StatusOK + ActiveSessions("DEV", server.URL, "client", "user", "pass", mock) + + if len(mock.Messages) != 1 { + t.Errorf("Expected 1 message to be sent to RocketChat") + } + rcText := mock.Messages[0][0] + rcTitle := mock.Messages[0][1] + + if rcText != "Session Data Loaded Successfully" { + t.Errorf("Expected successfuly rocket chat message") + } + if rcTitle != "DEV" { + t.Errorf("Expected rocket chat message to include the environment name") + } + + // When client response is not ok, should notify rc + mock.ResetMock() + clientStatusCode = http.StatusForbidden + + ActiveSessions("TEST", server.URL, "client", "user", "pass", mock) + + if len(mock.Messages) != 1 { + t.Errorf("Expected 1 message to be sent to RocketChat") + } + rcTitle = mock.Messages[0][1] + if rcTitle != fmt.Sprintf(ClientErrorMessage, "TEST") { + t.Errorf("Expected rocket chat message to include the environment name") + } +} + +func TestClientContinue(t *testing.T) { + var requestCount int + var clientStatusCode int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + validToken := keycloak.GenerateJWT(time.Now().Add(time.Hour).Unix()) + if r.URL.Path == "/realms/master/protocol/openid-connect/token" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte(fmt.Sprintf(`{"access_token":"%s", "refresh_token":"%s"}`, validToken, validToken))) + } + if strings.HasSuffix(r.URL.Path, "/client-session-stats") { + requestCount++ + w.WriteHeader(clientStatusCode) + } + })) + defer server.Close() + rm := keycloak.NewRequestHandler(&keycloak.RequestHandler{}, server.URL, server.URL, "", "", "") + + clientStatusCode = http.StatusBadGateway + GetClientStats(rm, []string{"realm 1", "realm 2", "realm 3"}, "dev") + if requestCount != 3 { + t.Errorf("Expeted all realm requests to be attempted even if one fails") + } +} diff --git a/aggregator/models.py b/aggregator/models.py index 36c656d..4eb11a0 100755 --- a/aggregator/models.py +++ b/aggregator/models.py @@ -2,6 +2,16 @@ from sqlalchemy.types import TIMESTAMP from database import Base +class ClientSession(Base): + __tablename__ = "client_sessions" + + environment = Column(String(255), nullable=False) + realm_id = Column(String(255), nullable=False) + client_id = Column(String(255), nullable=False) + count = Column(Integer, nullable=False) + date = Column(TIMESTAMP(timezone=True), nullable=False) + id = Column(Integer, primary_key=True) + class ClientEvent(Base): __tablename__ = "client_events" diff --git a/aggregator/webhooks/index.go b/aggregator/webhooks/index.go new file mode 100644 index 0000000..dae7306 --- /dev/null +++ b/aggregator/webhooks/index.go @@ -0,0 +1,57 @@ +package webhooks + +import ( + "bytes" + "fmt" + "log" + "net/http" + + "sso-dashboard.bcgov.com/aggregator/utils" +) + +type RocketChatNotifier interface { + NotifyRocketChat(text string, title string, body string) +} + +type RocketChat struct{} + +/* +For the RC webhook, text will appear at the top, with a title and collapsible body below. +*/ +func (r *RocketChat) NotifyRocketChat(text string, title string, body string) { + // HTTP endpoint + log.Println("Sending rocket chat notification") + posturl := utils.GetEnv("RC_WEBHOOK", "") + + // JSON body + requestBodyTemplate := `{ + "text": "%s", + "attachments": [ + { + "title": "%s", + "text": "%s" + } + ] + }` + + requestBody := []byte(fmt.Sprintf(requestBodyTemplate, text, title, body)) + + // Create a HTTP post request + resp, err := http.NewRequest("POST", posturl, bytes.NewBuffer(requestBody)) + if err != nil { + log.Println("Error sending rocket chat notification", err) + } + resp.Header.Add("Content-Type", "application/json") + + client := &http.Client{} + res, err := client.Do(resp) + + if err != nil { + log.Println("Error sending rocket chat notification", err) + } + if res.StatusCode != 200 { + log.Println("Error sending rocket chat notification", res.Status, res.Body) + } + + defer res.Body.Close() +} diff --git a/assets/sso-dashboard-arch.gif b/assets/sso-dashboard-arch.gif new file mode 100644 index 0000000..a0a703c Binary files /dev/null and b/assets/sso-dashboard-arch.gif differ diff --git a/assets/sso-dashboard.drawio b/assets/sso-dashboard.drawio new file mode 100644 index 0000000..4be409a --- /dev/null +++ b/assets/sso-dashboard.drawio @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/helm/aggregator/Makefile b/helm/aggregator/Makefile index f601e60..b213d3f 100644 --- a/helm/aggregator/Makefile +++ b/helm/aggregator/Makefile @@ -19,27 +19,27 @@ helm-dep: .PHONY: install install: helm-dep install: - helm install $(call arguments) + @helm install $(call arguments) .PHONY: upgrade upgrade: helm-dep upgrade: - helm upgrade --install $(call arguments) + @helm upgrade --install $(call arguments) .PHONY: lint lint: helm-dep lint: - helm upgrade --dry-run --install $(call arguments) + @helm upgrade --dry-run --install $(call arguments) .PHONY: uninstall uninstall: helm-dep uninstall: - helm uninstall ${NAME} -n ${NAMESPACE} + @helm uninstall ${NAME} -n ${NAMESPACE} .PHONY: template template: helm-dep template: - helm template $(call arguments) > template.yaml + @helm template $(call arguments) > template.yaml .PHONY: force-install force-install: uninstall diff --git a/helm/aggregator/README.md b/helm/aggregator/README.md index 785434d..4d601ee 100644 --- a/helm/aggregator/README.md +++ b/helm/aggregator/README.md @@ -2,8 +2,37 @@ A Helm chart for deploying [Keycloak event log aggregator](../../aggregator). +## Setup + +This chart includes two different deployments, running the same dockerfile. The dockerfile build copies the compactor into app/compactor, and the aggregator into app/aggregator. In the compactor [deployment template](./templates/deployment-compactor.yaml) the command is used to run the compactor, whereas the [entry shell file](../../aggregator/docker-entrypoint.sh) will run the aggregator by default (last line). + ## Local deployment via Helm chart +### Pre-Requisites + +- Ensure below network policy exists in the namespace where loki is being deployed + +```yaml +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: allow-sso-promtail + namespace: xxxx-xxxx +spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + name: xxxx + - podSelector: + matchLabels: + app.kubernetes.io/name: promtail + policyTypes: + - Ingress +status: {} +``` + ### Installing/Upgrading the Chart ```sh @@ -22,5 +51,5 @@ make uninstall NAMESPACE= once the deployment is completed with the patroni database created, please find the DB admin credentials in OCP secrets below to be used for Grafana datasource configuration: -- `dev`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/c6af30-prod/secrets/sso-aggregator-patroni-appusers +- `sandbox`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/e4ca1d-prod/secrets/sso-aggregator-patroni-appusers - `prod`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/eb75ad-prod/secrets/sso-aggregator-patroni-appusers diff --git a/helm/aggregator/templates/deployment-compactor.yaml b/helm/aggregator/templates/deployment-compactor.yaml index 8116bb1..4f391ca 100644 --- a/helm/aggregator/templates/deployment-compactor.yaml +++ b/helm/aggregator/templates/deployment-compactor.yaml @@ -59,6 +59,36 @@ spec: value: {{ .Values.postgres.database }} - name: RETENTION_PERIOD value: {{ .Values.compactor.retentionPeriod }} + - name: RC_WEBHOOK + value: {{ .Values.compactor.webhookUrl }} + + - name: DEV_KEYCLOAK_URL + value: {{ .Values.compactor.dev.keycloakUrl }} + - name: DEV_KEYCLOAK_CLIENT_ID + value: {{ .Values.compactor.dev.keycloakClientId }} + - name: DEV_KEYCLOAK_USERNAME + value: {{ .Values.compactor.dev.keycloakUsername }} + - name: DEV_KEYCLOAK_PASSWORD + value: {{ .Values.compactor.dev.keycloakPassword }} + + - name: TEST_KEYCLOAK_URL + value: {{ .Values.compactor.test.keycloakUrl }} + - name: TEST_KEYCLOAK_CLIENT_ID + value: {{ .Values.compactor.test.keycloakClientId }} + - name: TEST_KEYCLOAK_USERNAME + value: {{ .Values.compactor.test.keycloakUsername }} + - name: TEST_KEYCLOAK_PASSWORD + value: {{ .Values.compactor.test.keycloakPassword }} + + - name: PROD_KEYCLOAK_URL + value: {{ .Values.compactor.prod.keycloakUrl }} + - name: PROD_KEYCLOAK_CLIENT_ID + value: {{ .Values.compactor.prod.keycloakClientId }} + - name: PROD_KEYCLOAK_USERNAME + value: {{ .Values.compactor.prod.keycloakUsername }} + - name: PROD_KEYCLOAK_PASSWORD + value: {{ .Values.compactor.prod.keycloakPassword }} + resources: {{- toYaml .Values.compactor.resources | nindent 12 }} {{- end }} diff --git a/helm/aggregator/templates/hpa.yaml b/helm/aggregator/templates/hpa.yaml index 1beb802..ba8ebb2 100644 --- a/helm/aggregator/templates/hpa.yaml +++ b/helm/aggregator/templates/hpa.yaml @@ -1,5 +1,5 @@ {{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ include "aggregator.fullname" . }} @@ -17,7 +17,9 @@ spec: - type: Resource resource: name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} {{- end }} {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - type: Resource diff --git a/helm/aggregator/values-c6af30-prod.yaml b/helm/aggregator/values-c6af30-prod.yaml deleted file mode 100644 index c658317..0000000 --- a/helm/aggregator/values-c6af30-prod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -replicaCount: 2 - -patroni: - replicaCount: 2 - persistentVolume: - size: 1Gi - -compactor: - enabled: true - retentionPeriod: "1 month" diff --git a/helm/aggregator/values-e4ca1d-prod.yaml b/helm/aggregator/values-e4ca1d-prod.yaml new file mode 100644 index 0000000..e9c3827 --- /dev/null +++ b/helm/aggregator/values-e4ca1d-prod.yaml @@ -0,0 +1,29 @@ +replicaCount: 2 + +patroni: + replicaCount: 2 + persistentVolume: + size: 1Gi + +compactor: + enabled: true + retentionPeriod: '3 months' + webhookUrl: + dev: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + test: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + prod: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + +autoscaling: + enabled: true diff --git a/helm/aggregator/values-eb75ad-prod.yaml b/helm/aggregator/values-eb75ad-prod.yaml deleted file mode 100644 index 577a908..0000000 --- a/helm/aggregator/values-eb75ad-prod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -replicaCount: 10 - -patroni: - replicaCount: 3 - persistentVolume: - size: 10Gi - -compactor: - enabled: true - retentionPeriod: "1 year" diff --git a/helm/aggregator/values-eb75ad-tools.yaml b/helm/aggregator/values-eb75ad-tools.yaml new file mode 100644 index 0000000..d9f6bad --- /dev/null +++ b/helm/aggregator/values-eb75ad-tools.yaml @@ -0,0 +1,38 @@ +replicaCount: 10 + +patroni: + replicaCount: 2 + persistentVolume: + size: 1Gi + +compactor: + enabled: true + retentionPeriod: '6 months' + webhookUrl: + dev: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + test: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + prod: + keycloakUrl: + keycloakClientId: + keycloakUsername: + keycloakPassword: + +autoscaling: + enabled: true + maxReplicas: 5 + +resources: + limits: + cpu: 300m + memory: 1Gi + requests: + cpu: 150m + memory: 512Mi diff --git a/helm/aggregator/values.yaml b/helm/aggregator/values.yaml index d5212d9..2437379 100644 --- a/helm/aggregator/values.yaml +++ b/helm/aggregator/values.yaml @@ -16,14 +16,16 @@ serviceAccount: annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: "" + name: '' podAnnotations: {} -podSecurityContext: {} +podSecurityContext: + {} # fsGroup: 2000 -securityContext: {} +securityContext: + {} # capabilities: # drop: # - ALL @@ -37,11 +39,11 @@ service: resources: limits: - cpu: 200m - memory: 200Mi + cpu: 300m + memory: 512Mi requests: - cpu: 100m - memory: 100Mi + cpu: 150m + memory: 256Mi autoscaling: enabled: false @@ -70,6 +72,13 @@ patroni: image: repository: registry.opensource.zalan.do/acid/spilo-14 tag: 2.1-p7 + resources: + requests: + cpu: 150m + memory: 256Mi + limits: + cpu: 300m + memory: 512Mi postgresMajorVersion: 14 @@ -85,11 +94,11 @@ patroni: compactor: enabled: false - retentionPeriod: "1 year" + retentionPeriod: '1 year' resources: limits: cpu: 100m - memory: 200Mi + memory: 256Mi requests: cpu: 50m - memory: 100Mi + memory: 128Mi diff --git a/helm/grafana/.env.example b/helm/grafana/.env.example new file mode 100644 index 0000000..94f4a81 --- /dev/null +++ b/helm/grafana/.env.example @@ -0,0 +1,25 @@ + +NAME=sso-grafana +NAMESPACE= +SSO_CLIENT_ID= +SSO_CLIENT_SECRET= + +DS_SSO_LOGS_USERNAME= +DS_SSO_LOGS_PASSWORD= +DS_SSO_LOGS_DATABASE= + +DS_KEYCLOAK_DEV_USERNAME= +DS_KEYCLOAK_DEV_PASSWORD= +DS_KEYCLOAK_DEV_DATABASE= + +DS_KEYCLOAK_TEST_USERNAME= +DS_KEYCLOAK_TEST_PASSWORD= +DS_KEYCLOAK_TEST_DATABASE= + +DS_KEYCLOAK_PROD_USERNAME= +DS_KEYCLOAK_PROD_PASSWORD= +DS_KEYCLOAK_PROD_DATABASE= + +DS_AGGREGATOR_USERNAME= +DS_AGGREGATOR_PASSWORD= +DS_AGGREGATOR_DATABASE= diff --git a/helm/grafana/Makefile b/helm/grafana/Makefile index a571e74..5e68ab7 100644 --- a/helm/grafana/Makefile +++ b/helm/grafana/Makefile @@ -1,10 +1,6 @@ +include /$(PWD)/.env + SHELL := /usr/bin/env bash -NAMESPACE= -SSO_CLIENT_ID= -SSO_CLIENT_SECRET= -AGGREGATION_DB_USER= -AGGREGATION_DB_PASS= -NAME=sso-grafana ifndef NAMESPACE $(error NAMESPACE is not set) @@ -14,8 +10,21 @@ define arguments "${NAME}" . -n "${NAMESPACE}" -f values.yaml -f "values-${NAMESPACE}.yaml" \ --set grafana."grafana\.ini"."auth\.generic_oauth".client_id=${SSO_CLIENT_ID} \ --set grafana."grafana\.ini"."auth\.generic_oauth".client_secret=${SSO_CLIENT_SECRET} \ - --set grafana.datasources."datasources\.yaml".datasources[1].user=${AGGREGATION_DB_USER} \ - --set grafana.datasources."datasources\.yaml".datasources[1].secureJsonData.password=${AGGREGATION_DB_PASS} + --set grafana.datasources."datasources\.yaml".datasources[0].user=${DS_SSO_LOGS_USERNAME} \ + --set grafana.datasources."datasources\.yaml".datasources[0].database=${DS_SSO_LOGS_DATABASE} \ + --set grafana.datasources."datasources\.yaml".datasources[0].secureJsonData.password=${DS_SSO_LOGS_PASSWORD} \ + --set grafana.datasources."datasources\.yaml".datasources[1].user=${DS_KEYCLOAK_DEV_USERNAME} \ + --set grafana.datasources."datasources\.yaml".datasources[1].database=${DS_KEYCLOAK_DEV_DATABASE} \ + --set grafana.datasources."datasources\.yaml".datasources[1].secureJsonData.password=${DS_KEYCLOAK_DEV_PASSWORD} \ + --set grafana.datasources."datasources\.yaml".datasources[2].user=${DS_KEYCLOAK_TEST_USERNAME} \ + --set grafana.datasources."datasources\.yaml".datasources[2].database=${DS_KEYCLOAK_TEST_DATABASE} \ + --set grafana.datasources."datasources\.yaml".datasources[2].secureJsonData.password=${DS_KEYCLOAK_TEST_PASSWORD} \ + --set grafana.datasources."datasources\.yaml".datasources[3].user=${DS_KEYCLOAK_PROD_USERNAME} \ + --set grafana.datasources."datasources\.yaml".datasources[3].database=${DS_KEYCLOAK_PROD_DATABASE} \ + --set grafana.datasources."datasources\.yaml".datasources[3].secureJsonData.password=${DS_KEYCLOAK_PROD_PASSWORD} \ + --set grafana.datasources."datasources\.yaml".datasources[5].user=${DS_AGGREGATOR_USERNAME} \ + --set grafana.datasources."datasources\.yaml".datasources[5].database=${DS_AGGREGATOR_DATABASE} \ + --set grafana.datasources."datasources\.yaml".datasources[5].secureJsonData.password=${DS_AGGREGATOR_PASSWORD} endef .PHONY: helm-dep @@ -25,27 +34,27 @@ helm-dep: .PHONY: install install: helm-dep install: - helm install $(call arguments) + @helm install $(call arguments) .PHONY: upgrade upgrade: helm-dep upgrade: - helm upgrade --install $(call arguments) + @helm upgrade --install $(call arguments) .PHONY: lint lint: helm-dep lint: - helm upgrade --dry-run --install $(call arguments) + @helm upgrade --dry-run --install $(call arguments) .PHONY: uninstall uninstall: helm-dep uninstall: - helm uninstall ${NAME} -n ${NAMESPACE} + @helm uninstall ${NAME} -n ${NAMESPACE} .PHONY: template template: helm-dep template: - helm template $(call arguments) > template.yaml + @helm template $(call arguments) > template.yaml .PHONY: force-install force-install: uninstall diff --git a/helm/grafana/README.md b/helm/grafana/README.md index 0b64621..c39c7a3 100644 --- a/helm/grafana/README.md +++ b/helm/grafana/README.md @@ -4,35 +4,44 @@ A Helm chart for deploying [Grafana dashboard](https://github.com/grafana/helm-c ## Local deployment via Helm chart -### Installing/Upgrading the Chart +### Pre-Requisites + +#### Setup Network Policies + +- Below network policy has to be added to all the namespaces, where grafana requires to access the data source ```sh -make upgrade NAMESPACE= \ - SSO_CLIENT_ID= \ - SSO_CLIENT_SECRET= \ - AGGREGATION_DB_USER= \ - AGGREGATION_DB_PASS= +export LICENSE_PLATE= +export ENV= + +# run below command after logging into each namespace (dev, test and prod) +envsubst < net-policy-sso-keycloak.yaml | oc apply -f - ``` -- please find the SSO client credentials of the integration `#4492 SSO Dashboard` via [CSS app](https://bcgov.github.io/sso-requests): +#### Update Helm Values -- please find the DB admin credentials in OCP secrets: +- create `.env` from `.env.example` and fill the values - - `dev`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/c6af30-prod/secrets/sso-aggregator-patroni-appusers - - `prod`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/eb75ad-prod/secrets/sso-aggregator-patroni-appusers +### Installing/Upgrading the Chart + +```sh +make upgrade +``` + +- please find the SSO client credentials of the integration `#4492 SSO Dashboard` via [CSS app](https://bcgov.github.io/sso-requests): ### Uninstalling the Chart ```sh -make uninstall NAMESPACE= +make uninstall ``` ## Grafana Admin credentials once the deployment is completed, please find the Grafana admin credentials in OCP secrets below: -- `dev`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/c6af30-prod/secrets/sso-grafana -- `prod`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/eb75ad-prod/secrets/sso-grafana +- `dev`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/e4ca1d-tools/secrets/sso-grafana +- `prod`: https://console.apps.gold.devops.gov.bc.ca/k8s/ns/eb75ad-tools/secrets/sso-grafana ## Configuration diff --git a/helm/grafana/net-policy-sso-keycloak.yaml b/helm/grafana/net-policy-sso-keycloak.yaml new file mode 100644 index 0000000..fff8158 --- /dev/null +++ b/helm/grafana/net-policy-sso-keycloak.yaml @@ -0,0 +1,20 @@ +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: 'sso-$ENV-gold-grafana-access' + namespace: '$LICENSE_PLATE-$ENV' +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: sso-patroni + ingress: + - from: + - namespaceSelector: + matchLabels: + environment: tools + name: '$LICENSE_PLATE' + - podSelector: + matchLabels: + app.kubernetes.io/name: sso-grafana + policyTypes: + - Ingress diff --git a/helm/grafana/templates/route.yaml b/helm/grafana/templates/route.yaml index 9e5db05..9497cf3 100644 --- a/helm/grafana/templates/route.yaml +++ b/helm/grafana/templates/route.yaml @@ -6,7 +6,7 @@ metadata: annotations: haproxy.router.openshift.io/balance: roundrobin haproxy.router.openshift.io/disable_cookies: 'true' - haproxy.router.openshift.io/timeout: 30s + haproxy.router.openshift.io/timeout: 500s spec: {{- if .Values.route.host }} host: {{ .Values.route.host }} diff --git a/helm/grafana/values-e4ca1d-tools.yaml b/helm/grafana/values-e4ca1d-tools.yaml new file mode 100644 index 0000000..25a1037 --- /dev/null +++ b/helm/grafana/values-e4ca1d-tools.yaml @@ -0,0 +1,114 @@ +grafana: + replicas: 1 + persistence: + size: 1Gi + + grafana.ini: + server: + domain: sso-grafana-sandbox.apps.gold.devops.gov.bc.ca + root_url: https://sso-grafana-sandbox.apps.gold.devops.gov.bc.ca + auth.generic_oauth: + name: SSO Pathfinder Sandbox + auth_url: https://dev.sandbox.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/auth + token_url: https://dev.sandbox.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/token + + datasources: + datasources.yaml: + apiVersion: 1 + datasources: + - name: SSO Logs + type: postgres + access: proxy + orgId: 1 + url: kc-cron-patroni-readonly:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Dev + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.e4ca1d-dev.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Test + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.e4ca1d-test.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Prod + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.e4ca1d-prod.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Loki + type: loki + access: proxy + orgId: 1 + url: http://sso-loki-gateway.e4ca1d-prod.svc.cluster.local + basicAuth: false + isDefault: true + - name: SSO Aggregator + type: postgres + access: proxy + orgId: 1 + url: sso-aggregator-patroni-readonly.e4ca1d-prod.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + +route: + enabled: true + host: sso-grafana-sandbox.apps.gold.devops.gov.bc.ca diff --git a/helm/grafana/values-eb75ad-tools.yaml b/helm/grafana/values-eb75ad-tools.yaml new file mode 100644 index 0000000..31b529c --- /dev/null +++ b/helm/grafana/values-eb75ad-tools.yaml @@ -0,0 +1,114 @@ +grafana: + replicas: 1 + persistence: + size: 1Gi + + grafana.ini: + server: + domain: sso-grafana.apps.gold.devops.gov.bc.ca + root_url: https://sso-grafana.apps.gold.devops.gov.bc.ca + auth.generic_oauth: + name: SSO Pathfinder + auth_url: https://loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/auth + token_url: https://loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/token + + datasources: + datasources.yaml: + apiVersion: 1 + datasources: + - name: SSO Logs + type: postgres + access: proxy + orgId: 1 + url: kc-cron-patroni-readonly:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Dev + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.eb75ad-dev.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Test + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.eb75ad-test.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Keycloak Prod + type: postgres + access: proxy + orgId: 1 + url: sso-patroni-readonly.eb75ad-prod.svc.cluster.local:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + - name: SSO Loki + type: loki + access: proxy + orgId: 1 + url: http://sso-loki-gateway + basicAuth: false + isDefault: true + - name: SSO Aggregator + type: postgres + access: proxy + orgId: 1 + url: sso-aggregator-patroni-readonly:5432 + user: + database: + basicAuth: false + secureJsonData: + password: + jsonData: + sslmode: disable + maxOpenConns: + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1400 + timescaledb: false + +route: + enabled: true + host: sso-grafana.apps.gold.devops.gov.bc.ca diff --git a/helm/grafana/values.yaml b/helm/grafana/values.yaml index 4005792..6784c51 100644 --- a/helm/grafana/values.yaml +++ b/helm/grafana/values.yaml @@ -20,7 +20,7 @@ grafana: image: repository: grafana/grafana - tag: "9.3.2" + tag: '10.2.2' pullPolicy: IfNotPresent pullSecrets: [] @@ -29,13 +29,13 @@ grafana: securityContext: containerSecurityContext: - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi + resources: + limits: + cpu: 250m + memory: 2Gi + requests: + cpu: 150m + memory: 512Mi persistence: type: pvc @@ -59,6 +59,9 @@ grafana: server: domain: sso-grafana-sandbox.gov.bc.ca root_url: https://sso-grafana-sandbox.gov.bc.ca + auth: + disable_login_form: true + oauth_allow_insecure_email_lookup: true auth.generic_oauth: enabled: true name: Keycloak @@ -75,36 +78,13 @@ grafana: tls_skip_verify_insecure: true use_pkce: true login_attribute_path: preferred_username - + role_attribute_path: contains(client_roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(client_roles[*], 'admin') && 'Admin' || contains(client_roles[*], 'editor') && 'Editor' || 'Viewer' + dataproxy: + timeout: 500 + keep_alive_seconds: 500 + idle_conn_timeout_seconds: 500 + logging: true # see https://github.com/kubeflow/community/blob/master/devstats/config/grafana/provisioning/datasources/datasource.yaml - datasources: - datasources.yaml: - apiVersion: 1 - datasources: - - name: SSO Loki - type: loki - access: proxy - orgId: 1 - url: http://sso-loki-gateway - basicAuth: false - isDefault: true - - name: SSO Postgres - type: postgres - access: proxy - orgId: 1 - url: sso-aggregator-patroni-readonly:5432 - user: - database: aggregation - basicAuth: false - secureJsonData: - password: - jsonData: - sslmode: disable - maxOpenConns: - maxIdleConns: 2 - connMaxLifetime: 14400 - postgresVersion: 1400 - timescaledb: false route: enabled: true diff --git a/helm/loki/Chart.lock b/helm/loki/Chart.lock index 39c8e80..0f9aedb 100644 --- a/helm/loki/Chart.lock +++ b/helm/loki/Chart.lock @@ -1,9 +1,9 @@ dependencies: - name: loki repository: https://grafana.github.io/helm-charts - version: 3.6.1 + version: 5.36.0 - name: minio repository: https://charts.min.io/ - version: 5.0.4 -digest: sha256:2af49e537e0bb3f7b6075ebc360a2bab5b334aeff4c2dc8a68d47222f0963b26 -generated: "2023-01-03T13:07:32.954504898-08:00" + version: 5.0.10 +digest: sha256:1e56f453711e1567c4b51e6176a748b7dacb16074cde8d9d01b447add81f5cb2 +generated: "2023-10-30T15:49:37.042018-07:00" diff --git a/helm/loki/Chart.yaml b/helm/loki/Chart.yaml index f812323..b9c3b53 100644 --- a/helm/loki/Chart.yaml +++ b/helm/loki/Chart.yaml @@ -7,11 +7,11 @@ version: 0.1.0 dependencies: - name: loki alias: loki - version: 3.6.1 + version: 5.36.0 repository: https://grafana.github.io/helm-charts - name: minio alias: root-minio # see https://github.com/minio/minio/tree/master/helm-releases - version: 5.0.4 + version: 5.0.10 repository: https://charts.min.io/ condition: root-minio.enabled diff --git a/helm/loki/Makefile b/helm/loki/Makefile index 9754235..95dc07d 100644 --- a/helm/loki/Makefile +++ b/helm/loki/Makefile @@ -27,27 +27,27 @@ helm-dep: .PHONY: install install: helm-dep install: - helm install $(call arguments) + @helm install $(call arguments) .PHONY: upgrade upgrade: helm-dep upgrade: - helm upgrade --install $(call arguments) + @helm upgrade --install $(call arguments) .PHONY: lint lint: helm-dep lint: - helm upgrade --dry-run --install $(call arguments) + @helm upgrade --dry-run --install $(call arguments) .PHONY: uninstall uninstall: helm-dep uninstall: - helm uninstall ${NAME} -n ${NAMESPACE} + @helm uninstall ${NAME} -n ${NAMESPACE} .PHONY: template template: helm-dep template: - helm template $(call arguments) > template.yaml + @helm template $(call arguments) > template.yaml .PHONY: force-install force-install: uninstall diff --git a/helm/loki/README.md b/helm/loki/README.md index 5f9f092..e59539b 100644 --- a/helm/loki/README.md +++ b/helm/loki/README.md @@ -4,6 +4,41 @@ A Helm chart for deploying [Grafana Loki](https://github.com/grafana/loki/tree/m ## Local deployment via Helm chart +### Pre-Requisites + +- It's generally a good practice to stop Promtail before restarting Grafana Loki. Promtail is responsible for scraping and sending logs to Loki, and stopping it before a restart can prevent potential issues or data loss during the restart process. Stopping Promtail temporarily ensures that it doesn’t try to send logs while Loki is restarting, preventing any potential errors due to a disrupted connection. After Loki has restarted successfully, you can start Promtail again to resume log scraping and forwarding to Loki. This sequence helps in maintaining the integrity of log data and ensures a smoother restart process for Loki. + +```sh +export LICENSE_PLATE= + +oc scale --replicas=0 deployment sso-promtail -n ${LICENSE_PLATE}-dev +oc scale --replicas=0 deployment sso-promtail -n ${LICENSE_PLATE}-test +oc scale --replicas=0 deployment sso-promtail -n ${LICENSE_PLATE}-prod +``` + +- Ensure below network policy exists in the namespace where loki is being deployed + +```yaml +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: allow-sso-promtail + namespace: xxxx-xxxx +spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + name: xxxx + - podSelector: + matchLabels: + app.kubernetes.io/name: promtail + policyTypes: + - Ingress +status: {} +``` + ### Installing/Upgrading the Chart ```sh @@ -18,6 +53,14 @@ make upgrade NAMESPACE= \ - please generate the secure credentials for the initial `MinIO Admin` that can be set in the MinIO deployment. +### Post Installation/Update of Loki + +```sh +oc scale --replicas=1 deployment sso-promtail -n ${LICENSE_PLATE}-dev +oc scale --replicas=1 deployment sso-promtail -n ${LICENSE_PLATE}-test +oc scale --replicas=1 deployment sso-promtail -n ${LICENSE_PLATE}-prod +``` + ### Uninstalling the Chart ```sh diff --git a/helm/loki/values-c6af30-prod.yaml b/helm/loki/values-e4ca1d-prod.yaml similarity index 63% rename from helm/loki/values-c6af30-prod.yaml rename to helm/loki/values-e4ca1d-prod.yaml index b17752c..4163b4a 100644 --- a/helm/loki/values-c6af30-prod.yaml +++ b/helm/loki/values-e4ca1d-prod.yaml @@ -1,11 +1,12 @@ loki: write: - replicas: 2 + replicas: 3 persistence: size: 1Gi read: - replicas: 1 + replicas: 3 + legacyReadTarget: true persistence: size: 1Gi @@ -24,14 +25,14 @@ root-minio: rootUser: rootPassword: persistence: - size: 2Gi + size: 1Gi oidc: - configUrl: "https://dev.loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration" + configUrl: 'https://sandbox.loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration' clientId: clientSecret: - claimName: "client_roles" - scopes: "openid" - redirectUri: "https://sso-minio-sandbox.apps.gold.devops.gov.bc.ca/oauth_callback" + claimName: 'client_roles' + scopes: 'openid' + redirectUri: 'https://sso-minio-sandbox.apps.gold.devops.gov.bc.ca/oauth_callback' routes: minio: enabled: true diff --git a/helm/loki/values-eb75ad-prod.yaml b/helm/loki/values-eb75ad-prod.yaml deleted file mode 100644 index a18f013..0000000 --- a/helm/loki/values-eb75ad-prod.yaml +++ /dev/null @@ -1,38 +0,0 @@ -loki: - write: - replicas: 3 - persistence: - size: 1Gi - - read: - replicas: 3 - persistence: - size: 1Gi - - loki: - storage: - s3: - accessKeyId: - secretAccessKey: - - limits_config: - retention_period: 7200h # 10months - -root-minio: - enabled: true - replicas: 2 - rootUser: - rootPassword: - persistence: - size: 25Gi - oidc: - configUrl: "https://loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration" - clientId: - clientSecret: - claimName: "client_roles" - scopes: "openid" - redirectUri: "https://sso-minio.apps.gold.devops.gov.bc.ca/oauth_callback" -routes: - minio: - enabled: true - host: sso-minio.apps.gold.devops.gov.bc.ca diff --git a/helm/loki/values-eb75ad-tools.yaml b/helm/loki/values-eb75ad-tools.yaml new file mode 100644 index 0000000..2078d9a --- /dev/null +++ b/helm/loki/values-eb75ad-tools.yaml @@ -0,0 +1,60 @@ +loki: + write: + replicas: 3 + persistence: + size: 1Gi + resources: + limits: + cpu: 300m + memory: 2Gi + requests: + cpu: 150m + memory: 512Mi + + read: + replicas: 3 + legacyReadTarget: true + persistence: + size: 1Gi + resources: + limits: + cpu: 300m + memory: 2Gi + requests: + cpu: 150m + memory: 512Mi + + loki: + storage: + s3: + accessKeyId: + secretAccessKey: + + limits_config: + retention_period: 4320h # 6 months + +root-minio: + enabled: true + replicas: 3 + rootUser: + rootPassword: + persistence: + size: 5Gi + resources: + limits: + cpu: 450m + memory: 2Gi + requests: + cpu: 250m + memory: 512Mi + oidc: + configUrl: 'https://loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration' + clientId: + clientSecret: + claimName: 'client_roles' + scopes: 'openid' + redirectUri: 'https://sso-minio.apps.gold.devops.gov.bc.ca/oauth_callback' +routes: + minio: + enabled: true + host: sso-minio.apps.gold.devops.gov.bc.ca diff --git a/helm/loki/values.yaml b/helm/loki/values.yaml index 44fc9d6..0430f7f 100644 --- a/helm/loki/values.yaml +++ b/helm/loki/values.yaml @@ -1,9 +1,9 @@ # see https://github.com/grafana/loki/blob/main/production/helm/loki/values.yaml loki: global: - clusterDomain: "cluster.local" - dnsService: "dns-default" - dnsNamespace: "openshift-dns" + clusterDomain: 'cluster.local' + dnsService: 'dns-default' + dnsNamespace: 'openshift-dns' nameOverride: sso-loki fullnameOverride: sso-loki @@ -28,18 +28,30 @@ loki: retention_delete_delay: 1h retention_delete_worker_count: 150 + ingester: + chunk_target_size: 5242880 + max_chunk_age: 2h + chunk_idle_period: 2h + chunk_encoding: snappy + # see https://grafana.com/docs/loki/latest/configuration/#limits_config limits_config: enforce_metric_name: false reject_old_samples: true - reject_old_samples_max_age: 168h + reject_old_samples_max_age: 168h # 7 days max_cache_freshness_per_query: 1m split_queries_by_interval: 30m max_query_parallelism: 32 - max_query_series: 2000 + max_query_series: 3000 max_query_length: 721h ingestion_rate_mb: 10 - retention_period: 744h + retention_period: 744h # 31 days + max_entries_limit_per_query: 100000 + query_timeout: 500s + max_global_streams_per_user: 1000000 + + commonConfig: + replication_factor: 3 storage: type: s3 @@ -67,7 +79,7 @@ loki: rulerConfig: storage: - type: "s3" + type: 's3' s3: bucketnames: ruler @@ -78,9 +90,20 @@ loki: cache_location: /var/loki/cache cache_ttl: 24h + server: + http_server_read_timeout: 500s + http_server_write_timeout: 500s + http_server_idle_timeout: 500s + grpc_server_max_recv_msg_size: 26214400 + grpc_server_max_send_msg_size: 26214400 + serviceAccount: create: false + rbac: + create: true + namespaced: true + test: enabled: false @@ -100,8 +123,8 @@ loki: grafanaAgent: installOperator: false - lokiCanary: - enabled: false + lokiCanary: + enabled: false write: replicas: 3 @@ -111,9 +134,9 @@ loki: resources: limits: cpu: 250m - memory: 1Gi + memory: 512Mi requests: - cpu: 50m + cpu: 150m memory: 256Mi read: @@ -124,9 +147,9 @@ loki: resources: limits: cpu: 250m - memory: 1Gi + memory: 512Mi requests: - cpu: 50m + cpu: 150m memory: 256Mi ingress: @@ -139,9 +162,9 @@ loki: resources: limits: cpu: 250m - memory: 1Gi + memory: 512Mi requests: - cpu: 50m + cpu: 150m memory: 256Mi networkPolicy: @@ -155,7 +178,7 @@ root-minio: fullnameOverride: sso-minio image: repository: quay.io/minio/minio - tag: RELEASE.2023-01-02T09-40-09Z + tag: RELEASE.2023-10-25T06-33-25Z pullPolicy: IfNotPresent rootUser: admin rootPassword: adminadmin @@ -172,28 +195,28 @@ root-minio: policy: none purge: false persistence: - size: 2Gi + size: 1Gi storageClass: netapp-block-standard resources: limits: - cpu: 500m - memory: 1Gi + cpu: 300m + memory: 512Mi requests: - cpu: 250m - memory: 800Mi + cpu: 150m + memory: 256Mi serviceAccount: create: false certsPath: /tmp/minio/certs/ configPathmc: /tmp/minio/mc/ oidc: enabled: true - configUrl: "https://identity-provider-url/.well-known/openid-configuration" - clientId: "minio" - clientSecret: "" + configUrl: 'https://identity-provider-url/.well-known/openid-configuration' + clientId: 'minio' + clientSecret: '' # please assign admin user to 'consoleAdmin' role for admin access via CSS app - claimName: "client_roles" - scopes: "openid" - redirectUri: "https://console-endpoint-url/oauth_callback" + claimName: 'client_roles' + scopes: 'openid' + redirectUri: 'https://console-endpoint-url/oauth_callback' routes: minio: diff --git a/helm/promtail/Makefile b/helm/promtail/Makefile index 77984f6..bc07c7e 100644 --- a/helm/promtail/Makefile +++ b/helm/promtail/Makefile @@ -17,27 +17,27 @@ helm-dep: .PHONY: install install: helm-dep install: - helm install $(call arguments) + @helm install $(call arguments) .PHONY: upgrade upgrade: helm-dep upgrade: - helm upgrade --install $(call arguments) + @helm upgrade --install $(call arguments) .PHONY: lint lint: helm-dep lint: - helm upgrade --dry-run --install $(call arguments) + @helm upgrade --dry-run --install $(call arguments) .PHONY: uninstall uninstall: helm-dep uninstall: - helm uninstall ${NAME} -n ${NAMESPACE} + @helm uninstall ${NAME} -n ${NAMESPACE} .PHONY: template template: helm-dep template: - helm template $(call arguments) > template.yaml + @helm template $(call arguments) > template.yaml .PHONY: force-install force-install: uninstall diff --git a/helm/promtail/values-c6af30-dev.yaml b/helm/promtail/values-e4ca1d-dev.yaml similarity index 76% rename from helm/promtail/values-c6af30-dev.yaml rename to helm/promtail/values-e4ca1d-dev.yaml index cbd90fe..77bccf7 100644 --- a/helm/promtail/values-c6af30-dev.yaml +++ b/helm/promtail/values-e4ca1d-dev.yaml @@ -8,9 +8,9 @@ promtail: config: logLevel: info clients: - - url: http://sso-loki-gateway.c6af30-prod.svc.cluster.local/loki/api/v1/push + - url: http://sso-loki-gateway.e4ca1d-prod.svc.cluster.local/loki/api/v1/push tenant_id: sso-team - - url: http://sso-aggregator.c6af30-prod.svc.cluster.local:8080/api/promtail/push + - url: http://sso-aggregator.e4ca1d-prod.svc.cluster.local:8080/api/promtail/push snippets: scrapeConfigs: | {{- tpl .Values.ssoScrapeConfigs . }} diff --git a/helm/promtail/values-e4ca1d-prod.yaml b/helm/promtail/values-e4ca1d-prod.yaml new file mode 100644 index 0000000..c0c9025 --- /dev/null +++ b/helm/promtail/values-e4ca1d-prod.yaml @@ -0,0 +1,19 @@ +promtail: + environment: prod + + deployment: + replicaCount: 1 + + # see https://grafana.com/docs/loki/latest/clients/promtail/configuration/#supported-contents-and-default-values-of-configyaml + config: + logLevel: info + clients: + - url: http://sso-loki-gateway.e4ca1d-prod.svc.cluster.local/loki/api/v1/push + tenant_id: sso-team + - url: http://sso-aggregator.e4ca1d-prod.svc.cluster.local:8080/api/promtail/push + snippets: + scrapeConfigs: | + {{- tpl .Values.ssoScrapeConfigs . }} + +persistentVolume: + size: 100Mi diff --git a/helm/promtail/values-e4ca1d-test.yaml b/helm/promtail/values-e4ca1d-test.yaml new file mode 100644 index 0000000..e5f2354 --- /dev/null +++ b/helm/promtail/values-e4ca1d-test.yaml @@ -0,0 +1,19 @@ +promtail: + environment: test + + deployment: + replicaCount: 1 + + # see https://grafana.com/docs/loki/latest/clients/promtail/configuration/#supported-contents-and-default-values-of-configyaml + config: + logLevel: info + clients: + - url: http://sso-loki-gateway.e4ca1d-prod.svc.cluster.local/loki/api/v1/push + tenant_id: sso-team + - url: http://sso-aggregator.e4ca1d-prod.svc.cluster.local:8080/api/promtail/push + snippets: + scrapeConfigs: | + {{- tpl .Values.ssoScrapeConfigs . }} + +persistentVolume: + size: 100Mi diff --git a/helm/promtail/values-eb75ad-dev.yaml b/helm/promtail/values-eb75ad-dev.yaml index 84db767..2cac2cc 100644 --- a/helm/promtail/values-eb75ad-dev.yaml +++ b/helm/promtail/values-eb75ad-dev.yaml @@ -2,15 +2,15 @@ promtail: environment: dev deployment: - replicaCount: 2 + replicaCount: 1 # see https://grafana.com/docs/loki/latest/clients/promtail/configuration/#supported-contents-and-default-values-of-configyaml config: logLevel: info clients: - - url: http://sso-loki-gateway.eb75ad-prod.svc.cluster.local/loki/api/v1/push + - url: http://sso-loki-gateway.eb75ad-tools.svc.cluster.local/loki/api/v1/push tenant_id: sso-team - - url: http://sso-aggregator.eb75ad-prod.svc.cluster.local:8080/api/promtail/push + - url: http://sso-aggregator.eb75ad-tools.svc.cluster.local:8080/api/promtail/push snippets: scrapeConfigs: | {{- tpl .Values.ssoScrapeConfigs . }} diff --git a/helm/promtail/values-eb75ad-prod.yaml b/helm/promtail/values-eb75ad-prod.yaml index d86903b..c8a3fb2 100644 --- a/helm/promtail/values-eb75ad-prod.yaml +++ b/helm/promtail/values-eb75ad-prod.yaml @@ -2,15 +2,15 @@ promtail: environment: prod deployment: - replicaCount: 3 + replicaCount: 1 # see https://grafana.com/docs/loki/latest/clients/promtail/configuration/#supported-contents-and-default-values-of-configyaml config: logLevel: info clients: - - url: http://sso-loki-gateway.eb75ad-prod.svc.cluster.local/loki/api/v1/push + - url: http://sso-loki-gateway.eb75ad-tools.svc.cluster.local/loki/api/v1/push tenant_id: sso-team - - url: http://sso-aggregator.eb75ad-prod.svc.cluster.local:8080/api/promtail/push + - url: http://sso-aggregator.eb75ad-tools.svc.cluster.local:8080/api/promtail/push snippets: scrapeConfigs: | {{- tpl .Values.ssoScrapeConfigs . }} diff --git a/helm/promtail/values-eb75ad-test.yaml b/helm/promtail/values-eb75ad-test.yaml index 950a2d9..b332247 100644 --- a/helm/promtail/values-eb75ad-test.yaml +++ b/helm/promtail/values-eb75ad-test.yaml @@ -2,15 +2,15 @@ promtail: environment: test deployment: - replicaCount: 2 + replicaCount: 1 # see https://grafana.com/docs/loki/latest/clients/promtail/configuration/#supported-contents-and-default-values-of-configyaml config: logLevel: info clients: - - url: http://sso-loki-gateway.eb75ad-prod.svc.cluster.local/loki/api/v1/push + - url: http://sso-loki-gateway.eb75ad-tools.svc.cluster.local/loki/api/v1/push tenant_id: sso-team - - url: http://sso-aggregator.eb75ad-prod.svc.cluster.local:8080/api/promtail/push + - url: http://sso-aggregator.eb75ad-tools.svc.cluster.local:8080/api/promtail/push snippets: scrapeConfigs: | {{- tpl .Values.ssoScrapeConfigs . }} diff --git a/helm/promtail/values.yaml b/helm/promtail/values.yaml index 5c110e7..8581b24 100644 --- a/helm/promtail/values.yaml +++ b/helm/promtail/values.yaml @@ -24,13 +24,13 @@ promtail: imagePullSecrets: [] - resources: {} - # limits: - # cpu: 200m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi podSecurityContext: containerSecurityContext: @@ -78,21 +78,11 @@ promtail: - json: expressions: timestamp: '"@timestamp"' - level: level - - labels: - level: - timestamp: format: RFC3339Nano source: timestamp - - match: - selector: '{job="keycloak"}' - stages: - - regex: - expression: "type=(?P\\S+),.*realmId=(?P\\S+),.*clientId=(?P\\S+)," - - labels: - event_type: type - realm_id: - client_id: + - labeldrop: + - filename persistentVolume: size: 100Mi diff --git a/requirements.txt b/requirements.txt index 604546d..b247bf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ pre-commit==2.9.3 +gitlint==0.15.1