From 36901c7024bf4d84e54a1482a71c76abbcc68a86 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Feb 2022 15:16:37 +0100 Subject: [PATCH 01/14] Bump dev version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4252d55..5114118 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ plugins { allprojects { group = "org.radarbase" - version = "0.4.0" + version = "0.4.1-SNAPSHOT" } val githubRepoName = "RADAR-base/radar-jersey" From 66c61e96e371a600ba08ae23101ae552729e3b64 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Feb 2022 15:23:38 +0100 Subject: [PATCH 02/14] Fix gihtub URL --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5114118..354d68d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ allprojects { version = "0.4.1-SNAPSHOT" } -val githubRepoName = "RADAR-base/radar-jersey" +val githubRepoName = "RADAR-base/radar-app-config" val githubUrl = "https://github.com/$githubRepoName.git" val githubIssueUrl = "https://github.com/$githubRepoName/issues" From 141438a4f004c1b973784ac963916611a67a62cb Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 8 Feb 2022 12:04:51 +0100 Subject: [PATCH 03/14] Add docker release build --- .github/workflows/release.yml | 118 +++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee88bd6..0978c14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: types: [published] jobs: - check: + publish: # The type of runner that the job will run on runs-on: ubuntu-latest @@ -64,3 +64,119 @@ jobs: OSSRH_USER: ${{ secrets.OSSRH_USER }} OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} run: ./gradlew -Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_ID }} -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} publish closeAndReleaseSonatypeStagingRepository + + # Build and push tagged release backend docker image + dockerBackend: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + env: + DOCKER_IMAGE: radarbase/radar-app-config + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Add Docker labels and tags + - name: Docker meta + id: docker_meta + uses: docker/metadata-action@v3 + with: + images: ${{ env.DOCKER_IMAGE }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + # Setup docker build environment + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build backend docker + uses: docker/build-push-action@v2 + with: + context: . + file: ./radar-app-config/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + # Use runtime labels from docker_meta_backend as well as fixed labels + labels: | + ${{ steps.docker_meta.outputs.labels }} + maintainer=Joris Borgdorff , Nivethika Mahasivam + org.opencontainers.image.description=RADAR-base app config service + org.opencontainers.image.authors=Joris Borgdorff , Nivethika Mahasivam + org.opencontainers.image.vendor=RADAR-base + org.opencontainers.image.licenses=Apache-2.0 + + - name: Inspect docker image + run: | + docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + + dockerFrontend: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + env: + DOCKER_IMAGE: radarbase/radar-app-config-frontend + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Add Docker labels and tags + - name: Docker meta + id: docker_meta + uses: docker/metadata-action@v3 + with: + images: ${{ env.DOCKER_IMAGE }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + # Setup docker build environment + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build frontend docker + uses: docker/build-push-action@v2 + with: + context: ./radar-app-config-frontend + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + # Use runtime labels from docker_meta_backend as well as fixed labels + labels: | + ${{ steps.docker_meta.outputs.labels }} + maintainer=Peyman Mohtashami , Joris Borgdorff + org.opencontainers.image.description=RADAR-base app config frontend + org.opencontainers.image.authors=Peyman Mohtashami , Joris Borgdorff + org.opencontainers.image.vendor=RADAR-base + org.opencontainers.image.licenses=Apache-2.0 + + - name: Inspect docker image + run: | + docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} From 0280e7a6e06c62b6749f93b483f809dc0fa890b4 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 8 Feb 2022 13:58:07 +0100 Subject: [PATCH 04/14] Fix release workflow --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0978c14..201cb79 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,7 +93,6 @@ jobs: tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} # Setup docker build environment - name: Set up QEMU @@ -151,7 +150,6 @@ jobs: tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} # Setup docker build environment - name: Set up QEMU From 794642482977b3c45bc670c0ba507369c2d940f3 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 8 Feb 2022 14:00:28 +0100 Subject: [PATCH 05/14] Simplify docker build --- radar-app-config-frontend/Dockerfile | 13 +- radar-app-config-frontend/package-lock.json | 463 +++++++++++++++++++- radar-app-config-frontend/package.json | 2 + 3 files changed, 465 insertions(+), 13 deletions(-) diff --git a/radar-app-config-frontend/Dockerfile b/radar-app-config-frontend/Dockerfile index 708d7ff..4664c58 100644 --- a/radar-app-config-frontend/Dockerfile +++ b/radar-app-config-frontend/Dockerfile @@ -17,8 +17,7 @@ WORKDIR /code COPY package*.json /code/ -RUN echo "==> Installing dependencies" \ - && npm i --silent +RUN npm install COPY .browserslistrc angular.json karma.conf.js ts* /code/ COPY src /code/src @@ -29,16 +28,6 @@ RUN npm run build -- --output-path=./dist/out --configuration ${configuration} WORKDIR /code/dist/out -RUN find . -type f "(" \ - -name "*.html" \ - -o -name "*.js" \ - -o -name "*.css" \ - -o -name "*.xml" \ - -o -name "*.svg" \ - -o -name "*.json" \ - ")" -print0 \ - | xargs -0 -n 1 gzip -k - # Stage 2, based on Nginx, to have only the compiled app, ready for production with Nginx FROM nginxinc/nginx-unprivileged:1.20-alpine diff --git a/radar-app-config-frontend/package-lock.json b/radar-app-config-frontend/package-lock.json index 286b7e0..bc02aea 100644 --- a/radar-app-config-frontend/package-lock.json +++ b/radar-app-config-frontend/package-lock.json @@ -1,6 +1,6 @@ { "name": "appconfig", - "version": "0.3.4", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3321,6 +3321,15 @@ "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", "dev": true }, + "@gfx/zopfli": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@gfx/zopfli/-/zopfli-1.0.15.tgz", + "integrity": "sha512-7mBgpi7UD82fsff5ThQKet0uBTl4BYerQuc+/qA1ELTwWEiIedRTcD3JgiUu9wwZ2kytW8JOb165rSdAt8PfcQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.0" + } + }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -4312,6 +4321,12 @@ "postcss-value-parser": "^4.1.0" } }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -6235,6 +6250,12 @@ "domhandler": "^4.2.0" } }, + "duplex-maker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/duplex-maker/-/duplex-maker-1.0.0.tgz", + "integrity": "sha1-FgT4uUPLAGOm0+H+QqplETp52ko=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -6446,6 +6467,69 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -7082,6 +7166,12 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -7272,6 +7362,16 @@ "pump": "^3.0.0" } }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -7336,6 +7436,62 @@ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, + "gzipper": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gzipper/-/gzipper-7.0.0.tgz", + "integrity": "sha512-Pfr8FXg4JOQ9hwWWS+d5KDOokRlfjkuNuK7HVJsiXwQhYTugKMHXAnAeEuKr5ILtxX0cAzJBtR6g3Wclze8+LA==", + "dev": true, + "requires": { + "@gfx/zopfli": "^1.0.15", + "commander": "^7.2.0", + "deep-equal": "^2.0.5", + "simple-zstd": "^1.4.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } + } + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -7384,6 +7540,12 @@ } } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8034,6 +8196,17 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -8094,6 +8267,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -8103,12 +8285,28 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, "is-core-module": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", @@ -8211,12 +8409,33 @@ "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", "dev": true }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -8266,18 +8485,86 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", + "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", @@ -8299,6 +8586,12 @@ "is-docker": "^2.0.0" } }, + "is-zst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-zst/-/is-zst-1.0.0.tgz", + "integrity": "sha512-ZA5lvshKAl8z30dX7saXLpVhpsq3d2EHK9uf7qtUjnOtdw4XBpAoWb2RvZ5kyoaebdoidnGI0g2hn9Z7ObPbww==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -9937,6 +10230,12 @@ } } }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -10401,6 +10700,17 @@ "sha.js": "^2.4.8" } }, + "peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -10868,6 +11178,17 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "process-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/process-streams/-/process-streams-1.0.1.tgz", + "integrity": "sha1-4iwqrb94jvDFdU6l4Ffphch91pE=", + "dev": true, + "requires": { + "duplex-maker": "^1.0.0", + "quotemeta": "0.0.0", + "tempfile": "^1.1.0" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -11224,6 +11545,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quotemeta": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/quotemeta/-/quotemeta-0.0.0.tgz", + "integrity": "sha1-UdOgbuD81uO1AdvSiQQ1Gtelo4w=", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12116,12 +12443,57 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "simple-zstd": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/simple-zstd/-/simple-zstd-1.4.0.tgz", + "integrity": "sha512-9zBNnu7MkwRiZm7voFUX7ehCcLO2d1FmJ2RWEVsN8Exw2tVYK9k/0/8WjPUmSmtoHOyoFTkHHaOLuPSwkgFmrA==", + "dev": true, + "requires": { + "is-zst": "^1.0.0", + "peek-stream": "^1.1.3", + "process-streams": "^1.0.1", + "through2": "^4.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -12781,6 +13153,26 @@ "strip-ansi": "^6.0.0" } }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -12999,6 +13391,24 @@ } } }, + "tempfile": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", + "integrity": "sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I=", + "dev": true, + "requires": { + "os-tmpdir": "^1.0.0", + "uuid": "^2.0.1" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + } + } + }, "terser": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz", @@ -13326,6 +13736,18 @@ "integrity": "sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw==", "dev": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -14813,12 +15235,51 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-typed-array": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", + "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.7" + } + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/radar-app-config-frontend/package.json b/radar-app-config-frontend/package.json index 2663e3a..6ece822 100644 --- a/radar-app-config-frontend/package.json +++ b/radar-app-config-frontend/package.json @@ -5,6 +5,7 @@ "ng": "ng", "start": "ng serve --port=4200 --base-href=/", "build": "ng build --prod", + "postbuild": "gzipper compress -e ico,png --gzip dist", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", @@ -42,6 +43,7 @@ "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", "codelyzer": "^6.0.0", + "gzipper": "^7.0.0", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~6.1.1", From 06c82a8d65546512dfe9954f20fe6a9dc2f77ea3 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 8 Feb 2022 14:01:01 +0100 Subject: [PATCH 06/14] Bump package.json version --- radar-app-config-frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radar-app-config-frontend/package.json b/radar-app-config-frontend/package.json index 6ece822..b457999 100644 --- a/radar-app-config-frontend/package.json +++ b/radar-app-config-frontend/package.json @@ -1,6 +1,6 @@ { "name": "appconfig", - "version": "0.4.0", + "version": "0.4.1", "scripts": { "ng": "ng", "start": "ng serve --port=4200 --base-href=/", From 86ad8061e659c10058b56e4edc328e22e82a9386 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 10 Feb 2022 10:12:34 +0100 Subject: [PATCH 07/14] Make app config client easier to debug --- .../appconfig/client/AppConfigClient.kt | 36 +++++++++++-------- .../appconfig/client/AppConfigClientConfig.kt | 15 ++++++-- .../radarbase/appconfig/client/LruCache.kt | 5 ++- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt index 1cf3ebb..fe0261e 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt @@ -13,27 +13,34 @@ import org.radarbase.appconfig.api.ClientConfig import org.radarbase.exception.TokenException import org.radarbase.oauth.OAuth2Client import java.io.IOException -import java.time.Duration @Suppress("unused") class AppConfigClient(config: AppConfigClientConfig) { - private val client: OkHttpClient = config.httpClient ?: OkHttpClient() - - private val oauth2ClientId: String = requireNotNull(config.clientId) - private val oauth2Client: OAuth2Client = OAuth2Client.Builder().apply { - httpClient(client) - endpoint(requireNotNull(config.tokenUrl)) - credentials(oauth2ClientId, requireNotNull(config.clientSecret)) - }.build() + private val oauth2ClientId: String + private val oauth2Client: OAuth2Client - private val baseUrl: HttpUrl = requireNotNull(config.appConfigUrl) + private val baseUrl: HttpUrl private val configPrefix: String? = config.configPrefix private val type: TypeReference = config.type - private val cache: LruCache = LruCache(Duration.ofHours(1), config.cacheSize) + + private val cache: LruCache = LruCache(config.cacheMaxAge, config.cacheSize) private val objectMappers = ObjectMapperCache(config.mapper ?: ObjectMapper()) + private val client: OkHttpClient = config.httpClient ?: OkHttpClient() + + init { + oauth2ClientId = requireNotNull(config.clientId) { "App config client ID missing in $config" } + oauth2Client = OAuth2Client.Builder().apply { + httpClient(client) + endpoint(requireNotNull(config.tokenUrl) { "App config client token URL missing in $config" }) + credentials(oauth2ClientId, requireNotNull(config.clientSecret) { "App config client secret missing in $config" }) + }.build() + baseUrl = requireNotNull(config.appConfigUrl) { "App config client URL missing in $config" } + } - constructor(type: TypeReference, builder: AppConfigClientConfig.() -> Unit) - : this(AppConfigClientConfig(type).apply(builder)) + constructor( + type: TypeReference, + builder: AppConfigClientConfig.() -> Unit + ) : this(AppConfigClientConfig(type).apply(builder)) @Throws(TokenException::class, IOException::class) fun getUserConfig(projectId: String, userId: String): T = cache.computeIfAbsent(userId) { @@ -93,7 +100,8 @@ class AppConfigClient(config: AppConfigClientConfig) { } throw IOException("Unknown response from AppConfig: $responseString") } - return objectMappers.readerFor(ClientConfig::class.java).readValue(body.byteStream()) + return objectMappers.readerFor(ClientConfig::class.java) + .readValue(body.byteStream()) } } } diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt index c9e26ea..3431465 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt @@ -4,13 +4,11 @@ import com.fasterxml.jackson.core.type.TypeReference import okhttp3.HttpUrl import okhttp3.OkHttpClient import com.fasterxml.jackson.databind.ObjectMapper -import org.radarbase.oauth.OAuth2Client -import com.fasterxml.jackson.databind.DeserializationFeature import okhttp3.HttpUrl.Companion.toHttpUrl import java.net.MalformedURLException import java.lang.IllegalArgumentException import java.net.URL -import java.util.* +import java.time.Duration @Suppress("unused") class AppConfigClientConfig(val type: TypeReference) { @@ -24,6 +22,7 @@ class AppConfigClientConfig(val type: TypeReference) { } var mapper: ObjectMapper? = null var configPrefix: String? = null + var cacheMaxAge: Duration = Duration.ofHours(1) var cacheSize = 10_000 fun tokenUrl(url: String?) { @@ -41,4 +40,14 @@ class AppConfigClientConfig(val type: TypeReference) { fun appConfigUrl(url: String?) { appConfigUrl = url?.toHttpUrl() } + + override fun toString(): String = "AppConfigClientConfig(" + + "type=$type, " + + "appConfigUrl=$appConfigUrl, " + + "tokenUrl=$tokenUrl, " + + "clientId=$clientId, " + + "clientSecret=$clientSecret, " + + "configPrefix=$configPrefix, " + + "cacheMaxAge=$cacheMaxAge, " + + "cacheSize=$cacheSize)" } diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt index 0e56230..7482b02 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt @@ -5,7 +5,10 @@ import java.time.Instant import java.util.LinkedHashMap @Suppress("unused") -class LruCache(private val maxAge: Duration, private val capacity: Int): Iterable> { +class LruCache( + private val maxAge: Duration, + private val capacity: Int +): Iterable> { private val map: MutableMap = LinkedHashMap(16, 0.75f, true) @Synchronized From 696987aa46c178a196be027fc49df9cd04299be5 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 10 Feb 2022 10:12:59 +0100 Subject: [PATCH 08/14] Fix docker build --- radar-app-config/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/radar-app-config/Dockerfile b/radar-app-config/Dockerfile index 8458c57..792f7b9 100644 --- a/radar-app-config/Dockerfile +++ b/radar-app-config/Dockerfile @@ -31,7 +31,7 @@ COPY radar-app-config-core/src /code/radar-app-config-core/src COPY radar-expression-lang/src /code/radar-expression-lang/src COPY radar-app-config/src /code/radar-app-config/src -RUN gradle jar --no-watch-fs +RUN gradle :radar-app-config:jar --no-watch-fs FROM azul/zulu-openjdk-alpine:17-jre-headless @@ -49,8 +49,7 @@ WORKDIR /var/lib/radar-app-config COPY --from=builder /code/radar-app-config/build/third-party/* /usr/lib/ COPY --from=builder /code/radar-app-config/build/scripts/* /usr/bin/ -COPY --from=builder /code/radar-expression-lang/build/libs/* /usr/lib/ -COPY --from=builder /code/radar-app-config/build/libs/* /usr/lib/ +COPY --from=builder /code/*/build/libs/* /usr/lib/ USER 101 From 45f10876d3a7505a3101f737ceeb4fa77c1045c3 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 14 Feb 2022 16:52:16 +0100 Subject: [PATCH 09/14] Make it possible to update anohter client or a subset --- .../appconfig/client/AppConfigClient.kt | 35 +++++++++++++------ .../appconfig/client/ObjectMapperCache.kt | 3 ++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt index fe0261e..ca60045 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt @@ -3,6 +3,7 @@ package org.radarbase.appconfig.client import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectReader import okhttp3.HttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -26,6 +27,9 @@ class AppConfigClient(config: AppConfigClientConfig) { private val cache: LruCache = LruCache(config.cacheMaxAge, config.cacheSize) private val objectMappers = ObjectMapperCache(config.mapper ?: ObjectMapper()) private val client: OkHttpClient = config.httpClient ?: OkHttpClient() + private val mapReader: ObjectReader by lazy { + objectMappers.readerFor(object : TypeReference>() {}) + } init { oauth2ClientId = requireNotNull(config.clientId) { "App config client ID missing in $config" } @@ -43,8 +47,8 @@ class AppConfigClient(config: AppConfigClientConfig) { ) : this(AppConfigClientConfig(type).apply(builder)) @Throws(TokenException::class, IOException::class) - fun getUserConfig(projectId: String, userId: String): T = cache.computeIfAbsent(userId) { - fetchConfig(projectId, userId) + fun getUserConfig(projectId: String, userId: String, clientId: String = oauth2ClientId): T = cache.computeIfAbsent(userId) { + fetchConfig(projectId, userId, clientId) .convertToLocalConfig() } @@ -52,12 +56,20 @@ class AppConfigClient(config: AppConfigClientConfig) { fun setUserConfig( projectId: String, userId: String, - config: Map + config: T, + includeKeys: Set? = null, + clientId: String = oauth2ClientId, ): T { - val newConfig: ClientConfig = fetchConfig(projectId, userId) - .copyWithConfig(config) + val stringValue = objectMappers.writerFor(type).writeValueAsString(config) + var result: Map = mapReader.readValue(stringValue) + + if (includeKeys != null) { + result = result.filterKeys { it in includeKeys } + } + val newConfig: ClientConfig = fetchConfig(projectId, userId, clientId) + .copyWithConfig(result.mapValues { it.toString() }) - return putConfig(projectId, userId, newConfig) + return putConfig(projectId, userId, clientId, newConfig) .convertToLocalConfig() .also { cache[userId] = it } } @@ -65,9 +77,10 @@ class AppConfigClient(config: AppConfigClientConfig) { @Throws(IOException::class, TokenException::class) private fun fetchConfig( projectId: String, - userId: String + userId: String, + clientId: String, ): ClientConfig { - val request: Request = buildConfigRequest(projectId, userId) { + val request: Request = buildConfigRequest(projectId, userId, clientId) { get() } return executeConfigRequest(request) @@ -77,9 +90,10 @@ class AppConfigClient(config: AppConfigClientConfig) { private fun putConfig( projectId: String, userId: String, + clientId: String, config: ClientConfig ): ClientConfig { - val request: Request = buildConfigRequest(projectId, userId) { + val request: Request = buildConfigRequest(projectId, userId, clientId) { post( objectMappers.writerFor(ClientConfig::class.java) .writeValueAsString(config) @@ -110,6 +124,7 @@ class AppConfigClient(config: AppConfigClientConfig) { private fun buildConfigRequest( projectId: String, userId: String, + clientId: String, builder: (Request.Builder.() -> Unit) = {} ): Request { val token = oauth2Client.validToken.accessToken @@ -121,7 +136,7 @@ class AppConfigClient(config: AppConfigClientConfig) { addEncodedPathSegment("users") addPathSegment(userId) addEncodedPathSegment("config") - addPathSegment(oauth2ClientId) + addPathSegment(clientId) }.build() ) header("Authorization", "Bearer $token") diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt index d4034e3..44ac77d 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt @@ -18,4 +18,7 @@ class ObjectMapperCache(val mapper: ObjectMapper) { fun writerFor(type: Class<*>): ObjectWriter = writerCache.computeIfAbsent(type) { mapper.writerFor(type) } + + fun writerFor(type: TypeReference<*>): ObjectWriter = + writerCache.computeIfAbsent(type) { mapper.writerFor(type) } } From 2afa334af10519949f6540798fbe1ce1ec9e7951 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 17 Feb 2022 13:46:47 +0100 Subject: [PATCH 10/14] Fix config mapping --- .../kotlin/org/radarbase/appconfig/client/AppConfigClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt index ca60045..919b05d 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt @@ -67,7 +67,7 @@ class AppConfigClient(config: AppConfigClientConfig) { result = result.filterKeys { it in includeKeys } } val newConfig: ClientConfig = fetchConfig(projectId, userId, clientId) - .copyWithConfig(result.mapValues { it.toString() }) + .copyWithConfig(result.mapValues { (_, v) -> v.toString() }) return putConfig(projectId, userId, clientId, newConfig) .convertToLocalConfig() From b059e1ff084bcc1a0c8dfe55766c4b12d136e04f Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 17 Feb 2022 17:13:27 +0100 Subject: [PATCH 11/14] Simplified objectreader/writer setup --- .../appconfig/client/AppConfigClient.kt | 54 ++++++++++++------- .../appconfig/client/ObjectMapperCache.kt | 24 --------- .../radarbase/appconfig/api/ClientConfig.kt | 24 ++++----- 3 files changed, 46 insertions(+), 56 deletions(-) delete mode 100644 radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt index 919b05d..16f0755 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt @@ -3,7 +3,6 @@ package org.radarbase.appconfig.client import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.ObjectReader import okhttp3.HttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -11,6 +10,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.radarbase.appconfig.api.ClientConfig +import org.radarbase.appconfig.api.SingleVariable import org.radarbase.exception.TokenException import org.radarbase.oauth.OAuth2Client import java.io.IOException @@ -25,11 +25,14 @@ class AppConfigClient(config: AppConfigClientConfig) { private val type: TypeReference = config.type private val cache: LruCache = LruCache(config.cacheMaxAge, config.cacheSize) - private val objectMappers = ObjectMapperCache(config.mapper ?: ObjectMapper()) + private val objectMapper = config.mapper ?: ObjectMapper() private val client: OkHttpClient = config.httpClient ?: OkHttpClient() - private val mapReader: ObjectReader by lazy { - objectMappers.readerFor(object : TypeReference>() {}) - } + + private val mapReader by objectMapper.lazyReaderFor(object : TypeReference>() {}) + private val typedConfigReader by objectMapper.lazyReaderFor(config.type) + private val typedConfigWriter by objectMapper.lazyWriterFor(config.type) + private val clientConfigWriter by objectMapper.lazyWriterFor(ClientConfig::class.java) + private val clientConfigReader by objectMapper.lazyReaderFor(ClientConfig::class.java) init { oauth2ClientId = requireNotNull(config.clientId) { "App config client ID missing in $config" } @@ -49,7 +52,7 @@ class AppConfigClient(config: AppConfigClientConfig) { @Throws(TokenException::class, IOException::class) fun getUserConfig(projectId: String, userId: String, clientId: String = oauth2ClientId): T = cache.computeIfAbsent(userId) { fetchConfig(projectId, userId, clientId) - .convertToLocalConfig() + .toTypedConfig() } @Throws(TokenException::class, IOException::class) @@ -60,17 +63,15 @@ class AppConfigClient(config: AppConfigClientConfig) { includeKeys: Set? = null, clientId: String = oauth2ClientId, ): T { - val stringValue = objectMappers.writerFor(type).writeValueAsString(config) - var result: Map = mapReader.readValue(stringValue) - + var clientConfig = config.toClientConfig() if (includeKeys != null) { - result = result.filterKeys { it in includeKeys } + clientConfig = clientConfig.copy(config = clientConfig.config.filter { it.name in includeKeys }) } val newConfig: ClientConfig = fetchConfig(projectId, userId, clientId) - .copyWithConfig(result.mapValues { (_, v) -> v.toString() }) + .with(clientConfig) return putConfig(projectId, userId, clientId, newConfig) - .convertToLocalConfig() + .toTypedConfig() .also { cache[userId] = it } } @@ -95,7 +96,7 @@ class AppConfigClient(config: AppConfigClientConfig) { ): ClientConfig { val request: Request = buildConfigRequest(projectId, userId, clientId) { post( - objectMappers.writerFor(ClientConfig::class.java) + clientConfigWriter .writeValueAsString(config) .toRequestBody(APPLICATION_JSON) ) @@ -114,8 +115,7 @@ class AppConfigClient(config: AppConfigClientConfig) { } throw IOException("Unknown response from AppConfig: $responseString") } - return objectMappers.readerFor(ClientConfig::class.java) - .readValue(body.byteStream()) + return clientConfigReader.readValue(body.byteStream()) } } } @@ -145,8 +145,8 @@ class AppConfigClient(config: AppConfigClientConfig) { } @Throws(JsonProcessingException::class) - private fun ClientConfig.convertToLocalConfig(): T { - val node = objectMappers.mapper.createObjectNode().apply { + private fun ClientConfig.toTypedConfig(): T { + val node = objectMapper.createObjectNode().apply { val values = (defaults?.asSequence() ?: emptySequence()) + config.asSequence() if (configPrefix == null) { values.forEach { (name, value) -> put(name, value) } @@ -156,10 +156,28 @@ class AppConfigClient(config: AppConfigClientConfig) { .forEach { (name, value) -> put(name.substring(configPrefix.length), value) } } } - return objectMappers.readerFor(type).readValue(node.toString()) + return typedConfigReader.readValue(node.toString()) + } + + + @Throws(JsonProcessingException::class) + private fun T.toClientConfig(): ClientConfig { + val stringValue = typedConfigWriter.writeValueAsString(this) + val result: Map = mapReader.readValue(stringValue) + return ClientConfig( + clientId = null, + scope = null, + config = result.map { (k, v) -> SingleVariable(k, v.toString()) }, + ) } companion object { private val APPLICATION_JSON: MediaType = "application/json".toMediaType() + + private fun ObjectMapper.lazyWriterFor(type: TypeReference) = lazy { writerFor(type) } + private fun ObjectMapper.lazyWriterFor(type: Class) = lazy { writerFor(type) } + + private fun ObjectMapper.lazyReaderFor(type: TypeReference) = lazy { readerFor(type) } + private fun ObjectMapper.lazyReaderFor(type: Class) = lazy { readerFor(type) } } } diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt deleted file mode 100644 index 44ac77d..0000000 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/ObjectMapperCache.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.radarbase.appconfig.client - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.ObjectReader -import com.fasterxml.jackson.databind.ObjectWriter -import java.util.* - -class ObjectMapperCache(val mapper: ObjectMapper) { - private val readerCache = IdentityHashMap() - private val writerCache = IdentityHashMap() - - fun readerFor(type: Class<*>): ObjectReader = - readerCache.computeIfAbsent(type) { mapper.readerFor(type) } - - fun readerFor(type: TypeReference<*>): ObjectReader = - readerCache.computeIfAbsent(type) { mapper.readerFor(type) } - - fun writerFor(type: Class<*>): ObjectWriter = - writerCache.computeIfAbsent(type) { mapper.writerFor(type) } - - fun writerFor(type: TypeReference<*>): ObjectWriter = - writerCache.computeIfAbsent(type) { mapper.writerFor(type) } -} diff --git a/radar-app-config-core/src/main/kotlin/org/radarbase/appconfig/api/ClientConfig.kt b/radar-app-config-core/src/main/kotlin/org/radarbase/appconfig/api/ClientConfig.kt index d29ae14..e9a3a00 100644 --- a/radar-app-config-core/src/main/kotlin/org/radarbase/appconfig/api/ClientConfig.kt +++ b/radar-app-config-core/src/main/kotlin/org/radarbase/appconfig/api/ClientConfig.kt @@ -2,7 +2,6 @@ package org.radarbase.appconfig.api import org.radarbase.lang.expression.ResolvedVariable import org.radarbase.lang.expression.Scope -import java.util.stream.Collectors data class ClientConfig( val clientId: String?, @@ -10,6 +9,16 @@ data class ClientConfig( val config: List, val defaults: List? = null ) { + fun with(other: ClientConfig): ClientConfig = ClientConfig( + clientId = clientId, + scope = scope, + config = (config.asSequence() + other.config.asSequence()) + .associateBy { (k) -> k } + .values + .toList(), + defaults = defaults + ) + companion object { fun fromStream( clientId: String, @@ -32,17 +41,4 @@ data class ClientConfig( ) } } - - fun copyWithConfig(config: Map): ClientConfig { - val existingConfig = this.config.asSequence() - val configUpdate = config.entries.asSequence() - .map { (k, v) -> SingleVariable(k, v, null) } - - val newConfig = (existingConfig + configUpdate) - .associateBy { (k) -> k } - .values - .toList() - - return ClientConfig(clientId, scope, newConfig, defaults); - } } From 794f938f2dc6e330215ed34c142ab89c1f033538 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 21 Feb 2022 10:16:15 +0100 Subject: [PATCH 12/14] Fix snyk test --- .github/workflows/scheduled-snyk.yaml | 2 +- .github/workflows/snyk.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scheduled-snyk.yaml b/.github/workflows/scheduled-snyk.yaml index ae49a41..cdc3223 100644 --- a/.github/workflows/scheduled-snyk.yaml +++ b/.github/workflows/scheduled-snyk.yaml @@ -27,7 +27,7 @@ jobs: - name: Run Snyk env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - run: snyk test --all-projects --org=radar-base --configuration-matching='^runtimeClasspath$' --exclude plugins/radar-android-empatica --json-file-output=${{ env.REPORT_FILE }} + run: snyk test --all-projects --org=radar-base --configuration-matching='^runtimeClasspath$' --json-file-output=${{ env.REPORT_FILE }} - name: Report new vulnerabilities uses: thehyve/report-vulnerability@master diff --git a/.github/workflows/snyk.yaml b/.github/workflows/snyk.yaml index 5e7f70f..d69988b 100644 --- a/.github/workflows/snyk.yaml +++ b/.github/workflows/snyk.yaml @@ -26,4 +26,4 @@ jobs: - name: Run Snyk to check for vulnerabilities env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - run: snyk test --all-projects --org=radar-base --fail-on=upgradable --configuration-matching='^runtimeClasspath$' + run: snyk test --all-projects --org=radar-base --fail-on=upgradable --configuration-matching='^runtimeClasspath$' From ac1d93e82bbb000e12f9695c06c6d6768ef31887 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 3 Mar 2022 10:47:03 +0100 Subject: [PATCH 13/14] Add documentation --- radar-app-config-client/README.md | 29 ++++++++++++ .../appconfig/client/AppConfigClient.kt | 45 ++++++++++++++----- .../appconfig/client/AppConfigClientConfig.kt | 18 +++++++- .../radarbase/appconfig/client/LruCache.kt | 36 ++++++++++++++- 4 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 radar-app-config-client/README.md diff --git a/radar-app-config-client/README.md b/radar-app-config-client/README.md new file mode 100644 index 0000000..468fcf4 --- /dev/null +++ b/radar-app-config-client/README.md @@ -0,0 +1,29 @@ +# App config client + +Kotlin client library for the radar-app-config service. It uses OkHttp3 for communication and Jackson JSON parsing. +It may cache values. + +Import it with: + +```gradle +dependencies { + implementation("org.radarbase:radar-app-config-client:") +} +``` + +Example use: + +```kotlin +data class MyConfig(val paramA: String?) + +val appConfigClient = AppConfigClient(object : TypeReference() {}) { + appConfigUrl(appConfigUrl) + tokenUrl(authConfig.tokenUrl) + clientId = authConfig.clientId + clientSecret = authConfig.clientSecret +} + +val myConfig = appConfigClient.getUserConfig(projectId, userId) +val newConfig = updateConfig(myConfig) +appConfigClient.setUserConfig(projectId, userId, newConfig) +``` diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt index 16f0755..e256b11 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClient.kt @@ -15,13 +15,20 @@ import org.radarbase.exception.TokenException import org.radarbase.oauth.OAuth2Client import java.io.IOException +/** + * App config client. The type to serialize with can be constructed in Kotlin as + * `object : TypeReference() {}`. It can be any type as long as a Jackson Object Mapper can serialize and + * deserialize it from a JSON object with contents `{"key1": "value1", ...}`. + * + * @param config configuration to use. + */ @Suppress("unused") class AppConfigClient(config: AppConfigClientConfig) { private val oauth2ClientId: String private val oauth2Client: OAuth2Client private val baseUrl: HttpUrl - private val configPrefix: String? = config.configPrefix + private val configPrefix: String = config.configPrefix ?: "" private val type: TypeReference = config.type private val cache: LruCache = LruCache(config.cacheMaxAge, config.cacheSize) @@ -44,17 +51,39 @@ class AppConfigClient(config: AppConfigClientConfig) { baseUrl = requireNotNull(config.appConfigUrl) { "App config client URL missing in $config" } } + /** + * App config client. Configure it inside a code block. + * + * @param type deserialization type. + */ constructor( type: TypeReference, builder: AppConfigClientConfig.() -> Unit ) : this(AppConfigClientConfig(type).apply(builder)) + /** + * Get the config of given user. This will respond with a cached value if the cache is valid. + * + * @param projectId the project of the user. + * @param userId the ID of the user + * @param clientId the OAuth client ID that config should be fetched for. Defaults to the current client ID. + */ @Throws(TokenException::class, IOException::class) fun getUserConfig(projectId: String, userId: String, clientId: String = oauth2ClientId): T = cache.computeIfAbsent(userId) { fetchConfig(projectId, userId, clientId) .toTypedConfig() } + /** + * Set the config of given user. This will not remove any values on the service but only update ones provided by + * the config. + * + * @param projectId the project of the user. + * @param userId the ID of the user + * @param config new configuration values. + * @param includeKeys if given, only update that subset of keys provided in the config. + * @param clientId the OAuth client ID that config should be fetched for. Defaults to the current client ID. + */ @Throws(TokenException::class, IOException::class) fun setUserConfig( projectId: String, @@ -147,19 +176,13 @@ class AppConfigClient(config: AppConfigClientConfig) { @Throws(JsonProcessingException::class) private fun ClientConfig.toTypedConfig(): T { val node = objectMapper.createObjectNode().apply { - val values = (defaults?.asSequence() ?: emptySequence()) + config.asSequence() - if (configPrefix == null) { - values.forEach { (name, value) -> put(name, value) } - } else { - values - .filter { it.name.startsWith(configPrefix) && it.name.length > configPrefix.length } - .forEach { (name, value) -> put(name.substring(configPrefix.length), value) } - } + ((defaults?.asSequence() ?: emptySequence()) + config.asSequence()) + .filter { it.name.length > configPrefix.length && it.name.startsWith(configPrefix) } + .forEach { (name, value) -> put(name.substring(configPrefix.length), value) } } return typedConfigReader.readValue(node.toString()) } - @Throws(JsonProcessingException::class) private fun T.toClientConfig(): ClientConfig { val stringValue = typedConfigWriter.writeValueAsString(this) @@ -167,7 +190,7 @@ class AppConfigClient(config: AppConfigClientConfig) { return ClientConfig( clientId = null, scope = null, - config = result.map { (k, v) -> SingleVariable(k, v.toString()) }, + config = result.map { (k, v) -> SingleVariable(configPrefix + k, v.toString()) }, ) } diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt index 3431465..133f8cb 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/AppConfigClientConfig.kt @@ -10,19 +10,35 @@ import java.lang.IllegalArgumentException import java.net.URL import java.time.Duration +/** + * App config client configuration. The type to serialize with can be constructed in Kotlin as + * `object : TypeReference() {}`. It can be any type as long as a Jackson Object Mapper can serialize and + * deserialize it from a JSON object with contents `{"key1": "value1", ...}`. + * + * @param type configuration type to serialize and deserialize from. + */ @Suppress("unused") class AppConfigClientConfig(val type: TypeReference) { + /** App-config service URL. */ var appConfigUrl: HttpUrl? = null + /** OAuth 2.0 token endpoint URL. */ var tokenUrl: URL? = null + /** OAuth 2.0 client ID. */ var clientId: String? = null + /** OAuth 2.0 client secret. */ var clientSecret: String? = null + /** Http client. */ var httpClient: OkHttpClient? = null set(value) { field = value?.newBuilder()?.build() } + /** JSON object mapper. Should be loaded with the necessary modules and configuration to serialize the config type. */ var mapper: ObjectMapper? = null - var configPrefix: String? = null + /** Only get configuration options with the given prefix, and strip that prefix before serialization. */ + var configPrefix: String = "" + /** Time to cache each participants data after requesting. */ var cacheMaxAge: Duration = Duration.ofHours(1) + /** Number of per-participant cache items to keep. */ var cacheSize = 10_000 fun tokenUrl(url: String?) { diff --git a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt index 7482b02..b3964ce 100644 --- a/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt +++ b/radar-app-config-client/src/main/kotlin/org/radarbase/appconfig/client/LruCache.kt @@ -4,6 +4,12 @@ import java.time.Duration import java.time.Instant import java.util.LinkedHashMap +/** + * Least recently used cache. It is synchronized. + * + * @param maxAge maximum age that a cache item is considered valid. + * @param capacity number of items in cache. If it is not greater than 0, this cache will not cache anything. + */ @Suppress("unused") class LruCache( private val maxAge: Duration, @@ -11,6 +17,11 @@ class LruCache( ): Iterable> { private val map: MutableMap = LinkedHashMap(16, 0.75f, true) + /** + * Get item from cache. + * @param key cache key. + * @return cached value if available and valid, otherwise `null`. + */ @Synchronized operator fun get(key: K): V? { val value: Node = map[key] ?: return null @@ -21,9 +32,17 @@ class LruCache( return value.value } + /** + * Whether a cache item exists for a given key. + * @param key cache key. + * @return whether a cached value is available, regardless of whether it is still valid. + */ @Synchronized operator fun contains(key: K): Boolean = map.containsKey(key) + /** + * Iterates over all key-value pairs that are valid at the time that [Iterator.hasNext] is called. + */ override operator fun iterator(): Iterator> = object : AbstractIterator>() { val iterator = map.entries.iterator() override fun computeNext() { @@ -40,18 +59,32 @@ class LruCache( } } + /** + * Get a cache item if present and valid, otherwise compute a new value for in the cache. + */ fun computeIfAbsent(key: K, computation: () -> V): V = get(key) - ?: computation().also { set(key, it) } + ?: computation().let { + synchronized(this@LruCache) { + get(key) ?: run { + set(key, it) + it + } + } + } + /** Remove a cache item from the cache. */ @Synchronized fun remove(key: K) { map.remove(key) } + /** Remove a cache item from the cache. */ operator fun minusAssign(key: K) = remove(key) + /** Set a new cache item. */ @Synchronized operator fun set(key: K, value: V) { + if (capacity <= 0) return val oldValue = map.put(key, Node(value)) if (oldValue == null && map.size > capacity) { @@ -61,6 +94,7 @@ class LruCache( } } + /** Purge all expired items from the cache. */ @Synchronized fun purge() { map.values.removeIf { it.isExpired } From 23746905760ac74b63b5d9869f1bad418a796c5b Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 3 Mar 2022 10:48:17 +0100 Subject: [PATCH 14/14] Bump version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3eb02c1..38d7a73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ plugins { allprojects { group = "org.radarbase" - version = "0.4.1-SNAPSHOT" + version = "0.4.1" } val githubRepoName = "RADAR-base/radar-app-config"