diff --git a/.github/ct.yaml b/.github/ct.yaml index 531244f4..ff4f6239 100644 --- a/.github/ct.yaml +++ b/.github/ct.yaml @@ -4,7 +4,9 @@ chart-dirs: - helm chart-repos: - bitnami=https://charts.bitnami.com/bitnami + - elastic=https://helm.elastic.co helm-extra-args: --timeout 600s check-version-increment: true debug: false -validate-maintainers: false \ No newline at end of file +validate-maintainers: false +helm-dependency-extra-args: "--skip-refresh" \ No newline at end of file diff --git a/.github/workflows/lint_test.yaml b/.github/workflows/lint_test.yaml index 1ca99744..20be854e 100644 --- a/.github/workflows/lint_test.yaml +++ b/.github/workflows/lint_test.yaml @@ -22,29 +22,41 @@ jobs: check-latest: true - name: Set up chart-testing - uses: helm/chart-testing-action@v2.3.1 + uses: helm/chart-testing-action@v2.6.1 - name: Run chart-testing (list-changed) id: list-changed run: | changed=$(ct list-changed --config .github/ct.yaml) if [[ -n "$changed" ]]; then - echo "::set-output name=changed::true" + echo "changed=true >> $GITHUB_OUTPUT" fi - name: Run chart-testing (lint) run: ct lint --config .github/ct.yaml - - # deploy-charts-to-kind: - # name: ${{ matrix.environments }} - gen3 data portal build + + # TODO: add back in when we have tests + # deploy-and-test-chart: + # name: Deploy and Test Chart # timeout-minutes: 20 # runs-on: ubuntu-latest - # needs: [get-changes-for-envs] - # if: ${{ needs.get-changes-for-envs.outputs.matrix != '[]' && needs.get-changes-for-envs.outputs.matrix != '' }} # steps: + + # - name: Checkout + # uses: actions/checkout@v2 + # with: + # fetch-depth: 0 + + # - name: Set up Helm + # uses: azure/setup-helm@v3 + + # - name: Set up chart-testing + # uses: helm/chart-testing-action@v2.6.1 + + # - name: Create kind cluster - # uses: helm/kind-action@v1.4.0 - # if: steps.list-changed.outputs.changed == 'true' + # uses: helm/kind-action@v1.8.0 + - # - name: Run chart-testing (install) - # run: ct install + # - name: Run chart install + testing + # run: ct install --charts ./helm/gen3 --config .github/ct.yaml diff --git a/.gitignore b/.gitignore index 66497221..df2d04fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ postgres.txt **/charts/ notes/ -Chart.lock \ No newline at end of file +Chart.lock +.DS_Store +_sample-*/ \ No newline at end of file diff --git a/.secrets.baseline b/.secrets.baseline index 202af15b..fc20cd69 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-02-21T02:49:06Z", + "generated_at": "2023-11-20T21:39:41Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -60,72 +60,138 @@ "results": { "README.md": [ { - "hashed_secret": "ac0fedaac180de6bd70a97b711692a92dade479e", + "hashed_secret": "64ab0c1d3edc1c8c166351207b840ac7b2a90523", "is_secret": false, "is_verified": false, - "line_number": 83, + "line_number": 87, "type": "Secret Keyword" - }, + } + ], + "docs/CONFIGURATION.md": [ { "hashed_secret": "64ab0c1d3edc1c8c166351207b840ac7b2a90523", + "is_secret": true, + "is_verified": false, + "line_number": 135, + "type": "Secret Keyword" + }, + { + "hashed_secret": "9b5925ea817163740dfb287a9894e8ab3aba2c18", + "is_secret": true, + "is_verified": false, + "line_number": 301, + "type": "Secret Keyword" + } + ], + "docs/PREREQUISITES.md": [ + { + "hashed_secret": "ac0fedaac180de6bd70a97b711692a92dade479e", "is_secret": false, "is_verified": false, - "line_number": 112, + "line_number": 94, "type": "Secret Keyword" } ], "docs/databases.md": [ + { + "hashed_secret": "de469a49b80aa4bb9aed52a9eda64dea425dff69", + "is_secret": true, + "is_verified": false, + "line_number": 22, + "type": "Secret Keyword" + }, { "hashed_secret": "ac0fedaac180de6bd70a97b711692a92dade479e", "is_secret": false, "is_verified": false, - "line_number": 22, + "line_number": 38, + "type": "Secret Keyword" + } + ], + "docs/kubernetes-in-docker.md": [ + { + "hashed_secret": "5320294d100314ce19330d99abada8c26c4993a3", + "is_secret": false, + "is_verified": false, + "line_number": 96, + "type": "Secret Keyword" + } + ], + "examples/gke_dev_values.yaml": [ + { + "hashed_secret": "75cb4c02576c9abae38fadc84bc832f2af203f3e", + "is_secret": false, + "is_verified": false, + "line_number": 13, + "type": "Secret Keyword" + } + ], + "examples/gke_values.yaml": [ + { + "hashed_secret": "75cb4c02576c9abae38fadc84bc832f2af203f3e", + "is_secret": true, + "is_verified": false, + "line_number": 14, "type": "Secret Keyword" } ], "helm/arborist/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 32, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 37, + "line_number": 45, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 39, + "line_number": 47, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 58, + "line_number": 65, "type": "Secret Keyword" } ], "helm/audit/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 40, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 48, + "line_number": 53, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 50, + "line_number": 55, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 77, + "line_number": 75, "type": "Secret Keyword" } ], @@ -134,11 +200,18 @@ "hashed_secret": "7c150ec931dbb741d0bfd6c8f4ef914026c0b44b", "is_secret": false, "is_verified": false, - "line_number": 30, + "line_number": 61, "type": "Secret Keyword" } ], "helm/common/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 11, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, @@ -170,35 +243,65 @@ "type": "Secret Keyword" } ], + "helm/dicom-server/README.md": [ + { + "hashed_secret": "b47233f6f28e9716c72d5eba0278edea3a24baad", + "is_secret": false, + "is_verified": false, + "line_number": 39, + "type": "Secret Keyword" + }, + { + "hashed_secret": "3f6d5580af2ddf647ca25346aa6ec9c434577d05", + "is_secret": false, + "is_verified": false, + "line_number": 55, + "type": "Secret Keyword" + } + ], "helm/dicom-server/values.yaml": [ { "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_secret": false, "is_verified": false, - "line_number": 65, + "line_number": 79, "type": "Secret Keyword" } ], "helm/fence/README.md": [ + { + "hashed_secret": "49bed5bac5cc06bafd528df89918bf34973861ec", + "is_secret": false, + "is_verified": false, + "line_number": 93, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 150, + "line_number": 106, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 152, + "line_number": 108, "type": "Secret Keyword" }, { "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 224, + "line_number": 135, + "type": "Secret Keyword" + }, + { + "hashed_secret": "9d8fada0e01336e865c461bb3549084d206fe6da", + "is_secret": false, + "is_verified": false, + "line_number": 181, "type": "Secret Keyword" } ], @@ -262,34 +365,36 @@ "hashed_secret": "5d07e1b80e448a213b392049888111e1779a52db", "is_secret": false, "is_verified": false, - "line_number": 1862, + "line_number": 1916, "type": "Secret Keyword" } ], "helm/gen3/README.md": [ { - "hashed_secret": "4caa5dcab48a481e96f4352e45459c0ecd6f3cf7", + "hashed_secret": "1740c48fa3141d4851b14f97e3bc0f46f7670672", "is_secret": false, "is_verified": false, - "line_number": 65, + "line_number": 127, "type": "Secret Keyword" - }, + } + ], + "helm/gen3/values.yaml": [ { - "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", + "hashed_secret": "9b5925ea817163740dfb287a9894e8ab3aba2c18", "is_secret": false, "is_verified": false, - "line_number": 78, + "line_number": 216, "type": "Secret Keyword" - }, + } + ], + "helm/guppy/README.md": [ { - "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", + "hashed_secret": "39e819806b607b544fec2ea49fa88a7ab81929ca", "is_secret": false, "is_verified": false, - "line_number": 80, + "line_number": 43, "type": "Secret Keyword" - } - ], - "helm/guppy/README.md": [ + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, @@ -306,25 +411,32 @@ } ], "helm/hatchery/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 30, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 36, + "line_number": 47, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 38, + "line_number": 49, "type": "Secret Keyword" }, { - "hashed_secret": "1740c48fa3141d4851b14f97e3bc0f46f7670672", + "hashed_secret": "e94cc2a86b04ad4ddc98fcbf91ed236437939d47", "is_secret": false, "is_verified": false, - "line_number": 50, + "line_number": 57, "type": "Secret Keyword" } ], @@ -333,30 +445,44 @@ "hashed_secret": "9b5925ea817163740dfb287a9894e8ab3aba2c18", "is_secret": false, "is_verified": false, - "line_number": 151, + "line_number": 186, "type": "Secret Keyword" } ], "helm/indexd/README.md": [ + { + "hashed_secret": "0d5cd5f3caaaf8354a6c62816b97bcae006d4bcf", + "is_secret": false, + "is_verified": false, + "line_number": 31, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 40, + "line_number": 48, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 42, + "line_number": 50, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 60, + "line_number": 69, + "type": "Secret Keyword" + }, + { + "hashed_secret": "1cc98556e7b1353c7bd08344f9190808b0d3d6d4", + "is_secret": true, + "is_verified": false, + "line_number": 101, "type": "Secret Keyword" } ], @@ -369,12 +495,12 @@ "type": "Basic Auth Credentials" } ], - "helm/indexd/templates/indexd-secret.yaml": [ + "helm/manifestservice/README.md": [ { - "hashed_secret": "c2dae5a3c7ce218639b38d8a0256f02fe81d439e", + "hashed_secret": "611f2e9064b518afdb23f201321f39029dd28917", "is_secret": false, "is_verified": false, - "line_number": 19, + "line_number": 74, "type": "Secret Keyword" } ], @@ -388,48 +514,69 @@ } ], "helm/metadata/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 49, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 51, + "line_number": 66, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 53, + "line_number": 68, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 78, + "line_number": 87, "type": "Secret Keyword" } ], "helm/peregrine/README.md": [ + { + "hashed_secret": "4e7b6794afbe3027589b92744144f18a3920b115", + "is_secret": false, + "is_verified": false, + "line_number": 32, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 40, + "line_number": 49, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 42, + "line_number": 51, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 60, + "line_number": 67, + "type": "Secret Keyword" + }, + { + "hashed_secret": "7d4e263f1ae83868444f5327219830493a7d1486", + "is_secret": false, + "is_verified": false, + "line_number": 96, "type": "Secret Keyword" } ], @@ -452,25 +599,32 @@ } ], "helm/pidgin/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 36, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 54, + "line_number": 53, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 56, + "line_number": 55, "type": "Secret Keyword" }, { "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", "is_secret": false, "is_verified": false, - "line_number": 74, + "line_number": 67, "type": "Secret Keyword" } ], @@ -479,28 +633,35 @@ "hashed_secret": "eb9739c6625f06b4ab73035223366dda6262ae77", "is_secret": false, "is_verified": false, - "line_number": 23, + "line_number": 37, "type": "Base64 High Entropy String" }, { "hashed_secret": "08eeb737b239bdb7362a875b90e22c10b8826b20", "is_secret": false, "is_verified": false, - "line_number": 27, + "line_number": 41, "type": "Base64 High Entropy String" }, + { + "hashed_secret": "0d5cd5f3caaaf8354a6c62816b97bcae006d4bcf", + "is_secret": false, + "is_verified": false, + "line_number": 42, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 43, + "line_number": 59, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 45, + "line_number": 61, "type": "Secret Keyword" } ], @@ -508,13 +669,13 @@ { "hashed_secret": "08eeb737b239bdb7362a875b90e22c10b8826b20", "is_verified": false, - "line_number": 418, + "line_number": 469, "type": "Base64 High Entropy String" }, { "hashed_secret": "eb9739c6625f06b4ab73035223366dda6262ae77", "is_verified": false, - "line_number": 421, + "line_number": 472, "type": "Base64 High Entropy String" } ], @@ -523,44 +684,51 @@ "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 49, + "line_number": 58, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 51, + "line_number": 60, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 72, + "line_number": 83, "type": "Secret Keyword" } ], "helm/revproxy/README.md": [ + { + "hashed_secret": "5f0d5766b5954edbce68e73920428d26b9a293c8", + "is_secret": false, + "is_verified": false, + "line_number": 29, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 32, + "line_number": 46, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 34, + "line_number": 48, "type": "Secret Keyword" }, { "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", "is_secret": false, "is_verified": false, - "line_number": 59, + "line_number": 73, "type": "Secret Keyword" } ], @@ -574,25 +742,53 @@ } ], "helm/sheepdog/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 41, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 56, + "line_number": 58, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 58, + "line_number": 60, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", "is_secret": false, "is_verified": false, - "line_number": 80, + "line_number": 75, + "type": "Secret Keyword" + }, + { + "hashed_secret": "c2c4e52c03a03ce3efeb21eb202d301018d4548e", + "is_secret": false, + "is_verified": false, + "line_number": 96, + "type": "Secret Keyword" + }, + { + "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", + "is_secret": false, + "is_verified": false, + "line_number": 103, + "type": "Secret Keyword" + }, + { + "hashed_secret": "fa4497447699cdb0a81c66a7f21af28a75170195", + "is_secret": false, + "is_verified": false, + "line_number": 105, "type": "Secret Keyword" } ], @@ -619,82 +815,117 @@ "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_secret": false, "is_verified": false, - "line_number": 197, + "line_number": 229, "type": "Secret Keyword" } ], - "helm/ssjdispatcher/README.md": [ + "helm/sower/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 36, + "type": "Secret Keyword" + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 41, + "line_number": 51, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 43, + "line_number": 53, "type": "Secret Keyword" } ], - "helm/ssjdispatcher/templates/ssjdispatcher-secret.yaml": [ + "helm/sower/templates/pelican-creds.yaml": [ { "hashed_secret": "d2e2ab0f407e4ee3cf2ab87d61c31b25a74085e5", "is_secret": false, "is_verified": false, - "line_number": 23, + "line_number": 13, "type": "Secret Keyword" } ], - "helm/ssjdispatcher/values.yaml": [ + "helm/ssjdispatcher/README.md": [ { - "hashed_secret": "13d9ed7e3d69f1b6330dff80bc4658931708eddc", + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", "is_secret": false, "is_verified": false, - "line_number": 163, + "line_number": 40, "type": "Secret Keyword" - } - ], - "helm/wts/README.md": [ + }, { "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", "is_secret": false, "is_verified": false, - "line_number": 38, + "line_number": 57, "type": "Secret Keyword" }, { "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", "is_secret": false, "is_verified": false, - "line_number": 40, + "line_number": 59, "type": "Secret Keyword" }, { - "hashed_secret": "abb751db44bcfd1bb9d4ad53e40138422abd739e", + "hashed_secret": "0c86d58792b32e1d12af733a0614837ff9002014", "is_secret": false, "is_verified": false, - "line_number": 66, + "line_number": 114, "type": "Secret Keyword" } ], - "sample-values/fence-config.yaml": [ + "helm/ssjdispatcher/templates/ssjdispatcher-secret.yaml": [ { - "hashed_secret": "5d07e1b80e448a213b392049888111e1779a52db", + "hashed_secret": "d2e2ab0f407e4ee3cf2ab87d61c31b25a74085e5", "is_secret": false, "is_verified": false, - "line_number": 560, + "line_number": 23, "type": "Secret Keyword" } ], - "sample-values/values_aws_dev.yaml": [ + "helm/ssjdispatcher/values.yaml": [ { - "hashed_secret": "9b5925ea817163740dfb287a9894e8ab3aba2c18", + "hashed_secret": "13d9ed7e3d69f1b6330dff80bc4658931708eddc", "is_secret": false, "is_verified": false, - "line_number": 34, + "line_number": 215, + "type": "Secret Keyword" + } + ], + "helm/wts/README.md": [ + { + "hashed_secret": "8a10cd156f8f43ec303f885a7985b1cf90635e23", + "is_secret": false, + "is_verified": false, + "line_number": 30, + "type": "Secret Keyword" + }, + { + "hashed_secret": "2546383b95bb44732e9be6a877fd476c0442fdab", + "is_secret": false, + "is_verified": false, + "line_number": 47, + "type": "Secret Keyword" + }, + { + "hashed_secret": "d84ce25b0f9bc2cc263006ae39453efb22cc2900", + "is_secret": false, + "is_verified": false, + "line_number": 49, + "type": "Secret Keyword" + }, + { + "hashed_secret": "f09dd6e359833a12f48c4c4255d6e87a6e55cfe9", + "is_secret": false, + "is_verified": false, + "line_number": 70, "type": "Secret Keyword" } ], diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..c81ac0d3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# Contributing + +We welcome contributions to the gen3-helm repository! This document outlines the guidelines for contributing to this project. + +## Git and GitHub resources + +Before starting a new contribution, you need to be familiar with [Git](https://git-scm.com/) and [GitHub](https://github.com/) concepts like: ***commit, branch, push, pull, remote, fork, repository***, etc. There are plenty of resources online to learn Git and GitHub, for example: +- [Git Guide](https://github.com/git-guides/) +- [GitHub Quick start](https://docs.github.com/en/get-started/quickstart) +- [GitHub on YouTube](https://www.youtube.com/github) +- [Git and GitHub learning resources](https://docs.github.com/en/get-started/quickstart/git-and-github-learning-resources) +- [Collaborating with Pull Requests](https://docs.github.com/en/github/collaborating-with-pull-requests) +- [GitHub Documentation, guides and help topics](https://docs.github.com/en/github) +- And many more... + + +## Before You Begin + + +If you have an idea for a new feature or a bugfix, it is best to communicate with the University of Chicago Center for Translational Data Science (CTDS) developers early. The primary venue for this is the [GitHub issue tracker](https://github.com/uc-cdis/gen3-helm/issues). Browse through existing GitHub issues and if one seems related, comment on it. For more direct communication, CTDS developers are generally available via Slack. + + +## Reporting a New Issue + +If you have identified a potential new issue the first step is to ask the community whether this is something they are familiar with and for which they may already have a solution. The slack channel #gen3_helm_ext is the preferred forum for communication regarding helm. Please inquire in #gen3_community if you would like access ([request access here](https://docs.google.com/forms/d/e/1FAIpQLSczyhhOXeCK9FdVtpQpelOHYnRj1EAq1rwwnm9q6cPAe5a7ug/viewform)). + +If the community has no solution and no existing gen3-helm issue seems appropriate, a new issue can be opened using [this form](https://github.com/uc-cdis/gen3-helm/issues/new). Please be specific in your comment and include instructions on how to reproduce the issue. Please also make sure to add a short descriptive title. + +## How to Contribute + +All changes to the gen3-helm repository should be made through pull requests. + +1. Fork the [gen3-helm repository](https://github.com/uc-cdis/gen3-helm) on GitHub to make your changes. + +4. Run the relevant tests for the features added or bugs fixed by your pull request. + +5. Write a descriptive commit message. + +6. Commit and push your changes to your fork. + +7. Open a pull request with these changes. + +8. Your pull request will be reviewed by a project maintainer and merged if it is deemed appropriate. + +## Style Guidelines + +### Helm + +- `gen3-helm` follows [General Conventions](https://helm.sh/docs/chart_best_practices/) for helm charts. + +## Documentation + +Documentation is found in the ``docs/`` directory. + +The documentation source files are written in [Markdown](https://daringfireball.net/projects/markdown/syntax) format. + +Each chart has its own README.md that is automatically built with [helm-docs](https://github.com/norwoodj/helm-docs). This happens in the pre-commit so make sure to check in all the changed files. + +## Helm chart release strategy + +It is important to understand that when a branch is merged into the main branch, a GitHub action will generate a new helm chart release if the helm chart version in the chart.yaml file has been incremented. Consider the following example where a change to the Helm chart has been made and the contributor wants a new version to be released: + +The original Chart.yaml file: + + ```yaml + apiVersion: v2 + name: Sheepdog + description: A Helm chart for Kubernetes + type: application + version: 0.1.0 + ``` + +If a modification to the Helm chart is made (an update to the values.yaml file for instance) the version in Chart.yaml is incremented to `0.2.0`: + + ```yaml + apiVersion: v2 + name: Sheepdog + description: A Helm chart for Kubernetes + type: application + version: 0.2.0 # version updates to 0.2.0 + ``` + +Once the associated branch is merged into the main branch, the GitHub action packages and publishes an artifact, making it available for consumption. The release name is based off the 'name' field and the 'version' field in the Chart.yaml file. Given the example above, GitHub action will produce a release called `sheepdog-0.2.0`. + + +## Branch Naming Conventions + +Branches are named as `type/scope`, and commit messages are written as `type(scope): explanation`, where + +- `scope` identifies the thing that was added or modified, +- `explanation` is a brief description of the changes in imperative present tense (such as “add function to _”, not “added function”), +- and `type` is defined as: + ``` + type = "chore" | "docs" | "feat" | "fix" | "refactor" | "style" | "test" + ``` + +Some example branch names: + +- `refactor/db-calls` +- `test/user` +- `docs/deployment` + +Some example commit messages: + +- `fix(scope): remove admin scope from client` +- `feat(project_members): list all members given project` +- `docs(generation): fix generation script and update docs` + +## Pull Requests (PRs) + + +Before submitting a PR for review, try to make sure you’ve accomplished these things: + +The PR: +- contains a brief description of what it changes and/or adds +- passes status checks +- If there are changes to the charts, it bumps the chart versions + + +To merge the PR: + +If the branch now has conflicts with the master branch, follow these steps to update it: + +```bash +git checkout master +git pull origin master +git checkout $YOUR_BRANCH_NAME +git merge master +git commit +# The previous command should open an editor with the default merge commit +# message; simply save and exit +git push + +``` diff --git a/README.md b/README.md index aef945f3..5a63cef1 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,7 @@ Helm charts for deploying [Gen3](https://gen3.org) on any kubernetes cluster. -# Deployment instructions -For a full set of configuration options see the [README.md for gen3](./helm/gen3/README.md) - -To see documentation around setting up gen3 developer environments see [gen3_developer_environments.md](./docs/gen3_developer_environments.md) +# Deploying gen3 with helm ## TL;DR ``` @@ -17,23 +14,36 @@ helm repo update helm upgrade --install gen3 gen3/gen3 -f ./values.yaml ``` -Use the following as a template for your `values.yaml` file for a minimum deployment of gen3 using these helm charts. +Assuming you already have the [prerequisites](./docs/PREREQUISITES.md) installed and configured, you can deploy Gen3 with the helm command. +> **Warning** +> The default Helm chart configuration is not intended for production. The default chart creates a proof of concept (PoC) implementation where all Gen3 services are deployed in the cluster, including postgres and elasticsearch. For production deployments, you must follow the [Production/Cloud Native/Hybrid architecture](./docs/PRODUCTION.md) -```yaml -global: - hostname: example-commons.com -fence: - FENCE_CONFIG: - OPENID_CONNECT: - google: - client_id: "insert.google.client_id.here" - client_secret: "insert.google.client_secret.here" -``` +For a production deployment, you should have strong working knowledge of Kubernetes. This method of deployment has different management, observability, and concepts than traditional deployments. + +In a production deployment: + +- The stateful components, like PostgreSQL or Elasticsearch, must run outside the cluster on PaaS or compute instances. This configuration is required to scale and reliably service the variety of workloads found in production Gen3 environments. + +- You should use Cloud PaaS for PostgreSQL, Elasticsearch, and object storage. + + +## Configuration + +For a full set of configuration options see the [CONFIGURATION.md](./docs/CONFIGURATION.md) for a more in depth instructions on how to configure each service. + +There's also an auto-generated table of basic configuration options here: + +[README.md for gen3 chart](./helm/gen3/README.md) (auto-generated documentation) or + + +To see documentation around setting up gen3 developer environments see [gen3_developer_environments.md](./docs/gen3_developer_environments.md) + + +Use the following as a template for your `values.yaml` file for a minimum deployment of gen3 using these helm charts. -This is to have a gen3 deployment with google login. You may also use MOCK_AUTH using the following config. NB! This will bypass any login and is only recommended for testing environments ```yaml @@ -42,15 +52,18 @@ global: fence: FENCE_CONFIG: - # if true, will automatically login a user with username "test" - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - MOCK_AUTH: true + # Any fence-config overrides here. ``` + ## Selective deployments -All service helm charts are sub-charts of the gen3 chart (which acts as an umbrella chart) -To enable or disable a service you can add this pattern to your `values.yaml` +All gen3 services are sub-charts of the gen3 chart (which acts as an umbrella chart). + +For your specific installation of gen3, you may not require all our services. + + +To enable or disable a service you can use this pattern in your `values.yaml` ```yaml fence: @@ -60,50 +73,12 @@ wts: enabled: false ``` - -## Prerequisites - -### Kubernetes cluster -Any kubernetes cluster _should_ work. We are testing with EKS, AKS, GKE and Rancher Desktop. - -It is suggested to use [Rancher Desktop](https://rancherdesktop.io/) as Kubernetes on your laptop, especially on M1 Mac's. You also get ingress and other benefits out of the box. - -### Postgres -We need a postgres database. For development/CI clusters an instance of postgres is deployed and automatically configured for you. - -For production environments please fill out these values and provide a master password for postgres - -``` -global: - postgres: - db_create: true - master: - host: insert.postgres.hostname.here - username: postgres - password: - port: "5432" -``` - - -### Login Options +## Gen3 Login Options Gen3 does not have any IDP, but can integrate with many. We will cover Google login here, but refer to the fence documentation for additional options. TL/DR: At minimum to have google logins working you need to set these settings in your `values.yaml` file ``` -global: - aws: - # If you're deploying to an EKS set this to true. This will annotate ingress/service accounts appropriately. - # In the future we will be adding support for GKE/AKS using same method. - enabled: true - aws_access_key_id: - aws_secret_access_key: - postgres: - master: - host: "rds.host.com" - username: "postgres" - password: "test" - port: "5432" fence: FENCE_CONFIG: OPENID_CONNECT: @@ -134,7 +109,7 @@ For `"Authorized redirect URIs"` add `https:///user/login/google/logi After configuration is complete, take note of the client ID that was created. You will need the client ID and client secret to complete the next steps. # Production deployments -For production deployments you have to use an external postgres server and elasticsearch server. +Please read [this](./docs/PRODUCTION.md) for more details on production deployments. NOTE: Gen3 helm charts are currently not used in production by CTDS, but we are aiming to do that soon and will have additional documentation on that. @@ -142,6 +117,9 @@ NOTE: Gen3 helm charts are currently not used in production by CTDS, but we are For local development you must be connected to a kubernetes cluster. As referenced above in the section `Kubernetes cluster` we recommend using [Rancher Desktop](https://rancherdesktop.io/) as Kubernetes on your local machine, especially on M1 Mac's. You also get ingress and other benefits out of the box. +> **Warning** +> If you are using Rancher Desktop you need to increase the vm.max_map_count as outlined [here](https://docs.rancherdesktop.io/how-to-guides/increasing-open-file-limit/) + 1. Clone the repository 2. Navigate to the `gen3-helm/helm/gen3` directory and run `helm dependency update` 3. Navigate to the back to the `gen3-helm` directory and create your values.yaml file. See the `TL;DR` section for a minimal example. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 00000000..6ba633f8 --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,608 @@ +# Gen3 Services + +# Ambassador + +## What Does it Do + +Ambassador is an envoy proxy. We use this service to direct traffic toward our workspaces, hatchery and jupyter containers. + +## How to Configure it + +For a full set of configuration see the [helm README.md for ambassador](../helm/ambassador/README.md) or read the [values.yaml](../helm/ambassador/values.yaml) directly + +Example configuration using gen3 umbrella chart: + +```yaml +ambassador: + # Whether or not to deploy the service or not + enabled: true + + # What image/ tag to pull + image: + repository: quay.io/datawire/ambassador + tag: "1.4.2" + pullPolicy: Always +``` + + +## Extra Information + +Ambassador is only necessary if there is a hatchery deployment, as this is used as an envoy proxy primarily for workspaces. This may change in the future. + +--- + +# aws-es-proxy + +## What Does it Do + +The aws-es-proxy is a proxy for hitting the elasticsearch service running in AWS. It is required for guppy, ETL and metadata, if you are running managed elasticsearch in AWS, as they leverage this pod to talk to elasticsearch. + +## How to Configure it + + +For a full set of configuration see the [helm README.md for aws-es-proxy](../helm/aws-es-proxy/README.md) or read the [values.yaml](../helm/aws-es-proxy/values.yaml) directly + + +Some important configuration items for `aws-es-proxy` in helm: + +```yaml +# -- AWS user to use to connect to ES +aws-es-proxy: + # Whether or not to deploy the service or not + enabled: true + + # What image/ tag to pull + image: + repository: + tag: + + # AWS secrets + secrets: + awsAccessKeyId: "" + awsSecretAccessKey: "" + + # Elasticsearch endpoint in AWS + esEndpoint: test.us-east-1.es.amazonaws.com +``` + + +## Extra Information + +This pod can also be used to make direct queries to elastic search. If you know you want to make a manaul query to elastic search. You can exec into the aws-es-proxy pod and run the following, filling in the appropriate endpoint you want to hit to query elasticsearch. + +```bash +curl http://localhost:9200/ +``` + +--- + +# Arborist + +## What Does it Do + +Arborist is the authorization service. It works with fence to assign authortizations to a user based on their authentication information. Information around user authorizations are set within a useryaml, or telemetry file for dbgap authorized users, and put into the arborist db during usersync. + +## How to configure it + +For a full set of configuration see the [helm README.md for arborist](../helm/arborist/README.md) or read the [values.yaml](../helm/arborist/values.yaml) directly + +Some configuration options include: +- postgres configuration +- image repo/ tag + + +```yaml +arborist: + # Whether or not to deploy the service or not + enabled: true + + # What image/ tag to pull + image: + tag: + repository: +``` + +## Extra Information + +Common arborist database queries can be found [here](https://github.com/uc-cdis/cdis-wiki/blob/master/dev/gen3-sql-queries.md#arborist-database). + +--- + +# Fence + +## What Does it Do + +Fence is a core service for a gen3 datacommons which handles authentication. It is necessary for a commons to run and will handle authentication on the /login endpoint as well as creating presigned url's in the presigned-url-fence pods. + +## How to Configure it + + +```yaml +fence: + # Whether or not to deploy the service or not + enabled: true + + # What image/ tag to pull + image: + tag: + repository: + + # FENCE_CONFIG + FENCE_CONFIG: + OPENID_CONNECT: + google: + client_id: "insert.google.client_id.here" + client_secret: "insert.google.client_secret.here" + + # -- (string) USER YAML. Passed in as a multiline string. + USER_YAML: | + + + +``` + +You need to ensure a proper working fence-config file. Fence is highly configurable and a lot of config is commons specific, but some important fields to configure are as follows. + +1. BASE_URL + * This should be (the url of the commons)/user. +2. DB + * This should contain the psql connection string, which should contain the correct database, user, password and hostname. +3. OPENID_CONNECT + * This is where different IdP's can be configured. To be able to leverage an IdP as a login option you need to add the client id's/secrets and any other necesary config to the predefined blocks. +4. ENABLED_IDENTITY_PROVIDERS/LOGIN_OPTIONS + * Use one of these blocks to enable/configure buttons for logging into the IdP's defined in the OPENID_CONNECT block. +5. DEFAULT_LOGIN_IDP/DEFAULT_LOGIN_URL + * These blocks will define the default login option, which will be used by most external oidc clients. +6. dbGaP + * This will be used to connect to an sftp server which will contain telemetry files for usersync. Is necessary for setting up authorizations outside of the useryaml. +7. AWS_CREDENTIALS/S3_BUCKETS/DATA_UPLOAD_BUCKET + * The AWS_CREDENTIALS block will define credentials for service accounts used to access s3 buckets. The s3 buckets are defined in the S3_BUCKETS block, which will reference a credential in the AWS_CREDENTIALS block. The DATA_UPLOAD_BUCKET block defines the data upload bucket, which is the bucket used in the data upload flow, to upload files to a commons. +8. CIRRUS_CFG + * If google buckets are used you need to configure this block. It is used to setup the google bucket workflow, which essentially creates google users and google bucket access groups, which get filled with users and added to bucket policies to allow implicit access to users. + +For more infomation see [this](https://github.com/uc-cdis/fence/blob/master/fence/config-default.yaml) + + +For user.yaml see this how to construct one properly. This will control access to your data commons: + +https://github.com/uc-cdis/fence/blob/master/docs/user.yaml_guide.md + +## Extra Information + +### Fence Pods + +Fence is split into 2 deployments. There is the regular fence deployment which handles commons authentication. We also split the presigned url feature of fence into a seperate deployment, the presigned-url-fence deployment. They will both get setup/deployed with a gen3 installation. + +### Troubleshooting Fence + +There are some commons sql queries that can be found [here](https://github.com/uc-cdis/cdis-wiki/blob/master/dev/gen3-sql-queries.md#fence-database). + +### Setting up OIDC clients + +OIDC clients are used by applications to authenticate to fence. Many times this is external users to setup apps which leverage gen3 and an OIDC will have to be client will need to be setup for them. After creation, the client_id/secret will need to be shared with the application owner. To create these clients you will need to exec into a fence container and run the [following commands](https://github.com/uc-cdis/fence#register-oauth-client). + + +--- + +# Guppy + +## What Does it Do + +Guppy is used to render the explorer page. It uses elastic search indices to render the page. + +## How to Configure it + + +For a full set of configuration see the [helm README.md for guppy](../helm/guppy/README.md) or read the [values.yaml](../helm/guppy/values.yaml) directly + + +There is also config that needs to be set within the global block around the tier access level, defining how the explorer page should handle displaying unauthorized files, and the limit to how far unauthroized user can filter down files. Last there is a guppy block that needs to be configured with the elastic search indices guppy will use to render the explorer page. + +``` +global: + tierAccessLevel: "(libre|regular|private)" + +guppy: + # -- (int) Only relevant if tireAccessLevel is set to "regular". + # The minimum amount of files unauthorized users can filter down to + tierAccessLimit: "1000" + + # -- (list) Elasticsearch index configurations + indices: + - index: dev_case + type: case + - index: dev_file + type: file + + # -- (string) The Elasticsearch configuration index + configIndex: dev_case-array-config + # -- (string) The field used for access control and authorization filters + authFilterField: auth_resource_path + # -- (bool) Whether or not to enable encryption for specified fields + enableEncryptWhitelist: true + # -- (string) A comma-separated list of fields to encrypt + encryptWhitelist: test1 + + + # -- (string) Elasticsearch endpoint. + # defaults to "elasticsearch:9200" + esEndpoint: "" +``` + + +You will also need a mapping file to map the fields you want to pull from postgres into the elasticsearch indices. There are too many fields to describe here, but an example mapping file can be found [here](https://github.com/uc-cdis/cdis-manifest/blob/master/gen3.biodatacatalyst.nhlbi.nih.gov/etlMapping.yaml). + +Last, guppy works closely with portal to render the explorer page. You will need to ensure a proper [dataExplorer block](https://github.com/uc-cdis/cdis-manifest/blob/master/gen3.biodatacatalyst.nhlbi.nih.gov/portal/gitops.json#L212) is setup within the gitops.json file, referencing fields that have been pulled from postgres into the elasticsearch indices. + +## Extra Information + +Guppy relies on indices being created to run, if there are no indices created guppy will fail to start up. + +To create these indices you can run etl, however a valid ETL mapping file needs to be created and data needs to be submitted to the commons. + + +--- +# Hatchery + +## What Does it Do + +Hatchery is used to create workspaces. It contains information about workspaces images and resources set around those images to run. + +## How to Configure it + + +For a full set of configuration see the [helm README.md for hatchery](../helm/hatchery/README.md) or read the [values.yaml](../helm/hatchery/values.yaml) directly + + +``` +hatchery: + enabled: true + image: + repository: + tag: + + + # -- (map) Hatchery sidcar container configuration. + hatchery: + sidecarContainer: + cpu-limit: '0.1' + memory-limit: 256Mi + image: quay.io/cdis/ecs-ws-sidecar:master + + env: + NAMESPACE: "{{ .Release.Namespace }}" + HOSTNAME: "{{ .Values.global.hostname }}" + + args: [] + + command: + - "/bin/bash" + - "./sidecar.sh" + + lifecycle-pre-stop: + - su + - "-c" + - echo test + - "-s" + - "/bin/sh" + - root + + containers: + - target-port: 8888 + cpu-limit: '1.0' + memory-limit: 2Gi + name: "(Tutorials) Example Analysis Jupyter Lab Notebooks" + image: quay.io/cdis/heal-notebooks:combined_tutorials__latest + env: + FRAME_ANCESTORS: https://{{ .Values.global.hostname }} + args: + - "--NotebookApp.base_url=/lw-workspace/proxy/" + - "--NotebookApp.default_url=/lab" + - "--NotebookApp.password=''" + - "--NotebookApp.token=''" + - "--NotebookApp.shutdown_no_activity_timeout=5400" + - "--NotebookApp.quit_button=False" + command: + - start-notebook.sh + path-rewrite: "/lw-workspace/proxy/" + use-tls: 'false' + ready-probe: "/lw-workspace/proxy/" + lifecycle-post-start: + - "/bin/sh" + - "-c" + - export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; + ln -s /data /home/$IAM/pd/; true + user-uid: 1000 + fs-gid: 100 + user-volume-location: "/home/jovyan/pd" + gen3-volume-location: "/home/jovyan/.gen3" +``` + + +## Extra Information + +--- + +# Indexd + +## What Does it Do + +Indexd is a core service of the commons. It is used to index files within the commons, to be used by fence to download data. + +## How to Configure it + +For a full set of configuration see the [helm README.md for indexd](../helm/indexd/README.md) or read the [values.yaml](../helm/indexd/values.yaml) directly + + +```yaml +indexd: + enabled: true + + image: + repository: + tag: + + # default prefix that gets added to all indexd records. + defaultPrefix: "TEST/" + + # Secrets for fence and sheepdog to use to authenticate with indexd. + # If left blank, will be autogenerated. + secrets: + userdb: + fence: + sheepdog: +``` + +## Extra Information + +Indexd is used to hold information regarding files in the commons. We can index any files we want, but should ensure that bucket in indexd are configured within fence, so that downloading the files will work. To index files We have a variety of tools. First, data upload will automatically create indexd records for files uploaded. If we want to index files from external buckets we can also use [indexd-utils](https://github.com/uc-cdis/indexd_utils), or if the commons has dirm setup, create a manifest and upload it to the /indexing endpoint of a commons. From there GUID's will be created and/or assigned to objects. You can view the information about the records by hitting the (commons url)/index/(GUID) endpoint. To test that the download works for these files you will want to hit the (commons url)/user/data/download/(GUID) endpoint, while ensuring you user has the proper access to the ACL/Authz assigned to the indexd record. + +# Manifestservice + +## What Does it Do + +The manifestservice is used by the workspaces to mount files to a workspace. Workspace pods get setup with a sidecar container which will mount files to the data directory. This is used so that users can access files directly on the worskpace container. The files pulled are defined by manifests, created through the export to workspace button in the explorer page. These manifests live in an s3 bucket which the manifestservice can query. + +## How to Configure it + + + +## Extra Information + +--- +# Metadata + +## What Does it Do + +The Metadata Service provides an API for retrieving JSON metadata of GUIDs. It is a flexible option for "semi-structured" data (key:value mappings). + +The GUID (the key) can be any string that is unique within the instance. The value is the metadata associated with the GUID, it’s a JSON blob whose structure is not enforced on the server side. + + + +## How to Configure it + + +``` +manifestservice: + enabled: true + + manifestserviceG3auto: + hostname: testinstall + # -- (string) Bucket for the manifestservice to read and write to. + bucketName: testbucket + # -- (string) Directory name to use within the s3 bucket. + prefix: test + # -- (string) AWS access key. + awsaccesskey: "" + # -- (string) AWS secret access key. + awssecretkey: "" +``` + +## Extra Information + + +--- +# Peregrine + +## What Does it Do + +The peregrine service is used to query data in postgres. It works similar to guppy, but relies on querying postgres directly. It will create the charts on the front page of the commons, as well as the /query endpoint of a commons. + +## How to Configure it + +To configure peregrine we require an entry in the versions block. It also requires a dictionary in the global block. + + +```yaml +``` + + +## Extra Information + + +--- + +# Portal + +## What Does it Do + +Portal is a core service that renders the complete commons webpage, it is the front end service. + +## How to Configure it + +To configure portal we require an entry in the versions block. The portal_app also needs to be defined in the global block. Gitops sets to use the files in the ~/cdis-manifest/(commons url)/ portal directory, dev is the common setup for development environments and [there are default gitops.json](https://github.com/uc-cdis/data-portal/tree/master/data/config) files for most commons that the portal app can be set to. + +```yaml +portal: + enabled: true + + gitops: + # -- (string) multiline string - gitops.json + json: | + {} + # -- (string) - favicon in base64 + favicon: "" + # -- (string) - multiline string - gitops.css + css: | + /* gitops default css */ + # -- (string) - logo in base64 + logo: "" + # -- (string) - createdby.png - base64 + createdby: "" + sponsors: +``` + + +To do this you can follow the example [here](https://github.com/uc-cdis/data-portal/blob/master/docs/portal_config.md). + +Portal can also be configured with different images and icons by updating the values, similar to [this](https://github.com/uc-cdis/cdis-manifest/tree/master/gen3.biodatacatalyst.nhlbi.nih.gov/portal). + +## Extra Information + +--- +# Revproxy + +## What Does it Do + +Revproxy is a core service to a commons which handles networking within the kuberentes cluster. + +## How to Configure it + + + +## Extra Information + +Revproxy is essentially an nginx container, which contains informtation about the endpoints within the cluster. There needs to be an endpoint setup for revproxy to be able to send traffic to it and start normally. Because we have many services that may or may not be setup, we only configure revproxy with the services that are deployed to a commons. The kube-setup-revproxy script will look at current deployments and add configuration files from [here](https://github.com/uc-cdis/cloud-automation/tree/master/kube/services/revproxy/gen3.nginx.conf) to the pod. So if a new service is added, you will need to run kube-setup-revproxy to setup the endpoint. + +--- + +# Sheepdog + +## What Does it Do + +Sheepdog is a core service that handles data submission. Data gets submitted to the commons, using the dictionary as a schema, which is reflected within the sheepdog database. + +## How to Configure it + + +## Extra Information +--- +# Sower + +## What Does it Do + +Sower is a job dispatching service. Jobs are configured with .Values.sowerConfig and sower handles dispatching the jobs. + +## How to Configure it + +```yaml +sower: + enabled: true + sowerConfig: + - name: pelican-export + action: export + container: + name: job-task + image: quay.io/cdis/pelican-export:master + pull_policy: Always + env: + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: GEN3_HOSTNAME + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: ROOT_NODE + value: subject + volumeMounts: + - name: pelican-creds-volume + readOnly: true + mountPath: "/pelican-creds.json" + subPath: config.json + - name: peregrine-creds-volume + readOnly: true + mountPath: "/peregrine-creds.json" + subPath: creds.json + cpu-limit: '1' + memory-limit: 12Gi + volumes: + - name: pelican-creds-volume + secret: + secretName: pelicanservice-g3auto + - name: peregrine-creds-volume + secret: + secretName: peregrine-creds + restart_policy: Never + - name: pelican-export-files + action: export-files + container: + name: job-task + image: quay.io/cdis/pelican-export:master + pull_policy: Always + env: + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: GEN3_HOSTNAME + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: ROOT_NODE + value: file + - name: EXTRA_NODES + value: '' + volumeMounts: + - name: pelican-creds-volume + readOnly: true + mountPath: "/pelican-creds.json" + subPath: config.json + - name: peregrine-creds-volume + readOnly: true + mountPath: "/peregrine-creds.json" + subPath: creds.json + cpu-limit: '1' + memory-limit: 12Gi + volumes: + - name: pelican-creds-volume + secret: + secretName: pelicanservice-g3auto + - name: peregrine-creds-volume + secret: + secretName: peregrine-creds + restart_policy: Never +``` + +## Extra Information --> \ No newline at end of file diff --git a/docs/INGRESS.md b/docs/INGRESS.md new file mode 100644 index 00000000..c51f74d6 --- /dev/null +++ b/docs/INGRESS.md @@ -0,0 +1,35 @@ +# Ingress in Gen3 + +# Dev +if `global.dev` is set to true, a very basic ingress is created, that works out of the box with `traefik` service that is included in `Rancher-Desktop` + +# AWS + +if `global.aws.` is set to true, there is a special ingress resource that will be created, that has prepoulated annotations for the `alb-controller` to create an alb with similar settings as `cloud-automation`. + +# Custom Ingress + +There is a custom ingress that can be deployed and manipulated. This is a default helm ingress resource, and can be enabled using values like the ones below: + + +``` +revproxy: + ingress: + # Enable the custom ingress resource included by helm. Add any configurations as needed. + enabled: true + # Any annotations that needs to be passed to the ingress resource + annotations: + hosts: + # Replace with your hostname + - host: qureshi.planx-pla.net + paths: + - path: / + pathType: Prefix + tls: + # this is the secret generated by the cert and key from global.tls + # if you have your own secret, reference that. + - secretName: gen3-certs + hosts: + # Replace with your hostname + - qureshi.planx-pla.net +``` \ No newline at end of file diff --git a/docs/PREREQUISITES.md b/docs/PREREQUISITES.md new file mode 100644 index 00000000..bbdda80e --- /dev/null +++ b/docs/PREREQUISITES.md @@ -0,0 +1,96 @@ +# Pre-Requisites + +Before deploying the Gen3 application using Helm, ensure that the following prerequisites are met: + +- Kubernetes cluster with minimum version 1.21. We use [Amazon EKS](#) for our production deployments + +- + +- Postgres 13. + + - We use Amazon aurora serverless v2 for our production deployments. + - **Note:** Managing a database on Kubernetes can be a complex topic, and may result in data loss. We recommend using a managed database service outside of Kubernetes. In production, we use Amazon Aurora Serverless V2 or RDS for Postgres. + - It is possible for development purposes to run Postgres on kubernetes, if you set `global.dev=true` in your `Values.yaml` file, the postgres will be deployed. See [this document](docs/databases.md) for more information on running postgres on kubernetes + + + +- Elasticsearch version 6.8 + - **Note:** Managing elasticsearch on Kubernetes can be a complex topic, and may result in data loss. We recommend using a managed elasticsearch service outside of Kubernetes. In production, we use Amazon Opensearch service for Elasticsearch. + - We are working on supporting newer versions of elasticsearch in gen3, and should soon support ES7/8, but as of now we require version 6.8 + + + + +## Prerequisites + +### Kubernetes cluster +Any kubernetes cluster _should_ work. We are testing with EKS, AKS, GKE and Rancher Desktop. + +It is suggested to use [Rancher Desktop](https://rancherdesktop.io/) as Kubernetes on your laptop, especially on M1 Mac's. You also get ingress and other benefits out of the box. + + +### Postgres + +We recommend managing postgres outside of Gen3 deployments, so your deployments are stateless, and the state of gen3 is managed outside of the helm deployments. This way you may upgrade, or take down the helm deployments, and can restore the state of your gen3 deployment. + +However, there is a possibility to run postgres on kubernetes, alongside gen3 for development purposes. + +To deploy `postgres` and `elasticsearch` that is bundled with gen3 helm charts set the `global.dev` to true. By default these will run with no persistence storage enabled, and are mostly for CI/Development environments. + +If you want to enable persistence for postgres add the following to your values.yaml file for the gen3 chart + +``` +postgresql: + primary: + persistence: + enabled: true +``` + +This will create a [PVC]() for the postgres container. Unfortunately, helm [does not delete PVC on uninstall](https://github.com/helm/helm/issues/5156), so if you enable persistence you might have to manually clean this up between installs by running the following command: + +```bash +kubectl delete pvc data--postgresql-0 +``` + + +**NOTE**: Gen3 will autogenerate the secrets for postgres if you delete and re-install, unless the credentials for each service are supplied via the values.yaml + +In cases where you are using persistent postgres, provide the postgres username and password explicitely using the values.yaml file, so the gen3 deployments will skip generating those credentials. + +Example: + +``` +arborist: + postgres: + dbCreate: true + username: gen3_arborist + password: + +(Repeat for all services) +``` + + + + + + +For a detailed description of each service and it's configuration options see [CONFIGURATION.md](./docs/CONFIGURATION.md) for more information. + + + +We need a postgres database. + +For development/CI clusters an instance of postgres is deployed and automatically configured for you. + +For production environments please provision postgres outside of helm, and fill out these values to provide a master password for postgres. + +``` +global: + postgres: + dbCreate: true + master: + host: insert.postgres.hostname.here + username: postgres + password: + port: "5432" +``` diff --git a/docs/PRODUCTION.md b/docs/PRODUCTION.md new file mode 100644 index 00000000..01b85073 --- /dev/null +++ b/docs/PRODUCTION.md @@ -0,0 +1,15 @@ +# Gen3 using helm in production + + +The postgres and helm charts are included as conditionals in the Gen3 [umbrella chart](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies) + +``` +- name: elasticsearch + version: "0.1.3" + repository: "file://../elasticsearch" + condition: global.dev +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: global.dev + ``` diff --git a/docs/SECRETS.md b/docs/SECRETS.md new file mode 100644 index 00000000..1831d49c --- /dev/null +++ b/docs/SECRETS.md @@ -0,0 +1,2 @@ +# Gen3 Secrets +TBD \ No newline at end of file diff --git a/docs/databases.md b/docs/databases.md index 47d784cb..2a03ecba 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -1,21 +1,37 @@ # Databases in gen3 helm charts -This document will describe how databases are provisioned in gen3 when deploying with helm charts +This document will describe how databases are provisioned, and used in gen3 when deploying gen3 with helm charts. -## Database credentials -The detault behaviour of gen3 helm charts is to auto-generate database credentials and save them as kubernetes secrets. +We hihgly recommend the use of a managed postgres service such as AWS RDS/Aurora, or manage postgres outside of the helm deployment, when deploying gen3 to production environments. -Each service then consumes this same secret and mounts them as ENV vars to access databases. +The bundled version of postgres, that is used for development purposes, is deployed using this helm chart https://bitnami.com/stack/postgresql/helm -You can override this default behaviour by providing postgres credentials through Values.yaml files. +## Database credentials -If you are deploying a dev/CI environment, a postgres server is deployed alongside gen3, and that is used to hold databases for testing. +Every service that requires a postgres database, has the it's credentials stored in a kubernetes secret. + +Example (The secret values have been base64 decoded for documentation purposes): + +```yaml +kubectl get secret fence-dbcreds -o yaml +apiVersion: v1 +kind: Secret +data: + database: fence_gen3 # The default value of this is _ + dbcreated: true # This is updated by the dbCreate job, when a database is created, and configured. + host: gen3-postgresql # Default depends on whether or not `global.dev` is true or false. If it's true this will default to -postgresql. If this is a production deployment, it will look for either `global.postgres.master.host` or `postgres.host` in the Values.yaml + password: example_pass # If not explicitely provided via the values, this is auto-generated. + port: 5432 # Defaults to 5432, will read from `global.postgres.master.port` or `postgres.port` for ovverrides. + username: fence_gen3 # Defaults to _. Will look for overrides in `postgres.username`. +``` -For production deployments you need to provide the master credentials for a postgres server through these values. +Each service then consumes this same secret and mounts them as ENV vars to access databases. + +For production deployments you must at minimum provide the master credentials for a postgres server through these values. ``` global: postgres: - db_create: true + dbCreate: true master: host: insert.postgres.hostname.here username: postgres @@ -24,23 +40,20 @@ global: ``` -These values will then be used to provision databases for the environment. +These values can then be used to provision and configure databases for the gen3 environment. ## Automatic database creation through jobs -When deploying gen3 helm charts you need to specifiy a postgres server. For dev/CI environments an installation of postgres is included, and is not intended for use in production. - -We hihgly recommend the use of a managed postgres service such as RDS when deploying gen3 to cloud environments. -The dev/ci postgres is deployed using this helm chart https://bitnami.com/stack/postgresql/helm -If you set the `global.postgres.db_create` value to true, then a job is kicked off for each service that relies on postgres to provision databases. +If you set the `global.postgres.dbCreate` value to true, then a job is kicked off for each service that relies on postgres to provision databases. This will kick off a [database creation job](../helm/common/templates/_db_setup_job.tpl) + -## Database restoration. +## Database restoration. (BETA) There is a job to restore dummy data for Postgres and Elasticsearch to speed up setting up ephemeral enviornments for testing purposes, and to avoid running expensive ETL jobs in CI to have a fully featured gen3 environment In the future this job may be used to set up fully tested production environments, negating the need to run ETL in production, and have all your databases tested before doing a data-release. diff --git a/docs/etl.md b/docs/etl.md new file mode 100644 index 00000000..946438e8 --- /dev/null +++ b/docs/etl.md @@ -0,0 +1,29 @@ +# ETL + +The Gen3 Tube ETL is designed to translate data from a graph data model, stored in a PostgreSQL database, to indexed documents in ElasticSearch (ES), which supports efficient ways to query data from the front-end. The purpose of the Gen3 Tube ETL is to create indexed documents to reduce the response time of requests to query data. It is configured through an etlMapping.yaml configuration file, which describes which tables and fields to ETL to ElasticSearch. + + +You can configure the ETL like this: + +```yaml +etl: + enabled: true + esEndpoint: "" + etlMapping: + +``` + +To kick off etl job run this command: + +```bash +kubectl create job --from=cronjob/etl-cronjob etl +``` + +If you already have a job called etl run the following. This will delete the old job and create a new instance. + +```bash +kubectl delete job etl +kubectl create job --from=cronjob/etl-cronjob etl +``` + +For more information about our ETL read [here github.com/uc-cdis/tube](https://github.com/uc-cdis/tube) \ No newline at end of file diff --git a/docs/fence_usersync_job.md b/docs/fence_usersync_job.md new file mode 100644 index 00000000..a05ed7d6 --- /dev/null +++ b/docs/fence_usersync_job.md @@ -0,0 +1,65 @@ +# Fence Usersync CronJob + +If `.Values.usersync.usersync` is set to true, the Fence usersync-cron.yaml will be deployed to the cluster. + +User lists can be synced from three sources: + +1. A ftp/sftp server that hosts user csv files that follows the format provided by dbgap, enabled if `.Values.usersync.syncFromDbgap` is set to "true". Please follow the [Sftp Setup](#sftp-setup) guide before enabling this option. + +2. A user.yaml file that is pulled from the S3 bucket specified in the `.Values.usersync.userYamlS3Path` field is used to update fence's user-access database. Please note an IAM policy with S3 read is required for this option. Please follow [S3 user.yaml Setup](#s3-setup) guide below. + +3. If the `.Values.usersync.userYamlS3Path` string is set to "none", the user.yaml file specified in the fence values.yaml [HERE](https://github.com/uc-cdis/gen3-helm/blob/c7b8959cdf5f7756b29c33ff330923e95981827c/helm/fence/values.yaml#L449-L1319) will be used. + + + +# S3 user.yaml Setup {#s3-setup} +Please see [this](https://github.com/uc-cdis/fence/blob/master/docs/user.yaml_guide.md) documentation that details user.yaml formatting. + +You can pull this file from an S3 bucket that is set in the `.Values.usersync.userYamlS3Path` field. Then input the iam credentials for a user that has read access to the specified S3 bucket in the `.Values.usersync.secrets.awsAccessKeyId` and `.Values.usersync.secrets.awsSecretAccessKey` fields. + +As previously mentioned, if the `.Values.usersync.userYamlS3Path` string is set to "none", the user.yaml file from Fence values.yaml will be used. + + + +# Dbgap +## Sftp Setup {#sftp-setup} +You can configure one or more dbGaP SFTP servers to sync telemetry files from. To configure one single dbGaP server, add credentials and information to the fence-config.yaml under dbGaP, this is outlined [here](https://github.com/uc-cdis/gen3-helm/blob/c7b8959cdf5f7756b29c33ff330923e95981827c/helm/fence/values.yaml#L1796). + +To configure additional dbGaP servers, include in the config.yaml a list of dbGaP servers under dbGaP, like so: + +``` +dbGaP: +- info: + host: + username: + password: + ... + protocol: 'sftp' + ... + ... +- info: + host: + username: + ... +```` + +You can find more detailed information on the setup with examples [here](https://github.com/uc-cdis/fence/blob/master/docs/usersync.md). + +For an example of a dbGap auth file (csv), please see [this](https://github.com/uc-cdis/fence/blob/master/docs/usersync.md#example-of-dbgap-authorization-file-csv-format) example for formatting. + +## Dbgap Options + Set `.Values.usersync.addDbgap` to "true" to attempt a dbgap sync and fall back on user.yaml. + + Set `.Values.usersync.onlyDbgap` to "true" to run only a dbgap sync and ignore the user.yaml. + +## Slack Options + Set `.Values.usersync.slack_webhook` to configure a webhook endpoint to be used for regular usersync updates to Slack. + + Set `.Values.usersync.slack_send_dbgap` to "true" to echo the files that are being seen on dbgap ftp to Slack. + + + +# Other Customizations + The `.Values.usersync.schedule` option can be set to customize the cron schedule expression. The default setting is to have the job run once every 30 minutes. + + The `.Values.usersync.custom_image` can be set to override the default "awshelper" image for the init container of the userync cronjob. \ No newline at end of file diff --git a/docs/kubernetes-in-docker.md b/docs/kubernetes-in-docker.md new file mode 100644 index 00000000..91707a20 --- /dev/null +++ b/docs/kubernetes-in-docker.md @@ -0,0 +1,115 @@ +# Gen3 in KIND +## Kind (Kubernetes IN Docker) + +### Overview +KIND runs Kubernetes inside a Docker container, making it an excellent choice for local development and testing. It is also used by the Kubernetes team to test Kubernetes itself. + +### Pros: + +Fast cluster creation (around 20 seconds). +Robust and reliable, thanks to containerd usage. +Suitable for CI environments (e.g., TravisCI, CircleCI). + +### Cons: + +Ingress controllers needs to be deployed manually + + + + +# Step 1. Create cluster + +```bash +cat < OAuth client ID. + +Select the Web application application type. Name your OAuth 2.0 client and click Create. + +For Authorized Javascript Origins add https:// + +For "Authorized redirect URIs" add https:///user/login/google/login/ + +After configuration is complete, take note of the client ID that was created. You will need the client ID and client secret to complete the next steps. + +## Prepare values.yaml + +Create a file called values.yaml and populate it like this (This is the main way of configuring gen3. this is just some default values that will help you get started) + +```yaml +global: + # This can be anything you want! + hostname: dev.planx-pla.net + +fence: + FENCE_CONFIG: + OPENID_CONNECT: + google: + client_id: "" + client_secret: "" + +# Use a prebuilt portal image if you're deploying to a laptop, less resources consumed by gen3 +portal: + resources: + requests: + cpu: "0.2" + memory: 100Mi + image: + repository: quay.io/cdis/data-portal-prebuilt + tag: dev + +``` + +## deploy gen3 + +```bash +helm repo add gen3 http://helm.gen3.org +helm upgrade --install gen3 gen3/gen3 -f ./values.yaml +``` \ No newline at end of file diff --git a/docs/portal/Dockerfile b/docs/portal/Dockerfile index def5e54d..49b6fc43 100644 --- a/docs/portal/Dockerfile +++ b/docs/portal/Dockerfile @@ -37,6 +37,7 @@ FROM nginx:latest COPY overrides/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /data-portal/build/*.js /data-portal/build/index.html /usr/share/nginx/html/ +COPY --from=builder /data-portal/data /usr/share/nginx/html/data COPY --from=builder /data-portal/src/img/ /usr/share/nginx/html/src/img/ COPY --from=builder /data-portal/src/css/ /usr/share/nginx/html/src/css/ diff --git a/docs/portal/prebuild-portal.md b/docs/portal/prebuild-portal.md index b352c945..f6e71727 100644 --- a/docs/portal/prebuild-portal.md +++ b/docs/portal/prebuild-portal.md @@ -15,7 +15,7 @@ This documentation will provide instructions on how to set up a static Gen3 Port Setup the configuration locally on your machine. -The dockerfile expects your portal configuration under the configurations folder. See exapmle for `dev.planx-pla.net` +The dockerfile expects your portal configuration under the configurations folder. See example for `dev.planx-pla.net` ``` configurations @@ -36,7 +36,10 @@ https:///api/v0/submission/_dictionary/_all **Hint:** both of these are served via sheepdog service -Use the provided Dockerfile as a template for building your container. +Use the provided Dockerfile as a template for building your container. Update: +``` +ARG PORTAL_HOSTNAME= +``` Build your container using the following command inside the same folder as the Dockerfile: @@ -44,6 +47,10 @@ Build your container using the following command inside the same folder as the D docker build -t : . ``` +If you are using localhost, you will need to add the `--network="host"` option in the above command, e.g: +``` +docker build -t : . --network="host" +``` Push the container to your repository using the command: ``` @@ -54,6 +61,26 @@ Update the image and tag in the Gen3 Portal configuration to use the new contain Note: Make sure to replace the with the actual name you want to give to your image. +Update or create the `values.yaml`, for example: +``` +global: + dev: true + hostname: localhost + +portal: + image: + repository: + tag: + resources: + requests: + cpu: 0.2 + memory: 500Mi +``` + +Update helm charts +``` +helm upgrade --install dev gen3/gen3 -f values.yaml +``` # Conclusion: Using a static Gen3 Data Portal can significantly improve the performance of the Gen3 Portal by pre-running the WebPack build and creating static files, which are then served using nginx. diff --git a/examples/aws_dev_values.yaml b/examples/aws_dev_values.yaml new file mode 100644 index 00000000..a579069c --- /dev/null +++ b/examples/aws_dev_values.yaml @@ -0,0 +1,61 @@ +global: + # Deploys aws specific ingress + aws: + enabled: true + environment: devplanetv2 + # Deploys elasticsearch and postgres in k8s + dev: true + # Replace with your dev environment url. + hostname: qureshi.planx-pla.net + # this is arn to a certificate in AWS that needs to match the hostname. + # This one is for *.planx-pla.net + revproxyArn: arn:aws:acm:us-east-1:707767160287:certificate/520ede2f-fc82-4bb9-af96-4b4af7deabbd + + +# configuration for fence helm chart. You can add it for all our services. +fence: + # Override image + image: + repository: quay.io/cdis/fence + tag: master + + # Fence config overrides + FENCE_CONFIG: + APP_NAME: 'Gen3 Data Commons' + # A URL-safe base64-encoded 32-byte key for encrypting keys in db + # in python you can use the following script to generate one: + # import base64 + # import os + # key = base64.urlsafe_b64encode(os.urandom(32)) + # print(key) + ENCRYPTION_KEY: REPLACEME + + DEBUG: True + OPENID_CONNECT: + google: + client_id: "" + client_secret: "" + + AWS_CREDENTIALS: + 'fence-bot': + aws_access_key_id: '' + aws_secret_access_key: '' + + S3_BUCKETS: + # Name of the actual s3 bucket + jq-helm-testing: + cred: 'fence-bot' + region: us-east-1 + + # This is important for data upload. + DATA_UPLOAD_BUCKET: 'jq-helm-testing' + + + +# -- (map) To configure postgresql subchart +# Persistence is disabled by default +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: true diff --git a/examples/gke_dev_values.yaml b/examples/gke_dev_values.yaml new file mode 100644 index 00000000..8011210c --- /dev/null +++ b/examples/gke_dev_values.yaml @@ -0,0 +1,69 @@ +global: + # to disable local es/postgre + dev: true + hostname: qureshi.planx-pla.net + tls: + cert: + key: + # Postgres instance that is managed outside of helm + postgres: + master: + host: "postgres-host-address" + username: "postgres" + password: "postgres-password" + + +# configuration for fence helm chart. You can add it for all our services. +fence: + # Fence config overrides + FENCE_CONFIG: + OPENID_CONNECT: + google: + client_id: "" + client_secret: "" + + AWS_CREDENTIALS: + 'fence-bot': + aws_access_key_id: '' + aws_secret_access_key: '' + + S3_BUCKETS: + # Name of the actual s3 bucket + jq-helm-testing: + cred: 'fence-bot' + region: us-east-1 + + # This is important for data upload. + DATA_UPLOAD_BUCKET: 'jq-helm-testing' + + + +revproxy: + ingress: + # Enable the default ingress included by helm. Add any configurations as needed. + enabled: true + hosts: + # Replace with your hostname + - host: qureshi.planx-pla.net + paths: + - path: / + pathType: Prefix + tls: + # this is the secret generated by the cert and key from global.tls + # if you have your own secret, reference that. + - secretName: gen3-certs + hosts: + # Replace with your hostname + - qureshi.planx-pla.net + + +# -- (map) To configure postgresql subchart +# Persistence is disabled by default +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: true + + +# Add configuration overrides for fence and other services below as needed \ No newline at end of file diff --git a/examples/gke_values.yaml b/examples/gke_values.yaml new file mode 100644 index 00000000..6e83543c --- /dev/null +++ b/examples/gke_values.yaml @@ -0,0 +1,84 @@ +global: + # to disable local es/postgre + dev: false + hostname: qureshi.planx-pla.net + esEndpoint: "" + tls: + cert: + key: + # Postgres instance that is managed outside of k8s + postgres: + master: + host: "postgres-host-address" + username: "postgres" + password: "postgres-password" + +# configuration for fence helm chart. You can add it for all our services. +fence: + # Override image + image: + repository: quay.io/cdis/fence + tag: master + + # Fence config overrides + FENCE_CONFIG: + APP_NAME: 'Gen3 Data Commons' + # A URL-safe base64-encoded 32-byte key for encrypting keys in db + # in python you can use the following script to generate one: + # import base64 + # import os + # key = base64.urlsafe_b64encode(os.urandom(32)) + # print(key) + ENCRYPTION_KEY: REPLACEME + + DEBUG: True + OPENID_CONNECT: + google: + client_id: "" + client_secret: "" + + AWS_CREDENTIALS: + 'fence-bot': + aws_access_key_id: '' + aws_secret_access_key: '' + + S3_BUCKETS: + # Name of the actual s3 bucket + jq-helm-testing: + cred: 'fence-bot' + region: us-east-1 + + # This is important for data upload. + DATA_UPLOAD_BUCKET: 'jq-helm-testing' + + + +# -- (map) To configure postgresql subchart +# Persistence is disabled by default +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: true + + +revproxy: + ingress: + # Enable the default ingress included by helm. Add any configurations as needed. + enabled: true + hosts: + # Replace with your hostname + - host: qureshi.planx-pla.net + paths: + - path: / + pathType: Prefix + tls: + # this is the secret generated by the cert and key from global.tls + # if you have your own secret, reference that. + - secretName: gen3-certs + hosts: + # Replace with your hostname + - qureshi.planx-pla.net + + +# Add configuration overrides for fence and other services below as needed \ No newline at end of file diff --git a/examples/local_dev_values.yaml b/examples/local_dev_values.yaml new file mode 100644 index 00000000..1250b2c5 --- /dev/null +++ b/examples/local_dev_values.yaml @@ -0,0 +1,36 @@ +global: + dev: true + hostname: localhost + +# configuration for fence helm chart. You can add it for all our services. +fence: + # Fence config overrides + FENCE_CONFIG: + OPENID_CONNECT: + google: + client_id: "" + client_secret: "" + + AWS_CREDENTIALS: + 'fence-bot': + aws_access_key_id: '' + aws_secret_access_key: '' + + S3_BUCKETS: + # Name of the actual s3 bucket + jq-helm-testing: + cred: 'fence-bot' + region: us-east-1 + + # This is important for data upload. + DATA_UPLOAD_BUCKET: 'jq-helm-testing' + + +portal: + image: + repository: quay.io/cdis/data-portal-prebuilt + tag: brh.data-commons.org-feat-pr_comment + resources: + requests: + cpu: 0.2 + memory: 500Mi \ No newline at end of file diff --git a/helm/ambassador/Chart.yaml b/helm/ambassador/Chart.yaml index 9ee1e3bb..b4e745d5 100644 --- a/helm/ambassador/Chart.yaml +++ b/helm/ambassador/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "1.4.2" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/ambassador/README.md b/helm/ambassador/README.md index c3e0975e..06e4b0e2 100644 --- a/helm/ambassador/README.md +++ b/helm/ambassador/README.md @@ -1,16 +1,36 @@ # ambassador -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.4.2](https://img.shields.io/badge/AppVersion-1.4.2-informational?style=flat-square) +![Version: 0.1.9](https://img.shields.io/badge/Version-0.1.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.4.2](https://img.shields.io/badge/AppVersion-1.4.2-informational?style=flat-square) A Helm chart for deploying ambassador for gen3 +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | map | `{}` | Affinity to use for the deployment. | -| autoscaling | map | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":60}` | Autoscaling options. | +| autoscaling | map | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":60}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | +| autoscaling.maxReplicas | int | `10` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `60` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | | fullnameOverride | string | `"ambassador-deployment"` | Override the full name of the deployment. | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | image | map | `{"pullPolicy":"Always","repository":"quay.io/datawire/ambassador","tag":"1.4.2"}` | Docker image information. | | image.pullPolicy | string | `"Always"` | Docker pull policy. | | image.repository | string | `"quay.io/datawire/ambassador"` | Docker repository. | @@ -18,20 +38,26 @@ A Helm chart for deploying ambassador for gen3 | imagePullSecrets | list | `[]` | Docker image pull secrets. | | nameOverride | string | `""` | Override the name of the chart. | | nodeSelector | map | `{}` | Node selector labels. | +| partOf | string | `"Workspace-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | podAnnotations | map | `nil` | Annotations to add to the pod. | -| podLabels | map | `nil` | Labels to add to the pod. | | podSecurityContext | map | `{"runAsUser":8888}` | Pod-level security context. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | replicaCount | int | `1` | Number of replicas for the deployment. | -| resources | map | `{"limits":{"memory":"400Mi"},"requests":{"cpu":"100m","memory":"100Mi"}}` | Resource limits and requests. | +| resources | map | `{"limits":{"memory":"400Mi"},"requests":{"cpu":"100m","memory":"100Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"memory":"400Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.memory | string | `"400Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":"100m","memory":"100Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `"100m"` | The amount of CPU requested | +| resources.requests.memory | string | `"100Mi"` | The amount of memory requested | | securityContext | map | `{}` | Container-level security context. | -| selectorLabels | map | `{"service":"ambassador"}` | Labels to use for selecting the deployment. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | | service | map | `{"port":8877,"type":"ClusterIP"}` | Kubernetes service information. | -| service.port | int | `8877` | Service port to use. | -| service.type | string | `"ClusterIP"` | Service type to use. | +| service.port | int | `8877` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | | serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | | serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | -| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | | tolerations | list | `[]` | Tolerations to use for the deployment. | | userNamespace | string | `"jupyter-pods"` | Namespace to use for user resources. | diff --git a/helm/ambassador/templates/_helpers.tpl b/helm/ambassador/templates/_helpers.tpl index f54ca8a8..913976ff 100644 --- a/helm/ambassador/templates/_helpers.tpl +++ b/helm/ambassador/templates/_helpers.tpl @@ -34,21 +34,27 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "ambassador.labels" -}} -helm.sh/chart: {{ include "ambassador.chart" . }} -{{ include "ambassador.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "ambassador.selectorLabels" -}} -service: ambassador +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} - {{/* Create the name of the service account to use */}} diff --git a/helm/ambassador/templates/deployment.yaml b/helm/ambassador/templates/deployment.yaml index cba3c5e4..3065ec60 100644 --- a/helm/ambassador/templates/deployment.yaml +++ b/helm/ambassador/templates/deployment.yaml @@ -4,13 +4,16 @@ metadata: name: ambassador-deployment labels: {{- include "ambassador.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: - app: ambassador + {{- include "ambassador.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} @@ -18,10 +21,10 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: - app: ambassador - {{- with .Values.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end}} + {{- include "ambassador.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: affinity: podAntiAffinity: @@ -46,6 +49,9 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} imagePullPolicy: {{ .Values.image.pullPolicy }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 10 }} + {{- end }} - name: AMBASSADOR_NAMESPACE value: {{ printf "%s-%s" .Values.userNamespace .Release.Name | quote }} - name: AMBASSADOR_SINGLE_NAMESPACE diff --git a/helm/ambassador/templates/pdb.yaml b/helm/ambassador/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/ambassador/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/ambassador/templates/service.yaml b/helm/ambassador/templates/service.yaml index 580cb5df..ff7a1acb 100644 --- a/helm/ambassador/templates/service.yaml +++ b/helm/ambassador/templates/service.yaml @@ -17,11 +17,11 @@ apiVersion: v1 kind: Service metadata: labels: - service: ambassador + app: ambassador name: ambassador-service spec: ports: - port: 80 targetPort: 8080 selector: - app: ambassador \ No newline at end of file + {{- include "ambassador.selectorLabels" . | nindent 4 }} diff --git a/helm/ambassador/templates/tests/test-connection.yaml b/helm/ambassador/templates/tests/test-connection.yaml index 363d01c8..3b88ad1c 100644 --- a/helm/ambassador/templates/tests/test-connection.yaml +++ b/helm/ambassador/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "ambassador.fullname" . }}-test-connection" + name: "ambassador-test-connection" labels: {{- include "ambassador.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "ambassador.fullname" . }}:{{ .Values.service.port }}'] + args: ['ambassador-service:80/ambassador/v0/check_ready'] restartPolicy: Never diff --git a/helm/ambassador/values.yaml b/helm/ambassador/values.yaml index b2744a65..bb4a5e1b 100644 --- a/helm/ambassador/values.yaml +++ b/helm/ambassador/values.yaml @@ -2,6 +2,17 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + # -- (int) Number of replicas for the deployment. replicaCount: 1 @@ -30,15 +41,12 @@ serviceAccount: # -- (map) Annotations to add to the service account. annotations: {} # -- (string) The name of the service account to use. - # -- If not set and create is true, a name is generated using the fullname template. + # If not set and create is true, a name is generated using the fullname template. name: "" # -- (map) Annotations to add to the pod. podAnnotations: {} -# -- (map) Labels to add to the pod. -podLabels: {} - # -- (map) Pod-level security context. podSecurityContext: runAsUser: 8888 @@ -55,32 +63,38 @@ securityContext: {} # -- (map) Kubernetes service information. service: - # -- (string) Service type to use. + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP - # -- (int) Service port to use. + # -- (int) The port number that the service exposes. port: 8877 -# -- (map) Labels to use for selecting the deployment. -selectorLabels: - service: ambassador - # -- (string) Namespace to use for user resources. userNamespace: "jupyter-pods" -# -- (map) Resource limits and requests. +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of memory the container can use memory: 400Mi + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 100m + # -- (string) The amount of memory requested memory: 100Mi -# -- (map) Autoscaling options. +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled or not enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 10 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 60 + # -- (int) The target memory utilization percentage for autoscaling # targetMemoryUtilizationPercentage: 80 # -- (map) Node selector labels. @@ -91,3 +105,23 @@ tolerations: [] # -- (map) Affinity to use for the deployment. affinity: {} + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Workspace-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/arborist/Chart.yaml b/helm/arborist/Chart.yaml index 60e58897..3ef314d6 100644 --- a/helm/arborist/Chart.yaml +++ b/helm/arborist/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,5 +25,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/arborist/README.md b/helm/arborist/README.md index b93a804d..13f542c6 100644 --- a/helm/arborist/README.md +++ b/helm/arborist/README.md @@ -1,6 +1,6 @@ # arborist -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.9](https://img.shields.io/badge/Version-0.1.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 arborist @@ -8,22 +8,28 @@ A Helm chart for gen3 arborist | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | map | `{}` | Affinity rules to apply to the pod | -| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Autoscaling settings | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | | autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | | autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | | autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | | autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | | env | list | `[{"name":"JWKS_ENDPOINT","value":"http://fence-service/.well-known/jwks"}]` | Environment variables to pass to the container | | env[0] | string | `{"name":"JWKS_ENDPOINT","value":"http://fence-service/.well-known/jwks"}` | The URL of the JSON Web Key Set (JWKS) endpoint for authentication | | fullnameOverride | string | `""` | Override the full name of the deployment. | -| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | | global.dev | bool | `true` | Whether the deployment is for development purposes. | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | @@ -32,7 +38,9 @@ A Helm chart for gen3 arborist | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -43,9 +51,7 @@ A Helm chart for gen3 arborist | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | | image | map | `{"pullPolicy":"IfNotPresent","repository":"quay.io/cdis/arborist","tag":""}` | Docker image information. | | image.pullPolicy | string | `"IfNotPresent"` | Docker pull policy. | | image.repository | string | `"quay.io/cdis/arborist"` | Docker repository. | @@ -53,31 +59,37 @@ A Helm chart for gen3 arborist | imagePullSecrets | list | `[]` | Docker image pull secrets. | | nameOverride | string | `""` | Override the name of the chart. | | nodeSelector | map | `{}` | Node selector to apply to the pod | +| partOf | string | `"Authentication"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | podAnnotations | map | `{}` | Annotations to add to the pod | | podSecurityContext | map | `nil` | Security context to apply to the pod | -| postgres | map | `{"database":null,"dbCreate":null,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| postgres | map | `{"database":null,"dbCreate":null,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | replicaCount | int | `1` | Number of replicas for the deployment. | -| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits | -| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | Resource limits | -| resources.limits.cpu | string | `1` | CPU limit | -| resources.limits.memory | string | `"512Mi"` | Memory limit | -| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | Resource requests | -| resources.requests.cpu | string | `0.1` | CPU request | -| resources.requests.memory | string | `"12Mi"` | Memory request | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | | securityContext | map | `{}` | Security context to apply to the container | -| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service settings | -| service.port | int | `80` | The port number that the service exposes | -| service.type | string | `"ClusterIP"` | The type of the Kubernetes service | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | | serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | | serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | -| serviceAccount.name | string | `""` | The name of the service account to use. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | | tolerations | list | `[]` | Tolerations to apply to the pod | | volumeMounts | list | `[]` | Volume mounts to attach to the container | | volumes | list | `[]` | Volumes to attach to the pod | diff --git a/helm/arborist/templates/_helpers.tpl b/helm/arborist/templates/_helpers.tpl index db6153b5..2aed27dc 100644 --- a/helm/arborist/templates/_helpers.tpl +++ b/helm/arborist/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "arborist.labels" -}} -helm.sh/chart: {{ include "arborist.chart" . }} -{{ include "arborist.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "arborist.selectorLabels" -}} -app.kubernetes.io/name: {{ include "arborist.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/arborist/templates/deployment.yaml b/helm/arborist/templates/deployment.yaml index 5c7d900d..3fb7963e 100644 --- a/helm/arborist/templates/deployment.yaml +++ b/helm/arborist/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: arborist-deployment labels: {{- include "arborist.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -19,6 +22,9 @@ spec: {{- end }} labels: {{- include "arborist.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.volumes }} volumes: @@ -68,6 +74,9 @@ spec: # run arborist /go/src/github.com/uc-cdis/arborist/bin/arborist env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- toYaml .Values.env | nindent 12 }} - name: PGPASSWORD valueFrom: @@ -123,4 +132,4 @@ spec: {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} \ No newline at end of file diff --git a/helm/arborist/templates/pdb.yaml b/helm/arborist/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/arborist/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/arborist/templates/tests/test-connection.yaml b/helm/arborist/templates/tests/test-connection.yaml index c072755b..2a913dd0 100644 --- a/helm/arborist/templates/tests/test-connection.yaml +++ b/helm/arborist/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "arborist.fullname" . }}-test-connection" + name: "arborist-test-connection" labels: {{- include "arborist.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "arborist.fullname" . }}:{{ .Values.service.port }}'] + args: ['arborist-service:{{ .Values.service.port }}/health'] restartPolicy: Never diff --git a/helm/arborist/values.yaml b/helm/arborist/values.yaml index e40d5a3b..a2cbfcbf 100644 --- a/helm/arborist/values.yaml +++ b/helm/arborist/values.yaml @@ -34,13 +34,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -48,6 +44,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -63,6 +63,17 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false + +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false + # -- (int) Number of replicas for the deployment. replicaCount: 1 @@ -92,6 +103,7 @@ serviceAccount: # -- (map) Annotations to add to the service account. annotations: {} # -- (string) The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template name: "" # Declare variables to be passed into your templates. @@ -119,44 +131,38 @@ securityContext: {} # -- (int) The user ID to run the container as # runAsUser: 1000 -# -- (map) Kubernetes service settings +# -- (map) Kubernetes service information. service: - # -- (string) The type of the Kubernetes service + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP - - # -- (int) The port number that the service exposes + # -- (int) The port number that the service exposes. port: 80 -# -- (map) Resource requests and limits +# -- (map) Resource requests and limits for the containers in the pod resources: - # -- (map) Resource requests + # -- (map) The amount of resources that the container requests requests: - # -- (string) CPU request + # -- (string) The amount of CPU requested cpu: 0.1 - # -- (string) Memory request + # -- (string) The amount of memory requested memory: 12Mi - - # -- (map) Resource limits + # -- (map) The maximum amount of resources that the container is allowed to use limits: - # -- (string) CPU limit + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 - # -- (string) Memory limit + # -- (string) The maximum amount of memory the container can use memory: 512Mi -# -- (map) Autoscaling settings +# -- (map) Configuration for autoscaling the number of replicas autoscaling: # -- (bool) Whether autoscaling is enabled enabled: false - # -- (int) The minimum number of replicas to scale down to minReplicas: 1 - # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 - # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 - # -- (int) The target memory utilization percentage for autoscaling # targetMemoryUtilizationPercentage: 80 @@ -180,3 +186,24 @@ env: # -- (string) The URL of the JSON Web Key Set (JWKS) endpoint for authentication - name: JWKS_ENDPOINT value: "http://fence-service/.well-known/jwks" + + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Authentication" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/argo-wrapper/Chart.yaml b/helm/argo-wrapper/Chart.yaml index 5af96f2a..5d320e9d 100644 --- a/helm/argo-wrapper/Chart.yaml +++ b/helm/argo-wrapper/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.1.5 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/argo-wrapper/README.md b/helm/argo-wrapper/README.md index 9bafcdeb..b5f7b3a1 100644 --- a/helm/argo-wrapper/README.md +++ b/helm/argo-wrapper/README.md @@ -1,60 +1,69 @@ # argo-wrapper -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.5](https://img.shields.io/badge/Version-0.1.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Argo Wrapper Service +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"argo-wrapper"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | -| environment | string | `"default"` | | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/argo-wrapper"` | | -| image.tag | string | `""` | | -| indexdAdminUser | string | `"fence"` | | -| internalS3Bucket | string | `"argo-internal-bucket"` | | -| livenessProbe.httpGet.path | string | `"/test"` | | -| livenessProbe.httpGet.port | int | `8000` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| podAnnotations."gen3.io/network-ingress" | string | `"argo-wrapper"` | | -| ports[0].containerPort | int | `8000` | | -| ports[0].protocol | string | `"TCP"` | | -| pvc | string | `"test-pvc"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | string | `"100m"` | | -| resources.limits.memory | string | `"128Mi"` | | -| revisionHistoryLimit | int | `2` | | -| s3Bucket | string | `"argo-artifact-downloadable"` | | -| scalingGroups[0].user1 | string | `"workflow1"` | | -| scalingGroups[1].user2 | string | `"workflow2"` | | -| scalingGroups[2].user3 | string | `"workflow3"` | | -| service.port | int | `8000` | | -| service.type | string | `"ClusterIP"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| volumeMounts[0].mountPath | string | `"/argo.json"` | | -| volumeMounts[0].name | string | `"argo-config"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"argo.json"` | | -| volumes[0].configMap.items[0].key | string | `"argo.json"` | | -| volumes[0].configMap.items[0].path | string | `"argo.json"` | | -| volumes[0].configMap.name | string | `"manifest-argo"` | | -| volumes[0].name | string | `"argo-config"` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["argo-wrapper"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["argo-wrapper"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["argo-wrapper"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["argo-wrapper"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["argo-wrapper"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| environment | string | `"default"` | Environment name. | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/argo-wrapper","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/argo-wrapper"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| indexdAdminUser | string | `"fence"` | Admin user for Indexd. | +| internalS3Bucket | string | `"argo-internal-bucket"` | Name of the internal Argo bucket for Argo artifacts (does not allow pre-signed URLs). | +| partOf | string | `"Apps-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{"gen3.io/network-ingress":"argo-wrapper"}` | Annotations to add to the pod. | +| pvc | string | `"test-pvc"` | PVC for Argo. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":"100m","memory":"128Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `"100m"` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"128Mi"` | The maximum amount of memory the container can use | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| s3Bucket | string | `"argo-artifact-downloadable"` | S3 bucket name for Argo artifacts (allows pre-signed URLs). | +| scalingGroups | list | `[{"user1":"workflow1"},{"user2":"workflow2"},{"user3":"workflow3"}]` | The workflow scaling groups to be used by Argo. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":8000,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `8000` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| volumeMounts | list | `[{"mountPath":"/argo.json","name":"argo-config","readOnly":true,"subPath":"argo.json"}]` | Volumes to mount to the pod. | +| volumes | list | `[{"configMap":{"items":[{"key":"argo.json","path":"argo.json"}],"name":"manifest-argo"},"name":"argo-config"}]` | Volumes to attach to the pod. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/argo-wrapper/templates/_helpers.tpl b/helm/argo-wrapper/templates/_helpers.tpl index 01c2e9d2..cd6c98ae 100644 --- a/helm/argo-wrapper/templates/_helpers.tpl +++ b/helm/argo-wrapper/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "argo-wrapper.labels" -}} -helm.sh/chart: {{ include "argo-wrapper.chart" . }} -{{ include "argo-wrapper.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "argo-wrapper.selectorLabels" -}} -app.kubernetes.io/name: {{ include "argo-wrapper.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "argo-wrapper.name" . }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -71,15 +76,4 @@ Define environment {{- else}} {{- .Values.environment }} {{- end }} -{{- end }} - -{{/* -Define ddEnabled -*/}} -{{- define "argo-wrapper.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} {{- end }} \ No newline at end of file diff --git a/helm/argo-wrapper/templates/deployment.yaml b/helm/argo-wrapper/templates/deployment.yaml index 0a3f38df..7dfc6e21 100644 --- a/helm/argo-wrapper/templates/deployment.yaml +++ b/helm/argo-wrapper/templates/deployment.yaml @@ -8,6 +8,9 @@ metadata: {{- end }} labels: {{- include "argo-wrapper.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -23,16 +26,13 @@ spec: template: metadata: labels: + {{- include "argo-wrapper.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' - {{- if eq (include "argo-wrapper.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "argo-wrapper" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "argo-wrapper.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -45,18 +45,24 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} + httpGet: + path: /test + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.ports}} ports: - {{- toYaml . | nindent 12}} - {{- end }} + - containerPort: 8000 + protocol: TCP {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} {{- end }} resources: - {{- toYaml .Values.resources | nindent 12 }} \ No newline at end of file + {{- toYaml .Values.resources | nindent 12 }} + env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} \ No newline at end of file diff --git a/helm/argo-wrapper/templates/pdb.yaml b/helm/argo-wrapper/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/argo-wrapper/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/argo-wrapper/templates/tests/test-connection.yaml b/helm/argo-wrapper/templates/tests/test-connection.yaml index 0233f0e2..02e99617 100644 --- a/helm/argo-wrapper/templates/tests/test-connection.yaml +++ b/helm/argo-wrapper/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "argo-wrapper.fullname" . }}:{{ .Values.service.port }}'] + args: ['argo-wrapper-service:{{ .Values.service.port }}/test'] restartPolicy: Never diff --git a/helm/argo-wrapper/values.yaml b/helm/argo-wrapper/values.yaml index a0b9271b..47c711b0 100644 --- a/helm/argo-wrapper/values.yaml +++ b/helm/argo-wrapper/values.yaml @@ -2,42 +2,70 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + # Deployment +# -- (map) Annotations to add to the pod. podAnnotations: {"gen3.io/network-ingress": "argo-wrapper"} +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 + # -- (int) The target memory utilization percentage for autoscaling + # targetMemoryUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 -dataDog: - enabled: false - env: dev - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - argo-wrapper + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (list) Volumes to attach to the pod. volumes: - name: argo-config configMap: @@ -46,48 +74,71 @@ volumes: - key: argo.json path: argo.json +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/argo-wrapper + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" -livenessProbe: - httpGet: - path: /test - port: 8000 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 - -ports: - - containerPort: 8000 - protocol: TCP - +# -- (list) Volumes to mount to the pod. volumeMounts: - name: argo-config readOnly: true mountPath: /argo.json subPath: argo.json +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 100m + # -- (string) The maximum amount of memory the container can use memory: 128Mi - -# Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 8000 # Configmap +# -- (list) The workflow scaling groups to be used by Argo. scalingGroups: - user1: "workflow1" - user2: "workflow2" - user3: "workflow3" +# -- (string) S3 bucket name for Argo artifacts (allows pre-signed URLs). s3Bucket: "argo-artifact-downloadable" +# -- (string) Name of the internal Argo bucket for Argo artifacts (does not allow pre-signed URLs). internalS3Bucket: "argo-internal-bucket" +# -- (string) Admin user for Indexd. indexdAdminUser: "fence" +# -- (string) Environment name. environment: "default" +# -- (string) PVC for Argo. pvc: "test-pvc" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Apps-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/audit/Chart.yaml b/helm/audit/Chart.yaml index 3f3f0037..fbd974a8 100644 --- a/helm/audit/Chart.yaml +++ b/helm/audit/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.10 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -24,5 +24,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/audit/README.md b/helm/audit/README.md index cd2227ca..a8b49f18 100644 --- a/helm/audit/README.md +++ b/helm/audit/README.md @@ -1,6 +1,6 @@ # audit -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.10](https://img.shields.io/badge/Version-0.1.10-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for Kubernetes @@ -8,33 +8,36 @@ A Helm chart for Kubernetes | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"audit"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| api.QUERY_PAGE_SIZE | int | `1000` | | -| api.QUERY_TIMEBOX_MAX_DAYS | string | `nil` | | -| api.QUERY_USERNAMES | bool | `true` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["audit"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["audit"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["audit"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["audit"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["audit"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| api.QUERY_PAGE_SIZE | int | `1000` | The maximum number of entires the query endpoint will return. | +| api.QUERY_TIMEBOX_MAX_DAYS | int | `nil` | Amount to time-box queries. | +| api.QUERY_USERNAMES | bool | `true` | Whether to return usernames in query responses and allow querying by username. | | autoscaling | map | `{"enabled":false,"maxReplicas":4,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | | autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | -| autoscaling.maxReplicas | int | `4` | Maximum number of replicas | -| autoscaling.minReplicas | int | `1` | Minimum number of replicas | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | -| env[0].name | string | `"DEBUG"` | | -| env[0].value | string | `"false"` | | -| env[1].name | string | `"ARBORIST_URL"` | | -| env[1].valueFrom.configMapKeyRef.key | string | `"arboristUrl"` | | -| env[1].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | -| env[1].valueFrom.configMapKeyRef.optional | bool | `true` | | +| autoscaling.maxReplicas | int | `4` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| env | list | `[{"name":"DEBUG","value":"false"},{"name":"ARBORIST_URL","valueFrom":{"configMapKeyRef":{"key":"arborist_url","name":"manifest-global","optional":true}}}]` | Environment variables to pass to the container | | fullnameOverride | string | `""` | Override the full name of the chart, which is used as the name of resources created by the chart | -| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | | global.dev | bool | `true` | Whether the deployment is for development purposes. | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | @@ -43,7 +46,9 @@ A Helm chart for Kubernetes | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -54,56 +59,56 @@ A Helm chart for Kubernetes | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/audit-service","tag":"master"}` | Docker image information. | | image.pullPolicy | string | `"Always"` | When to pull the image. This value should be "Always" to ensure the latest image is used. | | image.repository | string | `"quay.io/cdis/audit-service"` | The Docker image repository for the audit service | -| image.tag | string | `"master"` | The tag to use for the image. | -| imagePullSecrets | list | `[]` | | -| initEnv | object | `{}` | | -| initVolumeMounts | list | `[]` | | -| labels."tags.datadoghq.com/service" | string | `"audit"` | | -| labels.app | string | `"audit"` | | -| labels.authprovider | string | `"yes"` | | -| labels.netnolimit | string | `"yes"` | | -| labels.public | string | `"yes"` | | -| labels.release | string | `"production"` | | -| labels.userhelper | string | `"yes"` | | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| initEnv | list | `{}` | Volumes to attach to the init container. | +| initVolumeMounts | list | `[]` | Volumes to mount to the init container. | | nameOverride | string | `""` | Override the name of the chart. This can be used to provide a unique name for a chart | | nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"Logging"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | podAnnotations | map | `{}` | Annotations to add to the pod | | podSecurityContext | map | `{}` | Security context for the pod | -| postgres | map | `{"database":null,"dbCreate":null,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| postgres | map | `{"database":null,"dbCreate":null,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | replicaCount | int | `1` | Number of desired replicas | -| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.2,"memory":"120Mi"}}` | Resource requests and limits for the containers in the pod | | resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | | resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | | resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | -| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | -| resources.requests.cpu | string | `0.1` | The amount of CPU requested | -| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| resources.requests | map | `{"cpu":0.2,"memory":"120Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.2` | The amount of CPU requested | +| resources.requests.memory | string | `"120Mi"` | The amount of memory requested | | securityContext | map | `{}` | Security context for the containers in the pod | -| server.AWS_CREDENTIALS | object | `{}` | | -| server.debug | bool | `false` | | -| server.pull_from_queue | bool | `false` | | -| server.sqs.region | string | `"us-east-1"` | | -| server.sqs.url | string | `"http://sqs.com"` | | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| server.AWS_CREDENTIALS | map | `{}` | AWS credentials to access SQS queue. | +| server.debug | bool | `false` | Whether to enable or disable debug mode. | +| server.pull_from_queue | bool | `false` | Whether to pull logs from sqs queue. | +| server.sqs | map | `{"region":"us-east-1","url":"http://sqs.com"}` | AWS SQS queue information. | +| server.sqs.region | string | `"us-east-1"` | SQS queue AWS region. | +| server.sqs.url | string | `"http://sqs.com"` | The URL for the SQS queue. | | service | map | `{"port":80,"type":"ClusterIP"}` | Configuration for the service | | service.port | int | `80` | Port on which the service is exposed | | service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{"eks.amazonaws.com/role-arn":null},"create":true,"name":"audit-service-sa"}` | Service account to use or create. | | serviceAccount.annotations."eks.amazonaws.com/role-arn" | string | `nil` | The Amazon Resource Name (ARN) of the role to associate with the service account | | serviceAccount.create | bool | `true` | Whether to create a service account | | serviceAccount.name | string | `"audit-service-sa"` | The name of the service account | | tolerations | list | `[]` | Tolerations for the pods | -| volumeMounts | list | `[]` | | -| volumes | list | `[]` | | +| volumeMounts | list | `[]` | Volumes to mount to the container. | +| volumes | list | `[]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/audit/templates/_helpers.tpl b/helm/audit/templates/_helpers.tpl index 65a9c211..e255d758 100644 --- a/helm/audit/templates/_helpers.tpl +++ b/helm/audit/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "audit.labels" -}} -helm.sh/chart: {{ include "audit.chart" . }} -{{ include "audit.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "audit.selectorLabels" -}} -app.kubernetes.io/name: {{ include "audit.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/audit/templates/deployment.yaml b/helm/audit/templates/deployment.yaml index b7ce45d7..83cf2078 100644 --- a/helm/audit/templates/deployment.yaml +++ b/helm/audit/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: audit-deployment labels: {{- include "audit.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -18,7 +21,10 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: - {{- include "audit.labels" . | nindent 8 }} + {{- include "audit.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "audit.serviceAccountName" . }} volumes: @@ -50,6 +56,9 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: DB_HOST valueFrom: secretKeyRef: diff --git a/helm/audit/templates/pdb.yaml b/helm/audit/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/audit/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/audit/templates/tests/test-connection.yaml b/helm/audit/templates/tests/test-connection.yaml index 72841458..d0a13d3a 100644 --- a/helm/audit/templates/tests/test-connection.yaml +++ b/helm/audit/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "audit.fullname" . }}:{{ .Values.service.port }}'] + args: ['audit-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/audit/values.yaml b/helm/audit/values.yaml index 4262243d..86a849f2 100644 --- a/helm/audit/values.yaml +++ b/helm/audit/values.yaml @@ -33,13 +33,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -47,6 +43,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -62,19 +62,30 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false + +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false # -- (int) Number of desired replicas replicaCount: 1 +# -- (map) Docker image information. image: # -- (string) The Docker image repository for the audit service repository: quay.io/cdis/audit-service # -- (string) When to pull the image. This value should be "Always" to ensure the latest image is used. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. - # -- (string) The tag to use for the image. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "master" +# -- (list) Docker image pull secrets. imagePullSecrets: [] # -- (string) Override the name of the chart. This can be used to provide a unique name for a chart @@ -83,6 +94,7 @@ nameOverride: "" # -- (string) Override the full name of the chart, which is used as the name of resources created by the chart fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: # Specifies whether a service account should be created # -- (bool) Whether to create a service account @@ -124,9 +136,9 @@ resources: # -- (map) The amount of resources that the container requests requests: # -- (string) The amount of CPU requested - cpu: 0.1 + cpu: 0.2 # -- (string) The amount of memory requested - memory: 12Mi + memory: 120Mi # -- (map) The maximum amount of resources that the container is allowed to use limits: # -- (string) The maximum amount of CPU the container can use @@ -138,11 +150,11 @@ resources: autoscaling: # -- (bool) Whether autoscaling is enabled or not enabled: false - # -- (int) Minimum number of replicas + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 - # -- (int) Maximum number of replicas + # -- (int) The maximum number of replicas to scale up to maxReplicas: 4 - # -- (int) Target CPU utilization percentage + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 @@ -153,31 +165,27 @@ nodeSelector: {} # -- (list) Tolerations for the pods tolerations: [] - -labels: - app: audit - release: production - # for network policy selectors - authprovider: "yes" - # uses explicit proxy and AWS APIs - netnolimit: "yes" - public: "yes" - userhelper: "yes" - tags.datadoghq.com/service: "audit" - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - audit + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (list) Environment variables to pass to the container env: - name: DEBUG value: "false" @@ -188,31 +196,61 @@ env: key: arboristUrl optional: true +# -- (list) Volumes to attach to the container. volumes: [] +# -- (list) Volumes to mount to the container. volumeMounts: [] +# -- (list) Volumes to mount to the init container. initVolumeMounts: [] +# -- (list) Volumes to attach to the init container. initEnv: {} server: + # -- (bool) Whether to pull logs from sqs queue. pull_from_queue: false + # -- (bool) Whether to enable or disable debug mode. debug: false + # -- (map) AWS SQS queue information. sqs: + # -- (string) SQS queue AWS region. region: "us-east-1" + # -- (string) The URL for the SQS queue. url: "http://sqs.com" + # -- (map) AWS credentials to access SQS queue. AWS_CREDENTIALS: {} # cred1: # aws_access_key_id: # aws_secret_access_key: api: - # if left empty, queries are not time-boxed + # -- (int) Amount to time-box queries. QUERY_TIMEBOX_MAX_DAYS: + # -- (int) The maximum number of entires the query endpoint will return. QUERY_PAGE_SIZE: 1000 - # whether to return usernames in query responses, - # and to allow querying by username + # -- (bool) Whether to return usernames in query responses and allow querying by username. QUERY_USERNAMES: true + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Logging" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/aws-es-proxy/Chart.yaml b/helm/aws-es-proxy/Chart.yaml index 87ada0a4..80466f8b 100644 --- a/helm/aws-es-proxy/Chart.yaml +++ b/helm/aws-es-proxy/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 +version: 0.1.7 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/aws-es-proxy/README.md b/helm/aws-es-proxy/README.md index 479c3da2..43ce7cb2 100644 --- a/helm/aws-es-proxy/README.md +++ b/helm/aws-es-proxy/README.md @@ -1,33 +1,64 @@ # aws-es-proxy -![Version: 0.1.2](https://img.shields.io/badge/Version-0.1.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.7](https://img.shields.io/badge/Version-0.1.7-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for AWS ES Proxy Service for gen3 +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| | automountServiceAccountToken | bool | `false` | Automount the default service account token | -| autoscaling | bool | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Enable autoscaling | -| autoscaling.maxReplicas | int | `100` | Maximum number of replicas | -| autoscaling.minReplicas | int | `1` | Minimum number of replicas | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | | esEndpoint | str | `"test.us-east-1.es.amazonaws.com"` | Elasticsearch endpoint in AWS | -| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/aws-es-proxy","tag":""}` | Image information | -| image.tag | str | `""` | Image tag | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/aws-es-proxy","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/aws-es-proxy"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| partOf | string | `"Explorer-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | podAnnotations | map | `nil` | Annotations to add to the pod | | ports | list | `[{"containerPort":9200}]` | List of container ports | -| replicaCount | int | `1` | Number of replicas | -| resources | map | `{"limits":{"memory":"2Gi"},"requests":{"cpu":0.1,"memory":"250Mi"}}` | Resource limits | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"memory":"2Gi"},"requests":{"cpu":0.1,"memory":"250Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"memory":"2Gi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.memory | string | `"2Gi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"250Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"250Mi"` | The amount of memory requested | | revisionHistoryLimit | int | `2` | Number of old revisions to retain | | secrets | map | `{"awsAccessKeyId":"","awsSecretAccessKey":""}` | Secret information | | secrets.awsAccessKeyId | str | `""` | AWS access key ID | | secrets.awsSecretAccessKey | str | `""` | AWS secret access key | -| service | map | `{"port":9200,"type":"ClusterIP"}` | Service information | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":9200,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `9200` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | | strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | -| volumeMounts | list | `[{"mountPath":"/root/.aws","name":"credentials","readOnly":true}]` | List of volume mounts | -| volumes | list | `[{"name":"credentials","secret":{"secretName":"aws-es-proxy"}}]` | List of volumes | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| volumeMounts | list | `[{"mountPath":"/root/.aws","name":"credentials","readOnly":true}]` | Volumes to mount to the pod. | +| volumes | list | `[{"name":"credentials","secret":{"secretName":"aws-es-proxy"}}]` | Volumes to attach to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/aws-es-proxy/templates/_helpers.tpl b/helm/aws-es-proxy/templates/_helpers.tpl index e33789e2..ed5de17a 100644 --- a/helm/aws-es-proxy/templates/_helpers.tpl +++ b/helm/aws-es-proxy/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "aws-es-proxy.labels" -}} -helm.sh/chart: {{ include "aws-es-proxy.chart" . }} -{{ include "aws-es-proxy.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "aws-es-proxy.selectorLabels" -}} -app.kubernetes.io/name: {{ include "aws-es-proxy.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ "esproxy" }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/aws-es-proxy/templates/deployment.yaml b/helm/aws-es-proxy/templates/deployment.yaml index 40f2085e..3c74d70e 100644 --- a/helm/aws-es-proxy/templates/deployment.yaml +++ b/helm/aws-es-proxy/templates/deployment.yaml @@ -8,6 +8,9 @@ metadata: {{- end }} labels: {{- include "aws-es-proxy.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -25,6 +28,9 @@ spec: labels: {{- include "aws-es-proxy.selectorLabels" . | nindent 8 }} netvpc: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} {{- with .Values.volumes }} @@ -40,6 +46,9 @@ spec: {{- toYaml . | nindent 12}} {{- end }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: ES_ENDPOINT value: {{ .Values.esEndpoint }} {{- with .Values.volumeMounts }} diff --git a/helm/aws-es-proxy/templates/pdb.yaml b/helm/aws-es-proxy/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/aws-es-proxy/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/aws-es-proxy/templates/tests/test-connection.yaml b/helm/aws-es-proxy/templates/tests/test-connection.yaml index 32c8c104..aa28c31d 100644 --- a/helm/aws-es-proxy/templates/tests/test-connection.yaml +++ b/helm/aws-es-proxy/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "aws-es-proxy.fullname" . }}:{{ .Values.service.port }}'] + args: ['elasticsearch:{{ .Values.service.port }}/'] restartPolicy: Never diff --git a/helm/aws-es-proxy/values.yaml b/helm/aws-es-proxy/values.yaml index 92f649bc..5367ba70 100644 --- a/helm/aws-es-proxy/values.yaml +++ b/helm/aws-es-proxy/values.yaml @@ -1,17 +1,33 @@ +# Default values for aws-es-proxy. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + # -- (map) Annotations to add to the pod podAnnotations: -# -- (bool) Enable autoscaling +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled or not enabled: false - # -- (int) Minimum number of replicas + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 - # -- (int) Maximum number of replicas + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 - # -- (int) Target CPU utilization percentage + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 -# -- (int) Number of replicas +# -- (int) Number of replicas for the deployment. replicaCount: 1 # -- (int) Number of old revisions to retain @@ -21,23 +37,27 @@ revisionHistoryLimit: 2 strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 # -- (bool) Automount the default service account token automountServiceAccountToken: false -# -- (list) List of volumes +# -- (list) Volumes to attach to the pod volumes: - name: credentials secret: secretName: aws-es-proxy -# -- (map) Image information +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/aws-es-proxy + # -- (string) Docker pull policy. pullPolicy: Always - # -- (str) Image tag + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" # -- (list) List of container ports @@ -47,23 +67,30 @@ ports: # -- (str) Elasticsearch endpoint in AWS esEndpoint: test.us-east-1.es.amazonaws.com -# -- (list) List of volume mounts +# -- (list) Volumes to mount to the pod. volumeMounts: - name: credentials readOnly: true mountPath: /root/.aws -# -- (map) Resource limits +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 250Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of memory the container can use memory: 2Gi -# -- (map) Service information +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 9200 # -- (map) Secret information @@ -72,3 +99,23 @@ secrets: awsAccessKeyId: "" # -- (str) AWS secret access key awsSecretAccessKey: "" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Explorer-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/common/Chart.yaml b/helm/common/Chart.yaml index edc949dc..5007c4a7 100644 --- a/helm/common/Chart.yaml +++ b/helm/common/Chart.yaml @@ -15,7 +15,7 @@ type: library # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.8 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm/common/README.md b/helm/common/README.md index 4add2b67..a93a01fa 100644 --- a/helm/common/README.md +++ b/helm/common/README.md @@ -1,6 +1,6 @@ # common -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: library](https://img.shields.io/badge/Type-library-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.8](https://img.shields.io/badge/Version-0.1.8-informational?style=flat-square) ![Type: library](https://img.shields.io/badge/Type-library-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for provisioning databases in gen3 @@ -8,7 +8,7 @@ A Helm chart for provisioning databases in gen3 | Key | Type | Default | Description | |-----|------|---------|-------------| -| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | | global.dev | bool | `true` | Whether the deployment is for development purposes. | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | @@ -28,9 +28,7 @@ A Helm chart for provisioning databases in gen3 | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/common/templates/_datadog_setup.tpl b/helm/common/templates/_datadog_setup.tpl new file mode 100644 index 00000000..4775e833 --- /dev/null +++ b/helm/common/templates/_datadog_setup.tpl @@ -0,0 +1,35 @@ +{{/* + Datadog Labels and Environment variables that will be inserted into the deployment.yaml of any chart the sets ddEnabled to "true". + Will use the parent chart's name and versionn as well as the values "environment", "datadogLogsInjection", "datadogProfilingEnabled", and "datadogTraceSampleRate" defined in the values.yaml file. +*/}} + +{{- define "common.datadogLabels" -}} +tags.datadoghq.com/env: {{ .Values.global.environment }} +tags.datadoghq.com/service: {{ .Chart.Name }} +tags.datadoghq.com/version: {{ .Chart.Version }} +{{- end }} + +{{- define "common.datadogEnvVar" -}} +- name: DD_ENV + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/env'] +- name: DD_SERVICE + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/service'] +- name: DD_VERSION + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/version'] +- name: DD_LOGS_INJECTION + value: {{ .Values.datadogLogsInjection | quote }} +- name: DD_PROFILING_ENABLED + value: {{ .Values.datadogProfilingEnabled | quote }} +- name: DD_TRACE_SAMPLE_RATE + value: {{ .Values.datadogTraceSampleRate | quote }} +- name: DD_AGENT_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP +{{- end }} \ No newline at end of file diff --git a/helm/common/templates/_es_index_restore.tpl b/helm/common/templates/_es_index_restore.tpl index 7953a627..c0c927a1 100644 --- a/helm/common/templates/_es_index_restore.tpl +++ b/helm/common/templates/_es_index_restore.tpl @@ -29,7 +29,7 @@ spec: - name: GEN3_HOME value: /home/ubuntu/cloud-automation - name: ESHOST - value: elasticsearch:9200 + value: {{ default "gen3-elasticsearch-master:9200" $.Values.esEndpoint }} - name: GUPPY_INDICES value: {{ range $.Values.indices }} {{ .index }} {{ end }} - name: GUPPY_CONFIGINDEX diff --git a/helm/common/templates/_labels_setup.tpl b/helm/common/templates/_labels_setup.tpl new file mode 100644 index 00000000..b6ba3eb8 --- /dev/null +++ b/helm/common/templates/_labels_setup.tpl @@ -0,0 +1,37 @@ +{{/* + Gen3 Chart Labels + Will use the parent chart's chart, release, and version as well as the values "release", "criticalService", and "partOf" defined in the values.yaml file. + These values can be completely overwritten with the "selectorLabels" and "commonLabels" provided in the parent chart's values.yaml file. + "selectorLabels" are mainly used for the matchLabels and pod labels in the deployment. + "commonLabels" are mainly used for the deployment's labels. +*/}} + +{{- define "common.commonLabels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.Version }} +app.kubernetes.io/part-of: {{ .Values.partOf }} +app.kubernetes.io/managed-by: "Helm" +app: {{ .Chart.Name }} +{{- if eq .Values.criticalService "true"}} +critical-service: "true" +{{- else }} +critical-service: "false" +{{- end }} +{{- if eq .Values.release "production"}} +release: "production" +{{- else }} +release: "dev" +{{- end }} +{{- end }} + +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ .Chart.Name }} +{{- if eq .Values.release "production"}} +release: "production" +{{- else }} +release: "dev" +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/common/templates/_pdb.tpl b/helm/common/templates/_pdb.tpl new file mode 100644 index 00000000..9fca9c95 --- /dev/null +++ b/helm/common/templates/_pdb.tpl @@ -0,0 +1,16 @@ +{{/* + Gen3 Pod Disruption Budgets + Pdb will help increase availability by ensuring that one pod for each service is always avialable. + Will use the parent chart's name. +*/}} +{{ define "common.pod_disruption_budget" -}} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ .Chart.Name }}-pdb +spec: + minAvailable: {{ .Values.global.minAvialable }} + selector: + matchLabels: + app: {{ .Chart.Name }} +{{- end }} \ No newline at end of file diff --git a/helm/common/values.yaml b/helm/common/values.yaml index 54d7422f..d61b0b39 100644 --- a/helm/common/values.yaml +++ b/helm/common/values.yaml @@ -34,13 +34,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true diff --git a/helm/dicom-server/Chart.yaml b/helm/dicom-server/Chart.yaml index 060d9bea..5605eab7 100644 --- a/helm/dicom-server/Chart.yaml +++ b/helm/dicom-server/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.6 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/dicom-server/README.md b/helm/dicom-server/README.md index 889bb680..fd8c52f9 100644 --- a/helm/dicom-server/README.md +++ b/helm/dicom-server/README.md @@ -1,50 +1,58 @@ # dicom-server -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.6](https://img.shields.io/badge/Version-0.1.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Dicom Server +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/gen3-orthanc"` | | -| image.tag | string | `"master"` | | -| livenessProbe.httpGet.path | string | `"/system"` | | -| livenessProbe.httpGet.port | int | `8042` | | -| livenessProbe.initialDelaySeconds | int | `5` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| ports[0].containerPort | int | `8042` | | -| readinessProbe.httpGet.path | string | `"/system"` | | -| readinessProbe.httpGet.port | int | `8042` | | -| readinessProbe.initialDelaySeconds | int | `5` | | -| readinessProbe.periodSeconds | int | `20` | | -| readinessProbe.timeoutSeconds | int | `30` | | -| replicaCount | int | `1` | | -| secrets.authenticationEnabled | bool | `false` | | -| secrets.dataBase | string | `"postgres"` | | -| secrets.enableIndex | bool | `true` | | -| secrets.enableStorage | bool | `true` | | -| secrets.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | | -| secrets.indexConnectionsCount | int | `5` | | -| secrets.lock | bool | `false` | | -| secrets.password | string | `"postgres"` | | -| secrets.port | string | `"5432"` | | -| secrets.userName | string | `"postgres"` | | -| service.port | int | `80` | | -| service.targetport | int | `8042` | | -| volumeMounts[0].mountPath | string | `"/etc/orthanc/orthanc_config_overwrites.json"` | | -| volumeMounts[0].name | string | `"config-volume-g3auto"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"orthanc_config_overwrites.json"` | | -| volumes[0].name | string | `"config-volume-g3auto"` | | -| volumes[0].secret.secretName | string | `"orthanc-g3auto"` | | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/gen3-orthanc","tag":"master"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/gen3-orthanc"` | Docker repository. | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| partOf | string | `"Imaging"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| secrets | map | `{"authenticationEnabled":false,"dataBase":"postgres","enableIndex":true,"enableStorage":true,"host":"postgres-postgresql.postgres.svc.cluster.local","indexConnectionsCount":5,"lock":false,"password":"postgres","port":"5432","userName":"postgres"}` | Secret information | +| secrets.authenticationEnabled | bool | `false` | Whether or not the password protection is enabled. | +| secrets.dataBase | string | `"postgres"` | Database name for postgres. | +| secrets.enableIndex | bool | `true` | Whether to enable index. If set to "false", Orthanc will continue to use its default SQLite back-end. | +| secrets.enableStorage | bool | `true` | Whether to enable storage. If set to "false", Orthanc will continue to use its default filesystem storage area. | +| secrets.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | Hostname for postgres server. | +| secrets.indexConnectionsCount | int | `5` | The number of connections from the index plugin to the PostgreSQL database. | +| secrets.lock | bool | `false` | Whether to lock the database. | +| secrets.password | string | `"postgres"` | Password for Postgres. | +| secrets.port | string | `"5432"` | Port for Postgres. | +| secrets.userName | string | `"postgres"` | Username for postgres. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"targetport":8042}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.targetport | int | `8042` | The port on the host machine that traffic is directed to. | +| volumeMounts | list | `[{"mountPath":"/etc/orthanc/orthanc_config_overwrites.json","name":"config-volume-g3auto","readOnly":true,"subPath":"orthanc_config_overwrites.json"}]` | Volumes to mount to the pod. | +| volumes | list | `[{"name":"config-volume-g3auto","secret":{"secretName":"orthanc-g3auto"}}]` | Volumes to attach to the pod. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/dicom-server/templates/_helpers.tpl b/helm/dicom-server/templates/_helpers.tpl index 2bfab706..0ad87443 100644 --- a/helm/dicom-server/templates/_helpers.tpl +++ b/helm/dicom-server/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "dicom-server.labels" -}} -helm.sh/chart: {{ include "dicom-server.chart" . }} -{{ include "dicom-server.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "dicom-server.selectorLabels" -}} -app.kubernetes.io/name: {{ include "dicom-server.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: "dicom-server" +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/dicom-server/templates/deployment.yaml b/helm/dicom-server/templates/deployment.yaml index 2f5b8fd7..d7090ab9 100644 --- a/helm/dicom-server/templates/deployment.yaml +++ b/helm/dicom-server/templates/deployment.yaml @@ -8,6 +8,9 @@ metadata: {{- end }} labels: {{- include "dicom-server.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -21,8 +24,10 @@ spec: metadata: labels: {{- include "dicom-server.selectorLabels" . | nindent 8 }} - release: "production" public: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.volumes }} volumes: @@ -32,18 +37,26 @@ spec: - name: "dicom-server" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.readinessProbe}} readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.livenessProbe}} + httpGet: + path: /system + port: 8042 + initialDelaySeconds: 5 + periodSeconds: 20 + timeoutSeconds: 30 livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.ports}} + httpGet: + path: /system + port: 8042 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 30 ports: - {{- toYaml . | nindent 12}} - {{- end }} + - containerPort: 8042 + env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/dicom-server/templates/pdb.yaml b/helm/dicom-server/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/dicom-server/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/dicom-server/values.yaml b/helm/dicom-server/values.yaml index 1457059e..d4dcdcc7 100644 --- a/helm/dicom-server/values.yaml +++ b/helm/dicom-server/values.yaml @@ -2,66 +2,102 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + # Deployment + +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (list) Volumes to attach to the pod. volumes: - name: config-volume-g3auto secret: secretName: orthanc-g3auto +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/gen3-orthanc + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "master" -readinessProbe: - httpGet: - path: /system - port: 8042 - initialDelaySeconds: 5 - periodSeconds: 20 - timeoutSeconds: 30 - -livenessProbe: - httpGet: - path: /system - port: 8042 - initialDelaySeconds: 5 - periodSeconds: 60 - timeoutSeconds: 30 - -ports: - - containerPort: 8042 - +# -- (list) Volumes to mount to the pod. volumeMounts: - name: config-volume-g3auto readOnly: true mountPath: /etc/orthanc/orthanc_config_overwrites.json subPath: orthanc_config_overwrites.json - # Service and Pod +# -- (map) Kubernetes service information. service: + # -- (int) The port number that the service exposes. port: 80 + # -- (int) The port on the host machine that traffic is directed to. targetport: 8042 -# Secret +# -- (map) Secret information secrets: + # -- (bool) Whether or not the password protection is enabled. authenticationEnabled: false + # -- (bool) Whether to enable index. If set to "false", Orthanc will continue to use its default SQLite back-end. enableIndex: true + # -- (bool) Whether to enable storage. If set to "false", Orthanc will continue to use its default filesystem storage area. enableStorage: true + # -- (string) Port for Postgres. port: "5432" + # -- (string) Hostname for postgres server. host: postgres-postgresql.postgres.svc.cluster.local + # -- (string) Database name for postgres. dataBase: postgres + # -- (string) Username for postgres. userName: postgres + # -- (string) Password for Postgres. password: postgres + # -- (int) The number of connections from the index plugin to the PostgreSQL database. indexConnectionsCount: 5 + # -- (bool) Whether to lock the database. lock: false + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Imaging" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/dicom-viewer/Chart.yaml b/helm/dicom-viewer/Chart.yaml index 94bd9e48..dd84c125 100644 --- a/helm/dicom-viewer/Chart.yaml +++ b/helm/dicom-viewer/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.6 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/dicom-viewer/README.md b/helm/dicom-viewer/README.md index 5289bcdb..78ea3e63 100644 --- a/helm/dicom-viewer/README.md +++ b/helm/dicom-viewer/README.md @@ -1,34 +1,45 @@ # dicom-viewer -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.6](https://img.shields.io/badge/Version-0.1.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Dicom Viewer +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/ohif-viewer"` | | -| image.tag | string | `"master"` | | -| livenessProbe.httpGet.path | string | `"/"` | | -| livenessProbe.httpGet.port | int | `80` | | -| livenessProbe.initialDelaySeconds | int | `5` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| ports[0].containerPort | int | `80` | | -| readinessProbe.httpGet.path | string | `"/"` | | -| readinessProbe.httpGet.port | int | `80` | | -| readinessProbe.initialDelaySeconds | int | `5` | | -| readinessProbe.periodSeconds | int | `20` | | -| readinessProbe.timeoutSeconds | int | `30` | | -| replicaCount | int | `1` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/ohif-viewer","tag":"master"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/ohif-viewer"` | Docker repository. | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| partOf | string | `"Imaging"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/dicom-viewer/templates/_helpers.tpl b/helm/dicom-viewer/templates/_helpers.tpl index a47308cd..7bbc396e 100644 --- a/helm/dicom-viewer/templates/_helpers.tpl +++ b/helm/dicom-viewer/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "dicom-viewer.labels" -}} -helm.sh/chart: {{ include "dicom-viewer.chart" . }} -{{ include "dicom-viewer.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "dicom-viewer.selectorLabels" -}} -app.kubernetes.io/name: {{ include "dicom-viewer.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: "dicom-viewer" +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/dicom-viewer/templates/deployment.yaml b/helm/dicom-viewer/templates/deployment.yaml index 55482af4..a34e4089 100644 --- a/helm/dicom-viewer/templates/deployment.yaml +++ b/helm/dicom-viewer/templates/deployment.yaml @@ -8,6 +8,9 @@ metadata: {{- end }} labels: {{- include "dicom-viewer.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -15,31 +18,40 @@ spec: selector: matchLabels: {{- include "dicom-viewer.selectorLabels" . | nindent 6 }} - release: "production" public: "yes" template: metadata: labels: {{- include "dicom-viewer.selectorLabels" . | nindent 8 }} - release: "production" public: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: containers: - name: "dicom-viewer" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.readinessProbe}} - readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.livenessProbe}} - livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.ports}} + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 20 + timeoutSeconds: 30 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 30 ports: - {{- toYaml . | nindent 12}} - {{- end }} + - containerPort: 80 + env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/dicom-viewer/templates/pdb.yaml b/helm/dicom-viewer/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/dicom-viewer/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/dicom-viewer/values.yaml b/helm/dicom-viewer/values.yaml index f38d8997..0fa31147 100644 --- a/helm/dicom-viewer/values.yaml +++ b/helm/dicom-viewer/values.yaml @@ -2,42 +2,66 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + # Deployment + +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/ohif-viewer + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "master" -readinessProbe: - httpGet: - path: / - port: 80 - initialDelaySeconds: 5 - periodSeconds: 20 - timeoutSeconds: 30 - -livenessProbe: - httpGet: - path: / - port: 80 - initialDelaySeconds: 5 - periodSeconds: 60 - timeoutSeconds: 30 - -ports: - - containerPort: 80 - - # Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Imaging" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/elasticsearch/README.md b/helm/elasticsearch/README.md deleted file mode 100644 index 7199547c..00000000 --- a/helm/elasticsearch/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# elasticsearch - -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.16.0](https://img.shields.io/badge/AppVersion-1.16.0-informational?style=flat-square) - -A Helm chart for Kubernetes - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| fullnameOverride | string | `""` | | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"quay.io/cdis/elasticsearch"` | | -| image.tag | string | `"feat_es_dockerfile"` | | -| imagePullSecrets | list | `[]` | | -| ingress.annotations | object | `{}` | | -| ingress.className | string | `""` | | -| ingress.enabled | bool | `false` | | -| ingress.hosts[0].host | string | `"chart-example.local"` | | -| ingress.hosts[0].paths[0].path | string | `"/"` | | -| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | -| ingress.tls | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | string | `"500m"` | | -| resources.limits.memory | string | `"750Mi"` | | -| resources.requests.cpu | string | `"500m"` | | -| resources.requests.memory | string | `"750Mi"` | | -| securityContext | object | `{}` | | -| service.port | int | `9200` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| tolerations | list | `[]` | | - ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/elasticsearch/templates/NOTES.txt b/helm/elasticsearch/templates/NOTES.txt deleted file mode 100644 index bf80dccb..00000000 --- a/helm/elasticsearch/templates/NOTES.txt +++ /dev/null @@ -1,22 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "elasticsearch.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "elasticsearch.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "elasticsearch.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "elasticsearch.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/helm/elasticsearch/templates/deployment.yaml b/helm/elasticsearch/templates/deployment.yaml deleted file mode 100644 index 78b75dfe..00000000 --- a/helm/elasticsearch/templates/deployment.yaml +++ /dev/null @@ -1,92 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: elasticsearch-deployment - labels: - {{- include "elasticsearch.labels" . | nindent 4 }} -spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "elasticsearch.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "elasticsearch.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "elasticsearch.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - initContainers: - - name: permissions-fix - image: busybox - volumeMounts: - - name: elasticsearch-data - mountPath: /var/lib/elasticsearch - command: ['sh', '-c', 'mkdir -p /var/lib/elasticsearch && chown 101:101 /var/lib/elasticsearch' ] - securityContext: - privileged: true - runAsUser: 0 - - name: sysctl - image: busybox - securityContext: - privileged: true - runAsUser: 0 - command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144'] - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - env: - - name: ES_JAVA_OPTS - value: "-Xms300m -Xmx300m" - ports: - - name: http - containerPort: 9200 - protocol: TCP - - name: transport - containerPort: 9300 - protocol: TCP - livenessProbe: - httpGet: - path: /_cluster/health?local=true - port: 9200 - initialDelaySeconds: 90 - readinessProbe: - httpGet: - path: /_cluster/health - port: 9200 - initialDelaySeconds: 5 - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: elasticsearch-data - mountPath: /var/lib/elasticsearch - volumes: - - name: elasticsearch-data - persistentVolumeClaim: - claimName: elasticsearch-pvc - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/helm/elasticsearch/templates/pvc.yaml b/helm/elasticsearch/templates/pvc.yaml deleted file mode 100644 index b8c3a8f9..00000000 --- a/helm/elasticsearch/templates/pvc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: elasticsearch-pvc -spec: - storageClassName: premium-rwo - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 20Gi diff --git a/helm/elasticsearch/templates/serviceaccount.yaml b/helm/elasticsearch/templates/serviceaccount.yaml deleted file mode 100644 index 1f191c55..00000000 --- a/helm/elasticsearch/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "elasticsearch.serviceAccountName" . }} - labels: - {{- include "elasticsearch.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/helm/elasticsearch/templates/tests/test-connection.yaml b/helm/elasticsearch/templates/tests/test-connection.yaml deleted file mode 100644 index af8dd035..00000000 --- a/helm/elasticsearch/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "elasticsearch.fullname" . }}-test-connection" - labels: - {{- include "elasticsearch.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "elasticsearch.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/helm/elasticsearch/values.yaml b/helm/elasticsearch/values.yaml deleted file mode 100644 index 45e6e78d..00000000 --- a/helm/elasticsearch/values.yaml +++ /dev/null @@ -1,85 +0,0 @@ -# Default values for elasticsearch. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: quay.io/cdis/elasticsearch - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "feat_es_dockerfile" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - 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: "" - -podAnnotations: {} - -podSecurityContext: - # fsGroup: 1000 - # runAsGroup: 1000 - runAsUser: 101 - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 9200 - -ingress: - enabled: false - className: "" - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: - limits: - cpu: 500m - memory: 750Mi - requests: - cpu: 500m - memory: 750Mi - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - -tolerations: [] - -affinity: {} - - -kibana: - elasticsearchHosts: "http://elasticsearch:9200" \ No newline at end of file diff --git a/helm/elasticsearch/Chart.yaml b/helm/etl/Chart.yaml similarity index 87% rename from helm/elasticsearch/Chart.yaml rename to helm/etl/Chart.yaml index 5204cb9b..0f9e2fb9 100644 --- a/helm/elasticsearch/Chart.yaml +++ b/helm/etl/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 -name: elasticsearch -description: A Helm chart for Kubernetes +name: etl +description: A Helm chart for gen3 etl # A chart can be either an 'application' or a 'library' chart. # @@ -15,10 +15,11 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" +appVersion: "master" + +dependencies: [] diff --git a/helm/etl/README.md b/helm/etl/README.md new file mode 100644 index 00000000..f874e334 --- /dev/null +++ b/helm/etl/README.md @@ -0,0 +1,107 @@ +# etl + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) + +A Helm chart for gen3 etl + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| esEndpoint | string | `"gen3-elasticsearch-master"` | | +| etlMapping.mappings[0].aggregated_props[0].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[0].name | string | `"_samples_count"` | | +| etlMapping.mappings[0].aggregated_props[0].path | string | `"samples"` | | +| etlMapping.mappings[0].aggregated_props[1].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[1].name | string | `"_aliquots_count"` | | +| etlMapping.mappings[0].aggregated_props[1].path | string | `"samples.aliquots"` | | +| etlMapping.mappings[0].aggregated_props[2].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[2].name | string | `"_submitted_methylations_count"` | | +| etlMapping.mappings[0].aggregated_props[2].path | string | `"samples.aliquots.submitted_methylation_files"` | | +| etlMapping.mappings[0].aggregated_props[3].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[3].name | string | `"_submitted_copy_number_files_on_aliquots_count"` | | +| etlMapping.mappings[0].aggregated_props[3].path | string | `"samples.aliquots.submitted_copy_number_files"` | | +| etlMapping.mappings[0].aggregated_props[4].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[4].name | string | `"_read_groups_count"` | | +| etlMapping.mappings[0].aggregated_props[4].path | string | `"samples.aliquots.read_groups"` | | +| etlMapping.mappings[0].aggregated_props[5].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[5].name | string | `"_submitted_aligned_reads_count"` | | +| etlMapping.mappings[0].aggregated_props[5].path | string | `"samples.aliquots.read_groups.submitted_aligned_reads_files"` | | +| etlMapping.mappings[0].aggregated_props[6].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[6].name | string | `"_submitted_unaligned_reads_count"` | | +| etlMapping.mappings[0].aggregated_props[6].path | string | `"samples.aliquots.read_groups.submitted_unaligned_reads_files"` | | +| etlMapping.mappings[0].aggregated_props[7].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[7].name | string | `"_submitted_copy_number_files_on_read_groups_count"` | | +| etlMapping.mappings[0].aggregated_props[7].path | string | `"samples.aliquots.read_groups.submitted_copy_number_files"` | | +| etlMapping.mappings[0].aggregated_props[8].fn | string | `"count"` | | +| etlMapping.mappings[0].aggregated_props[8].name | string | `"_submitted_somatic_mutations_count"` | | +| etlMapping.mappings[0].aggregated_props[8].path | string | `"samples.aliquots.read_groups.submitted_somatic_mutations"` | | +| etlMapping.mappings[0].doc_type | string | `"case"` | | +| etlMapping.mappings[0].flatten_props[0].path | string | `"demographics"` | | +| etlMapping.mappings[0].flatten_props[0].props[0].name | string | `"gender"` | | +| etlMapping.mappings[0].flatten_props[0].props[0].value_mappings[0].female | string | `"F"` | | +| etlMapping.mappings[0].flatten_props[0].props[0].value_mappings[1].male | string | `"M"` | | +| etlMapping.mappings[0].flatten_props[0].props[1].name | string | `"race"` | | +| etlMapping.mappings[0].flatten_props[0].props[1].value_mappings[0]."american indian or alaskan native" | string | `"Indian"` | | +| etlMapping.mappings[0].flatten_props[0].props[2].name | string | `"ethnicity"` | | +| etlMapping.mappings[0].flatten_props[0].props[3].name | string | `"year_of_birth"` | | +| etlMapping.mappings[0].joining_props[0].index | string | `"file"` | | +| etlMapping.mappings[0].joining_props[0].join_on | string | `"_case_id"` | | +| etlMapping.mappings[0].joining_props[0].props[0].fn | string | `"set"` | | +| etlMapping.mappings[0].joining_props[0].props[0].name | string | `"data_format"` | | +| etlMapping.mappings[0].joining_props[0].props[0].src | string | `"data_format"` | | +| etlMapping.mappings[0].joining_props[0].props[1].fn | string | `"set"` | | +| etlMapping.mappings[0].joining_props[0].props[1].name | string | `"data_type"` | | +| etlMapping.mappings[0].joining_props[0].props[1].src | string | `"data_type"` | | +| etlMapping.mappings[0].joining_props[0].props[2].fn | string | `"set"` | | +| etlMapping.mappings[0].joining_props[0].props[2].name | string | `"_file_id"` | | +| etlMapping.mappings[0].joining_props[0].props[2].src | string | `"_file_id"` | | +| etlMapping.mappings[0].name | string | `"dev_case"` | | +| etlMapping.mappings[0].props[0].name | string | `"submitter_id"` | | +| etlMapping.mappings[0].props[1].name | string | `"project_id"` | | +| etlMapping.mappings[0].props[2].name | string | `"disease_type"` | | +| etlMapping.mappings[0].props[3].name | string | `"primary_site"` | | +| etlMapping.mappings[0].root | string | `"case"` | | +| etlMapping.mappings[0].type | string | `"aggregator"` | | +| etlMapping.mappings[1].category | string | `"data_file"` | | +| etlMapping.mappings[1].doc_type | string | `"file"` | | +| etlMapping.mappings[1].injecting_props.case.props[0].fn | string | `"set"` | | +| etlMapping.mappings[1].injecting_props.case.props[0].name | string | `"_case_id"` | | +| etlMapping.mappings[1].injecting_props.case.props[0].src | string | `"id"` | | +| etlMapping.mappings[1].injecting_props.case.props[1].name | string | `"project_id"` | | +| etlMapping.mappings[1].name | string | `"dev_file"` | | +| etlMapping.mappings[1].props[0].name | string | `"object_id"` | | +| etlMapping.mappings[1].props[1].name | string | `"md5sum"` | | +| etlMapping.mappings[1].props[2].name | string | `"file_name"` | | +| etlMapping.mappings[1].props[3].name | string | `"file_size"` | | +| etlMapping.mappings[1].props[4].name | string | `"data_format"` | | +| etlMapping.mappings[1].props[5].name | string | `"data_type"` | | +| etlMapping.mappings[1].props[6].name | string | `"state"` | | +| etlMapping.mappings[1].root | string | `"None"` | | +| etlMapping.mappings[1].target_nodes[0].name | string | `"slide_image"` | | +| etlMapping.mappings[1].target_nodes[0].path | string | `"slides.samples.cases"` | | +| etlMapping.mappings[1].type | string | `"collector"` | | +| image.spark.pullPolicy | string | `"Always"` | When to pull the image. This value should be "Always" to ensure the latest image is used. | +| image.spark.repository | string | `"quay.io/cdis/gen3-spark"` | The Docker image repository for the spark service | +| image.spark.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| image.tube.pullPolicy | string | `"Always"` | When to pull the image. This value should be "Always" to ensure the latest image is used. | +| image.tube.repository | string | `"quay.io/cdis/tube"` | The Docker image repository for the fence service | +| image.tube.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| podAnnotations | map | `{}` | Annotations to add to the pod | +| resources | map | `{"spark":{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":0.3,"memory":"128Mi"}},"tube":{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":0.3,"memory":"128Mi"}}}` | Resource requests and limits for the containers in the pod | +| resources.spark.limits | map | `{"cpu":1,"memory":"2Gi"}` | The maximum amount of resources that the container is allowed to use | +| resources.spark.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.spark.limits.memory | string | `"2Gi"` | The maximum amount of memory the container can use | +| resources.spark.requests | map | `{"cpu":0.3,"memory":"128Mi"}` | The amount of resources that the container requests | +| resources.spark.requests.cpu | string | `0.3` | The amount of CPU requested | +| resources.spark.requests.memory | string | `"128Mi"` | The amount of memory requested | +| resources.tube.limits | map | `{"cpu":1,"memory":"2Gi"}` | The maximum amount of resources that the container is allowed to use | +| resources.tube.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.tube.limits.memory | string | `"2Gi"` | The maximum amount of memory the container can use | +| resources.tube.requests | map | `{"cpu":0.3,"memory":"128Mi"}` | The amount of resources that the container requests | +| resources.tube.requests.cpu | string | `0.3` | The amount of CPU requested | +| resources.tube.requests.memory | string | `"128Mi"` | The amount of memory requested | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/etl/templates/etl-job.yaml b/helm/etl/templates/etl-job.yaml new file mode 100644 index 00000000..0b306d07 --- /dev/null +++ b/helm/etl/templates/etl-job.yaml @@ -0,0 +1,211 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: etl-cronjob +spec: + schedule: "0 0 1 1 */5" + jobTemplate: + spec: + backoffLimit: 0 + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} + labels: + app: gen3job + spec: + shareProcessNamespace: true + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: karpenter.sh/capacity-type + operator: In + values: + - on-demand + - weight: 99 + preference: + matchExpressions: + - key: eks.amazonaws.com/capacityType + operator: In + values: + - ONDEMAND + volumes: + - name: signal-volume + emptyDir: {} + - name: creds-volume + secret: + secretName: "peregrine-dbcreds" + - name: etl-mapping + configMap: + name: etl-mapping + - name: fence-yaml + configMap: + name: useryaml + containers: + - name: gen3-spark + image: {{ .Values.image.spark.repository }}:{{ .Values.image.spark.tag }} + ports: + - containerPort: 22 + - containerPort: 9000 + - containerPort: 8030 + - containerPort: 8031 + - containerPort: 8032 + - containerPort: 7077 + readinessProbe: + tcpSocket: + port: 9000 + initialDelaySeconds: 10 + periodSeconds: 30 + env: + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: HADOOP_URL + value: hdfs://0.0.0.0:9000 + - name: HADOOP_HOST + value: 0.0.0.0 + volumeMounts: + - mountPath: /usr/share/pod + name: signal-volume + readOnly: true + imagePullPolicy: {{ .Values.image.spark.pullPolicy }} + resources: + requests: + cpu: {{ .Values.resources.spark.requests.cpu }} + memory: {{ .Values.resources.spark.requests.memory }} + # limits: + # cpu: {{ .Values.resources.spark.limits.cpu }} + # memory: {{ .Values.resources.spark.limits.memory }} + command: ["/bin/bash" ] + args: + - "-c" + - | + trap 'exit 0' SIGINT SIGQUIT SIGTERM + # get /usr/local/share/ca-certificates/cdis-ca.crt into system bundle + ssh server sudo /etc/init.d/ssh start + # update-ca-certificates + python run_config.py + hdfs namenode -format + hdfs --daemon start namenode + hdfs --daemon start datanode + yarn --daemon start resourcemanager + yarn --daemon start nodemanager + hdfs dfsadmin -safemode leave + hdfs dfs -mkdir /result + hdfs dfs -mkdir /jars + hdfs dfs -mkdir /archive + /spark/sbin/start-all.sh + while true; do sleep 5; done + - name: tube + imagePullPolicy: IfNotPresent + # image: quay.io/cdis/tube:feat_helm_test + image: {{ .Values.image.tube.repository }}:{{ .Values.image.tube.tag }} + ports: + - containerPort: 80 + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: peregrine-dbcreds + key: host + - name: DB_DATABASE + valueFrom: + secretKeyRef: + name: sheepdog-dbcreds + key: database + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: sheepdog-dbcreds + key: username + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: sheepdog-dbcreds + key: password + - name: DB_PORT + valueFrom: + secretKeyRef: + name: sheepdog-dbcreds + key: port + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: HADOOP_URL + value: hdfs://localhost:9000 + - name: ES_URL + value: {{ .Values.esEndpoint }} + - name: HADOOP_HOST + value: localhost + - name: HADOOP_CLIENT_OPTS + value: -Xmx1g + - name: SPARK_EXECUTOR_MEMORY + value: 4g + - name: SPARK_DRIVER_MEMORY + value: 6g + - name: ETL_FORCED + value: "TRUE" + - name: gen3Env + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: slackWebHook + valueFrom: + configMapKeyRef: + name: global + key: slack_webhook + optional: true + volumeMounts: + # - name: "creds-volume" + # readOnly: true + # mountPath: "/gen3/tube/creds.json" + # subPath: creds.json + # Volume to signal when to kill spark + - mountPath: /usr/share/pod + name: signal-volume + - name: "etl-mapping" + readOnly: true + mountPath: "/gen3/tube/etlMapping.yaml" + subPath: "etlMapping.yaml" + - name: "fence-yaml" + readOnly: true + mountPath: "/gen3/tube/user.yaml" + subPath: useryaml + resources: + requests: + cpu: {{ .Values.resources.tube.requests.cpu }} + memory: {{ .Values.resources.tube.requests.memory }} + # limits: + # cpu: {{ .Values.resources.tube.limits.cpu }} + # memory: {{ .Values.resources.tube.limits.memory }} + command: ["/bin/bash"] + args: + - "-c" + - | + while ! bash -c "echo >/dev/tcp/localhost/9000"; do + echo "Spark is not ready on port 9000... waiting for 10 seconds." + sleep 10 + done + + # Port 9000 is open, continue with the rest of the script + echo "Port 9000 is now open. Continuing with the script..." + + echo "python run_config.py && python run_etl.py" + python run_config.py && python run_etl.py + exitcode=$? + + # Kill sidecar and all processes + echo "Exit code: $exitcode" + pkill -u root && exit $exitcode + exit "$exitcode" & + restartPolicy: Never \ No newline at end of file diff --git a/helm/etl/templates/etl-mapping.yaml b/helm/etl/templates/etl-mapping.yaml new file mode 100644 index 00000000..184a3e25 --- /dev/null +++ b/helm/etl/templates/etl-mapping.yaml @@ -0,0 +1,10 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: etl-mapping +data: + etlMapping.yaml: | + {{- with .Values.etlMapping }} + {{- toYaml . | nindent 4 }} + {{ end }} +--- \ No newline at end of file diff --git a/helm/etl/values.yaml b/helm/etl/values.yaml new file mode 100644 index 00000000..1db9765e --- /dev/null +++ b/helm/etl/values.yaml @@ -0,0 +1,145 @@ +# populate with normal values from a regular chart created by helm create + +image: + tube: + # -- (string) The Docker image repository for the fence service + repository: quay.io/cdis/tube + # -- (string) When to pull the image. This value should be "Always" to ensure the latest image is used. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "master" + spark: + # -- (string) The Docker image repository for the spark service + repository: quay.io/cdis/gen3-spark + # -- (string) When to pull the image. This value should be "Always" to ensure the latest image is used. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "master" + + +# -- (list) Docker image pull secrets. +imagePullSecrets: [] + +# -- (map) Annotations to add to the pod +podAnnotations: {} + + +# -- (map) Resource requests and limits for the containers in the pod +resources: + tube: + # -- (map) The amount of resources that the container requests + requests: + # -- (string) The amount of CPU requested + cpu: 0.3 + # -- (string) The amount of memory requested + memory: 128Mi + # -- (map) The maximum amount of resources that the container is allowed to use + limits: + # -- (string) The maximum amount of CPU the container can use + cpu: 1.0 + # -- (string) The maximum amount of memory the container can use + memory: 2Gi + spark: + # -- (map) The amount of resources that the container requests + requests: + # -- (string) The amount of CPU requested + cpu: 0.3 + # -- (string) The amount of memory requested + memory: 128Mi + # -- (map) The maximum amount of resources that the container is allowed to use + limits: + # -- (string) The maximum amount of CPU the container can use + cpu: 1.0 + # -- (string) The maximum amount of memory the container can use + memory: 2Gi + + +esEndpoint: gen3-elasticsearch-master + +etlMapping: + mappings: + - name: dev_case + doc_type: case + type: aggregator + root: case + props: + - name: submitter_id + - name: project_id + - name: disease_type + - name: primary_site + flatten_props: + - path: demographics + props: + - name: gender + value_mappings: + - female: F + - male: M + - name: race + value_mappings: + - american indian or alaskan native: Indian + - name: ethnicity + - name: year_of_birth + aggregated_props: + - name: _samples_count + path: samples + fn: count + - name: _aliquots_count + path: samples.aliquots + fn: count + - name: _submitted_methylations_count + path: samples.aliquots.submitted_methylation_files + fn: count + - name: _submitted_copy_number_files_on_aliquots_count + path: samples.aliquots.submitted_copy_number_files + fn: count + - name: _read_groups_count + path: samples.aliquots.read_groups + fn: count + - name: _submitted_aligned_reads_count + path: samples.aliquots.read_groups.submitted_aligned_reads_files + fn: count + - name: _submitted_unaligned_reads_count + path: samples.aliquots.read_groups.submitted_unaligned_reads_files + fn: count + - name: _submitted_copy_number_files_on_read_groups_count + path: samples.aliquots.read_groups.submitted_copy_number_files + fn: count + - name: _submitted_somatic_mutations_count + path: samples.aliquots.read_groups.submitted_somatic_mutations + fn: count + joining_props: + - index: file + join_on: _case_id + props: + - name: data_format + src: data_format + fn: set + - name: data_type + src: data_type + fn: set + - name: _file_id + src: _file_id + fn: set + - name: dev_file + doc_type: file + type: collector + root: None + category: data_file + props: + - name: object_id + - name: md5sum + - name: file_name + - name: file_size + - name: data_format + - name: data_type + - name: state + injecting_props: + case: + props: + - name: _case_id + src: id + fn: set + - name: project_id + target_nodes: + - name: slide_image + path: slides.samples.cases diff --git a/helm/fence/Chart.yaml b/helm/fence/Chart.yaml index e7cd5f43..d3203de8 100644 --- a/helm/fence/Chart.yaml +++ b/helm/fence/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.14 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -24,7 +24,7 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common - name: postgresql version: 11.9.13 diff --git a/helm/fence/README.md b/helm/fence/README.md index 289d46d1..f80d4215 100644 --- a/helm/fence/README.md +++ b/helm/fence/README.md @@ -1,6 +1,6 @@ # fence -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.14](https://img.shields.io/badge/Version-0.1.14-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Fence @@ -8,14 +8,14 @@ A Helm chart for gen3 Fence | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | | https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| FENCE_CONFIG | map | `{"ACCESS_TOKEN_COOKIE_NAME":"access_token","ACCESS_TOKEN_EXPIRES_IN":1200,"ALLOWED_USER_SERVICE_ACCOUNT_DOMAINS":["developer.gserviceaccount.com","appspot.gserviceaccount.com","iam.gserviceaccount.com"],"ALLOW_GOOGLE_LINKING":true,"APPLICATION_ROOT":"/user","APP_NAME":"Gen3 Data Commons","ARBORIST":null,"ASSUME_ROLE_CACHE_SECONDS":1800,"AUDIT_SERVICE":"http://audit-service","AUTHLIB_INSECURE_TRANSPORT":true,"AWS_CREDENTIALS":{},"AZ_BLOB_CONTAINER_URL":"https://myfakeblob.blob.core.windows.net/my-fake-container/","AZ_BLOB_CREDENTIALS":null,"BILLING_PROJECT_FOR_SA_CREDS":null,"BILLING_PROJECT_FOR_SIGNED_URLS":null,"CIRRUS_CFG":{"GOOGLE_ADMIN_EMAIL":"","GOOGLE_API_KEY":"","GOOGLE_APPLICATION_CREDENTIALS":"","GOOGLE_CLOUD_IDENTITY_ADMIN_EMAIL":"","GOOGLE_IDENTITY_DOMAIN":"","GOOGLE_PROJECT_ID":"","GOOGLE_STORAGE_CREDS":""},"CLIENT_ALLOWED_SCOPES":["openid","user","data","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"DATA_UPLOAD_BUCKET":"bucket1","DBGAP_ACCESSION_WITH_CONSENT_REGEX":"(?Pphs[0-9]+)(.(?Pv[0-9]+)){0,1}(.(?Pp[0-9]+)){0,1}.(?Pc[0-9]+)","DEBUG":false,"DEFAULT_LOGIN_IDP":"google","DEFAULT_LOGIN_URL":"{{BASE_URL}}/login/google","DEV_LOGIN_COOKIE_NAME":"dev_login","DREAM_CHALLENGE_GROUP":"DREAM","DREAM_CHALLENGE_TEAM":"DREAM","EMAIL_SERVER":"localhost","ENABLED_IDENTITY_PROVIDERS":{},"ENABLE_AUDIT_LOGS":{"login":false,"presigned_url":false},"ENABLE_AUTOMATIC_BILLING_PERMISSION_SA_CREDS":false,"ENABLE_AUTOMATIC_BILLING_PERMISSION_SIGNED_URLS":false,"ENABLE_CSRF_PROTECTION":true,"ENABLE_DB_MIGRATION":true,"ENABLE_PROMETHEUS_METRICS":false,"ENCRYPTION_KEY":"REPLACEME","GA4GH_VISA_ISSUER_ALLOWLIST":["{{BASE_URL}}","https://sts.nih.gov","https://stsstg.nih.gov"],"GEN3_PASSPORT_EXPIRES_IN":43200,"GLOBAL_PARSE_VISAS_ON_LOGIN":null,"GOOGLE_ACCOUNT_ACCESS_EXPIRES_IN":86400,"GOOGLE_BULK_UPDATES":false,"GOOGLE_GROUP_PREFIX":"","GOOGLE_MANAGED_SERVICE_ACCOUNT_DOMAINS":["dataflow-service-producer-prod.iam.gserviceaccount.com","cloudbuild.gserviceaccount.com","cloud-ml.google.com.iam.gserviceaccount.com","container-engine-robot.iam.gserviceaccount.com","dataflow-service-producer-prod.iam.gserviceaccount.com","sourcerepo-service-accounts.iam.gserviceaccount.com","dataproc-accounts.iam.gserviceaccount.com","gae-api-prod.google.com.iam.gserviceaccount.com","genomics-api.google.com.iam.gserviceaccount.com","containerregistry.iam.gserviceaccount.com","container-analysis.iam.gserviceaccount.com","cloudservices.gserviceaccount.com","stackdriver-service.iam.gserviceaccount.com","appspot.gserviceaccount.com","partnercontent.gserviceaccount.com","trifacta-gcloud-prod.iam.gserviceaccount.com","gcf-admin-robot.iam.gserviceaccount.com","compute-system.iam.gserviceaccount.com","gcp-sa-websecurityscanner.iam.gserviceaccount.com","storage-transfer-service.iam.gserviceaccount.com","firebase-sa-management.iam.gserviceaccount.com","firebase-rules.iam.gserviceaccount.com","gcp-sa-cloudbuild.iam.gserviceaccount.com","gcp-sa-automl.iam.gserviceaccount.com","gcp-sa-datalabeling.iam.gserviceaccount.com","gcp-sa-cloudscheduler.iam.gserviceaccount.com"],"GOOGLE_SERVICE_ACCOUNT_KEY_FOR_URL_SIGNING_EXPIRES_IN":2592000,"GOOGLE_SERVICE_ACCOUNT_PREFIX":"","GOOGLE_USER_SERVICE_ACCOUNT_ACCESS_EXPIRES_IN":604800,"GUN_MAIL":{"datacommons.io":{"api_key":"","api_url":"https://api.mailgun.net/v3/mailgun.example.com","default_login":"postmaster@mailgun.example.com","smtp_hostname":"smtp.mailgun.org","smtp_password":""}},"HTTP_PROXY":{"host":null,"port":3128},"INDEXD":null,"INDEXD_PASSWORD":"","INDEXD_USERNAME":"fence","ITRUST_GLOBAL_LOGOUT":"https://auth.nih.gov/siteminderagent/smlogout.asp?mode=nih&AppReturnUrl=","LOGIN_OPTIONS":[{"desc":"description","idp":"google","name":"Login from Google"}],"LOGIN_REDIRECT_WHITELIST":[],"MAX_ACCESS_TOKEN_TTL":3600,"MAX_API_KEY_TTL":2592000,"MAX_PRESIGNED_URL_TTL":3600,"MAX_ROLE_SESSION_INCREASE":false,"MOCK_AUTH":false,"MOCK_GOOGLE_AUTH":false,"MOCK_STORAGE":false,"OAUTH2_JWT_ALG":"RS256","OAUTH2_JWT_ENABLED":true,"OAUTH2_JWT_ISS":"{{BASE_URL}}","OAUTH2_PROVIDER_ERROR_URI":"/api/oauth2/errors","OAUTH2_TOKEN_EXPIRES_IN":{"authorization_code":1200,"implicit":1200},"OPENID_CONNECT":{"cilogon":{"client_id":"","client_secret":"","discovery_url":"https://cilogon.org/.well-known/openid-configuration","mock":false,"mock_default_user":"http://cilogon.org/serverT/users/64703","redirect_url":"{{BASE_URL}}/login/cilogon/login/","scope":"openid email profile"},"cognito":{"client_id":"","client_secret":"","discovery_url":"https://cognito-idp.{REGION}.amazonaws.com/{USER-POOL-ID}/.well-known/openid-configuration","redirect_url":"{{BASE_URL}}/login/cognito/login/","scope":"openid email"},"fence":{"access_token_url":"{{api_base_url}}/oauth2/token","api_base_url":"","authorize_url":"{{api_base_url}}/oauth2/authorize","client_id":"","client_kwargs":{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"},"client_secret":"","mock":false,"mock_default_user":"test@example.com","name":"","refresh_token_url":"{{api_base_url}}/oauth2/token","shibboleth_discovery_url":"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"},"generic_oidc_idp":{"client_id":"","client_secret":"","discovery":{"authorization_endpoint":"","jwks_uri":"","token_endpoint":""},"discovery_url":"https://server.com/.well-known/openid-configuration","email_field":"","name":"some_idp","redirect_url":"{{BASE_URL}}/login/some_idp/login","scope":"","user_id_field":""},"google":{"client_id":"","client_secret":"","discovery_url":"https://accounts.google.com/.well-known/openid-configuration","mock":"","mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/google/login/","scope":"openid email"},"microsoft":{"client_id":"","client_secret":"","discovery_url":"https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/microsoft/login/","scope":"openid email"},"okta":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"{{BASE_URL}}/login/okta/login/","scope":"openid email"},"orcid":{"client_id":"","client_secret":"","discovery_url":"https://orcid.org/.well-known/openid-configuration","mock":false,"mock_default_user":"0000-0002-2601-8132","redirect_url":"{{BASE_URL}}/login/orcid/login/","scope":"openid"},"ras":{"client_id":"","client_secret":"","discovery_url":"https://sts.nih.gov/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/ras/callback","scope":"openid email profile ga4gh_passport_v1"},"shibboleth":{"client_id":"","client_secret":"","redirect_url":"{{BASE_URL}}/login/shib/login"},"synapse":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"","scope":"openid"}},"OVERRIDE_NGINX_RATE_LIMIT":18,"PRIVACY_POLICY_URL":null,"PROBLEM_USER_EMAIL_NOTIFICATION":{"admin":["admin@example.edu"],"content":"The Data Commons Framework utilizes dbGaP for data access authorization. Another member of a Google project you belong to ({}) is attempting to register a service account to the following additional datasets ({}). Please contact dbGaP to request access.\n","domain":"example.com","from":"do-not-reply@example.com","subject":"Account access error notification"},"PUSH_AUDIT_LOGS_CONFIG":{"aws_sqs_config":{"aws_cred":null,"region":null,"sqs_url":null},"type":"aws_sqs"},"RAS_REFRESH_EXPIRATION":1296000,"RAS_USERINFO_ENDPOINT":"/openid/connect/v1.1/userinfo","REFRESH_TOKEN_EXPIRES_IN":2592000,"REGISTERED_USERS_GROUP":"","REGISTER_USERS_ON":false,"REMOVE_SERVICE_ACCOUNT_EMAIL_NOTIFICATION":{"admin":["admin@example.edu"],"content":"Service accounts were removed from access control data because some users or service accounts of GCP Project {} are not authorized to access the data sets associated to the service accounts, or do not adhere to the security policies.\n","domain":"example.com","enable":false,"from":"do-not-reply@example.com","subject":"User service account removal notification"},"RENEW_ACCESS_TOKEN_BEFORE_EXPIRATION":false,"S3_BUCKETS":{},"SEND_FROM":"example@gmail.com","SEND_TO":"example@gmail.com","SERVICE_ACCOUNT_LIMIT":6,"SESSION_ALLOWED_SCOPES":["openid","user","credentials","data","admin","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"SESSION_COOKIE_DOMAIN":null,"SESSION_COOKIE_NAME":"fence","SESSION_COOKIE_SECURE":true,"SESSION_LIFETIME":28800,"SESSION_TIMEOUT":1800,"SHIBBOLETH_HEADER":"persistent_id","SSO_URL":"https://auth.nih.gov/affwebservices/public/saml2sso?SPID={{BASE_URL}}/shibboleth&RelayState=","STORAGE_CREDENTIALS":{},"SUPPORT_EMAIL_FOR_ERRORS":null,"SYNAPSE_AUTHZ_TTL":86400,"SYNAPSE_DISCOVERY_URL":null,"SYNAPSE_JWKS_URI":null,"SYNAPSE_URI":"https://repo-prod.prod.sagebase.org/auth/v1","TOKEN_PROJECTS_CUTOFF":10,"USERSYNC":{"fallback_to_dbgap_sftp":false,"sync_from_visas":false,"visa_types":{"ras":["https://ras.nih.gov/visas/v1","https://ras.nih.gov/visas/v1.1"]}},"USER_ALLOWED_SCOPES":["fence","openid","user","data","admin","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"WHITE_LISTED_GOOGLE_PARENT_ORGS":[],"WHITE_LISTED_SERVICE_ACCOUNT_EMAILS":[],"WTF_CSRF_SECRET_KEY":"{{ENCRYPTION_KEY}}","dbGaP":[{"decrypt_key":"","enable_common_exchange_area_access":false,"info":{"host":"","password":"","port":22,"proxy":"","proxy_user":"","username":""},"parse_consent_code":true,"protocol":"sftp","study_common_exchange_areas":{"example":"test_common_exchange_area"},"study_to_resource_namespaces":{"_default":["/"],"test_common_exchange_area":["/dbgap/"]}}]}` | Configuration settings for Fence app | +| FENCE_CONFIG | map | `{"ACCESS_TOKEN_COOKIE_NAME":"access_token","ACCESS_TOKEN_EXPIRES_IN":1200,"ALLOWED_USER_SERVICE_ACCOUNT_DOMAINS":["developer.gserviceaccount.com","appspot.gserviceaccount.com","iam.gserviceaccount.com"],"ALLOW_GOOGLE_LINKING":true,"APPLICATION_ROOT":"/user","APP_NAME":"Gen3 Data Commons","ARBORIST":"http://arborist-service","ASSUME_ROLE_CACHE_SECONDS":1800,"AUDIT_SERVICE":"http://audit-service","AUTHLIB_INSECURE_TRANSPORT":true,"AWS_CREDENTIALS":{},"AZ_BLOB_CONTAINER_URL":"https://myfakeblob.blob.core.windows.net/my-fake-container/","AZ_BLOB_CREDENTIALS":null,"BILLING_PROJECT_FOR_SA_CREDS":null,"BILLING_PROJECT_FOR_SIGNED_URLS":null,"CIRRUS_CFG":{"GOOGLE_ADMIN_EMAIL":"","GOOGLE_API_KEY":"","GOOGLE_APPLICATION_CREDENTIALS":"","GOOGLE_CLOUD_IDENTITY_ADMIN_EMAIL":"","GOOGLE_IDENTITY_DOMAIN":"","GOOGLE_PROJECT_ID":"","GOOGLE_STORAGE_CREDS":""},"CLIENT_ALLOWED_SCOPES":["openid","user","data","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"DATA_UPLOAD_BUCKET":"bucket1","DBGAP_ACCESSION_WITH_CONSENT_REGEX":"(?Pphs[0-9]+)(.(?Pv[0-9]+)){0,1}(.(?Pp[0-9]+)){0,1}.(?Pc[0-9]+)","DEBUG":false,"DEFAULT_LOGIN_IDP":"google","DEFAULT_LOGIN_URL":"{{BASE_URL}}/login/google","DEV_LOGIN_COOKIE_NAME":"dev_login","DREAM_CHALLENGE_GROUP":"DREAM","DREAM_CHALLENGE_TEAM":"DREAM","EMAIL_SERVER":"localhost","ENABLED_IDENTITY_PROVIDERS":{},"ENABLE_AUDIT_LOGS":{"login":false,"presigned_url":false},"ENABLE_AUTOMATIC_BILLING_PERMISSION_SA_CREDS":false,"ENABLE_AUTOMATIC_BILLING_PERMISSION_SIGNED_URLS":false,"ENABLE_CSRF_PROTECTION":true,"ENABLE_DB_MIGRATION":true,"ENABLE_PROMETHEUS_METRICS":false,"ENCRYPTION_KEY":"REPLACEME","GA4GH_VISA_ISSUER_ALLOWLIST":["{{BASE_URL}}","https://sts.nih.gov","https://stsstg.nih.gov"],"GEN3_PASSPORT_EXPIRES_IN":43200,"GLOBAL_PARSE_VISAS_ON_LOGIN":false,"GOOGLE_ACCOUNT_ACCESS_EXPIRES_IN":86400,"GOOGLE_BULK_UPDATES":false,"GOOGLE_GROUP_PREFIX":"","GOOGLE_MANAGED_SERVICE_ACCOUNT_DOMAINS":["dataflow-service-producer-prod.iam.gserviceaccount.com","cloudbuild.gserviceaccount.com","cloud-ml.google.com.iam.gserviceaccount.com","container-engine-robot.iam.gserviceaccount.com","dataflow-service-producer-prod.iam.gserviceaccount.com","sourcerepo-service-accounts.iam.gserviceaccount.com","dataproc-accounts.iam.gserviceaccount.com","gae-api-prod.google.com.iam.gserviceaccount.com","genomics-api.google.com.iam.gserviceaccount.com","containerregistry.iam.gserviceaccount.com","container-analysis.iam.gserviceaccount.com","cloudservices.gserviceaccount.com","stackdriver-service.iam.gserviceaccount.com","appspot.gserviceaccount.com","partnercontent.gserviceaccount.com","trifacta-gcloud-prod.iam.gserviceaccount.com","gcf-admin-robot.iam.gserviceaccount.com","compute-system.iam.gserviceaccount.com","gcp-sa-websecurityscanner.iam.gserviceaccount.com","storage-transfer-service.iam.gserviceaccount.com","firebase-sa-management.iam.gserviceaccount.com","firebase-rules.iam.gserviceaccount.com","gcp-sa-cloudbuild.iam.gserviceaccount.com","gcp-sa-automl.iam.gserviceaccount.com","gcp-sa-datalabeling.iam.gserviceaccount.com","gcp-sa-cloudscheduler.iam.gserviceaccount.com"],"GOOGLE_SERVICE_ACCOUNT_KEY_FOR_URL_SIGNING_EXPIRES_IN":2592000,"GOOGLE_SERVICE_ACCOUNT_PREFIX":"","GOOGLE_USER_SERVICE_ACCOUNT_ACCESS_EXPIRES_IN":604800,"GUN_MAIL":{"datacommons.io":{"api_key":"","api_url":"https://api.mailgun.net/v3/mailgun.example.com","default_login":"postmaster@mailgun.example.com","smtp_hostname":"smtp.mailgun.org","smtp_password":""}},"HTTP_PROXY":{"host":null,"port":3128},"INDEXD":"http://indexd-service","INDEXD_PASSWORD":"","INDEXD_USERNAME":"fence","ITRUST_GLOBAL_LOGOUT":"https://auth.nih.gov/siteminderagent/smlogout.asp?mode=nih&AppReturnUrl=","LOGIN_OPTIONS":[{"desc":"description","idp":"google","name":"Login from Google"}],"LOGIN_REDIRECT_WHITELIST":[],"MAX_ACCESS_TOKEN_TTL":3600,"MAX_API_KEY_TTL":2592000,"MAX_PRESIGNED_URL_TTL":3600,"MAX_ROLE_SESSION_INCREASE":false,"MOCK_AUTH":false,"MOCK_GOOGLE_AUTH":false,"MOCK_STORAGE":false,"OAUTH2_JWT_ALG":"RS256","OAUTH2_JWT_ENABLED":true,"OAUTH2_JWT_ISS":"{{BASE_URL}}","OAUTH2_PROVIDER_ERROR_URI":"/api/oauth2/errors","OAUTH2_TOKEN_EXPIRES_IN":{"authorization_code":1200,"implicit":1200},"OPENID_CONNECT":{"cilogon":{"client_id":"","client_secret":"","discovery_url":"https://cilogon.org/.well-known/openid-configuration","mock":false,"mock_default_user":"http://cilogon.org/serverT/users/64703","redirect_url":"{{BASE_URL}}/login/cilogon/login/","scope":"openid email profile"},"cognito":{"client_id":"","client_secret":"","discovery_url":"https://cognito-idp.{REGION}.amazonaws.com/{USER-POOL-ID}/.well-known/openid-configuration","redirect_url":"{{BASE_URL}}/login/cognito/login/","scope":"openid email"},"fence":{"access_token_url":"{{api_base_url}}/oauth2/token","api_base_url":"","authorize_url":"{{api_base_url}}/oauth2/authorize","client_id":"","client_kwargs":{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"},"client_secret":"","mock":false,"mock_default_user":"test@example.com","name":"","refresh_token_url":"{{api_base_url}}/oauth2/token","shibboleth_discovery_url":"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"},"generic_oidc_idp":{"client_id":"","client_secret":"","discovery":{"authorization_endpoint":"","jwks_uri":"","token_endpoint":""},"discovery_url":"https://server.com/.well-known/openid-configuration","email_field":"","name":"some_idp","redirect_url":"{{BASE_URL}}/login/some_idp/login","scope":"","user_id_field":""},"google":{"client_id":"","client_secret":"","discovery_url":"https://accounts.google.com/.well-known/openid-configuration","mock":"","mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/google/login/","scope":"openid email"},"microsoft":{"client_id":"","client_secret":"","discovery_url":"https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/microsoft/login/","scope":"openid email"},"okta":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"{{BASE_URL}}/login/okta/login/","scope":"openid email"},"orcid":{"client_id":"","client_secret":"","discovery_url":"https://orcid.org/.well-known/openid-configuration","mock":false,"mock_default_user":"0000-0002-2601-8132","redirect_url":"{{BASE_URL}}/login/orcid/login/","scope":"openid"},"ras":{"client_id":"","client_secret":"","discovery_url":"https://sts.nih.gov/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/ras/callback","scope":"openid email profile ga4gh_passport_v1"},"shibboleth":{"client_id":"","client_secret":"","redirect_url":"{{BASE_URL}}/login/shib/login"},"synapse":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"","scope":"openid"}},"OVERRIDE_NGINX_RATE_LIMIT":18,"PRIVACY_POLICY_URL":null,"PROBLEM_USER_EMAIL_NOTIFICATION":{"admin":["admin@example.edu"],"content":"The Data Commons Framework utilizes dbGaP for data access authorization. Another member of a Google project you belong to ({}) is attempting to register a service account to the following additional datasets ({}). Please contact dbGaP to request access.\n","domain":"example.com","from":"do-not-reply@example.com","subject":"Account access error notification"},"PUSH_AUDIT_LOGS_CONFIG":{"aws_sqs_config":{"aws_cred":null,"region":null,"sqs_url":null},"type":"aws_sqs"},"RAS_REFRESH_EXPIRATION":1296000,"RAS_USERINFO_ENDPOINT":"/openid/connect/v1.1/userinfo","REFRESH_TOKEN_EXPIRES_IN":2592000,"REGISTERED_USERS_GROUP":"","REGISTER_USERS_ON":false,"REMOVE_SERVICE_ACCOUNT_EMAIL_NOTIFICATION":{"admin":["admin@example.edu"],"content":"Service accounts were removed from access control data because some users or service accounts of GCP Project {} are not authorized to access the data sets associated to the service accounts, or do not adhere to the security policies.\n","domain":"example.com","enable":false,"from":"do-not-reply@example.com","subject":"User service account removal notification"},"RENEW_ACCESS_TOKEN_BEFORE_EXPIRATION":false,"S3_BUCKETS":{},"SEND_FROM":"example@gmail.com","SEND_TO":"example@gmail.com","SERVICE_ACCOUNT_LIMIT":6,"SESSION_ALLOWED_SCOPES":["openid","user","credentials","data","admin","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"SESSION_COOKIE_DOMAIN":null,"SESSION_COOKIE_NAME":"fence","SESSION_COOKIE_SECURE":true,"SESSION_LIFETIME":28800,"SESSION_TIMEOUT":1800,"SHIBBOLETH_HEADER":"persistent_id","SSO_URL":"https://auth.nih.gov/affwebservices/public/saml2sso?SPID={{BASE_URL}}/shibboleth&RelayState=","STORAGE_CREDENTIALS":{},"SUPPORT_EMAIL_FOR_ERRORS":null,"SYNAPSE_AUTHZ_TTL":86400,"SYNAPSE_DISCOVERY_URL":null,"SYNAPSE_JWKS_URI":null,"SYNAPSE_URI":"https://repo-prod.prod.sagebase.org/auth/v1","TOKEN_PROJECTS_CUTOFF":10,"USERSYNC":{"fallback_to_dbgap_sftp":false,"sync_from_visas":false,"visa_types":{"ras":["https://ras.nih.gov/visas/v1","https://ras.nih.gov/visas/v1.1"]}},"USER_ALLOWED_SCOPES":["fence","openid","user","data","admin","google_credentials","google_service_account","google_link","ga4gh_passport_v1"],"WHITE_LISTED_GOOGLE_PARENT_ORGS":[],"WHITE_LISTED_SERVICE_ACCOUNT_EMAILS":[],"WTF_CSRF_SECRET_KEY":"{{ENCRYPTION_KEY}}","dbGaP":[{"decrypt_key":"","enable_common_exchange_area_access":false,"info":{"host":"","password":"","port":22,"proxy":"","username":""},"parse_consent_code":true,"protocol":"sftp","study_common_exchange_areas":{"example":"test_common_exchange_area"},"study_to_resource_namespaces":{"_default":["/"],"test_common_exchange_area":["/dbgap/"]}}]}` | Configuration settings for Fence app | | FENCE_CONFIG.APP_NAME | string | `"Gen3 Data Commons"` | Name of the Fence app | | FENCE_CONFIG.AUTHLIB_INSECURE_TRANSPORT | bool | `true` | allow OIDC traffic on http for development. By default it requires https. WARNING: ONLY set to true when fence will be deployed in such a way that it will ONLY receive traffic from internal clients and can safely use HTTP. | | FENCE_CONFIG.CLIENT_ALLOWED_SCOPES | list | `["openid","user","data","google_credentials","google_service_account","google_link","ga4gh_passport_v1"]` | These are the *possible* scopes a client can be given, NOT scopes that are given to all clients. You can be more restrictive during client creation | @@ -32,20 +32,20 @@ A Helm chart for gen3 Fence | FENCE_CONFIG.MOCK_GOOGLE_AUTH | bool | `false` | if true, will fake a successful login response from Google in /login/google NOTE: this will also modify the behavior of /link/google endpoints WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) will login as the username set in cookie DEV_LOGIN_COOKIE_NAME | | FENCE_CONFIG.MOCK_STORAGE | bool | `false` | if true, will ignore anything configured in STORAGE_CREDENTIALS | | FENCE_CONFIG.OPENID_CONNECT | dict | `{"cilogon":{"client_id":"","client_secret":"","discovery_url":"https://cilogon.org/.well-known/openid-configuration","mock":false,"mock_default_user":"http://cilogon.org/serverT/users/64703","redirect_url":"{{BASE_URL}}/login/cilogon/login/","scope":"openid email profile"},"cognito":{"client_id":"","client_secret":"","discovery_url":"https://cognito-idp.{REGION}.amazonaws.com/{USER-POOL-ID}/.well-known/openid-configuration","redirect_url":"{{BASE_URL}}/login/cognito/login/","scope":"openid email"},"fence":{"access_token_url":"{{api_base_url}}/oauth2/token","api_base_url":"","authorize_url":"{{api_base_url}}/oauth2/authorize","client_id":"","client_kwargs":{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"},"client_secret":"","mock":false,"mock_default_user":"test@example.com","name":"","refresh_token_url":"{{api_base_url}}/oauth2/token","shibboleth_discovery_url":"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"},"generic_oidc_idp":{"client_id":"","client_secret":"","discovery":{"authorization_endpoint":"","jwks_uri":"","token_endpoint":""},"discovery_url":"https://server.com/.well-known/openid-configuration","email_field":"","name":"some_idp","redirect_url":"{{BASE_URL}}/login/some_idp/login","scope":"","user_id_field":""},"google":{"client_id":"","client_secret":"","discovery_url":"https://accounts.google.com/.well-known/openid-configuration","mock":"","mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/google/login/","scope":"openid email"},"microsoft":{"client_id":"","client_secret":"","discovery_url":"https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/microsoft/login/","scope":"openid email"},"okta":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"{{BASE_URL}}/login/okta/login/","scope":"openid email"},"orcid":{"client_id":"","client_secret":"","discovery_url":"https://orcid.org/.well-known/openid-configuration","mock":false,"mock_default_user":"0000-0002-2601-8132","redirect_url":"{{BASE_URL}}/login/orcid/login/","scope":"openid"},"ras":{"client_id":"","client_secret":"","discovery_url":"https://sts.nih.gov/.well-known/openid-configuration","mock":false,"mock_default_user":"test@example.com","redirect_url":"{{BASE_URL}}/login/ras/callback","scope":"openid email profile ga4gh_passport_v1"},"shibboleth":{"client_id":"","client_secret":"","redirect_url":"{{BASE_URL}}/login/shib/login"},"synapse":{"client_id":"","client_secret":"","discovery_url":"","redirect_url":"","scope":"openid"}}` | Configurations for OpenID Connect (OIDC) authentication - Fully configure at least one client so login works - WARNING: Be careful changing the *_ALLOWED_SCOPES as you can break basic and optional functionality | -| FENCE_CONFIG.OPENID_CONNECT.fence | object | `{"access_token_url":"{{api_base_url}}/oauth2/token","api_base_url":"","authorize_url":"{{api_base_url}}/oauth2/authorize","client_id":"","client_kwargs":{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"},"client_secret":"","mock":false,"mock_default_user":"test@example.com","name":"","refresh_token_url":"{{api_base_url}}/oauth2/token","shibboleth_discovery_url":"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"}` | dict: Contains multi-tenant Fence configuration Support for multi-tenant fence (another fence is this fence's IDP) If this fence instance is a client of another fence, fill this cfg out. REMOVE if not needed | -| FENCE_CONFIG.OPENID_CONNECT.fence.access_token_url | string | `"{{api_base_url}}/oauth2/token"` | str: URL for access token endpoint of the other fence | -| FENCE_CONFIG.OPENID_CONNECT.fence.api_base_url | string | `""` | str: Root URL for the other fence this api_base_url should be the root url for the OTHER fence something like: https://example.com | -| FENCE_CONFIG.OPENID_CONNECT.fence.authorize_url | string | `"{{api_base_url}}/oauth2/authorize"` | str: URL for authorization endpoint of the other fence The next 3 should not need to be changed if the provider is following Oauth2 endpoint naming conventions | -| FENCE_CONFIG.OPENID_CONNECT.fence.client_id | string | `""` | str: ID of the client of this fence on the other fence this client_id and client_secret should be obtained by registering THIS fence as a new client of the OTHER fence | +| FENCE_CONFIG.OPENID_CONNECT.fence | dict | `{"access_token_url":"{{api_base_url}}/oauth2/token","api_base_url":"","authorize_url":"{{api_base_url}}/oauth2/authorize","client_id":"","client_kwargs":{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"},"client_secret":"","mock":false,"mock_default_user":"test@example.com","name":"","refresh_token_url":"{{api_base_url}}/oauth2/token","shibboleth_discovery_url":"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"}` | : Contains multi-tenant Fence configuration Support for multi-tenant fence (another fence is this fence's IDP) If this fence instance is a client of another fence, fill this cfg out. REMOVE if not needed | +| FENCE_CONFIG.OPENID_CONNECT.fence.access_token_url | str | `"{{api_base_url}}/oauth2/token"` | : URL for access token endpoint of the other fence | +| FENCE_CONFIG.OPENID_CONNECT.fence.api_base_url | str | `""` | : Root URL for the other fence this api_base_url should be the root url for the OTHER fence something like: https://example.com | +| FENCE_CONFIG.OPENID_CONNECT.fence.authorize_url | str | `"{{api_base_url}}/oauth2/authorize"` | : URL for authorization endpoint of the other fence The next 3 should not need to be changed if the provider is following Oauth2 endpoint naming conventions | +| FENCE_CONFIG.OPENID_CONNECT.fence.client_id | str | `""` | : ID of the client of this fence on the other fence this client_id and client_secret should be obtained by registering THIS fence as a new client of the OTHER fence | | FENCE_CONFIG.OPENID_CONNECT.fence.client_kwargs | object | `{"redirect_uri":"{{BASE_URL}}/login/fence/login","scope":"openid"}` | dict: Additional client parameters | -| FENCE_CONFIG.OPENID_CONNECT.fence.client_kwargs.redirect_uri | string | `"{{BASE_URL}}/login/fence/login"` | str: The URL to which the other fence will redirect after logging in | -| FENCE_CONFIG.OPENID_CONNECT.fence.client_kwargs.scope | string | `"openid"` | str: Space-separated string of scopes openid is required to use OIDC flow | -| FENCE_CONFIG.OPENID_CONNECT.fence.client_secret | string | `""` | str: Secret of the client of this fence on the other fence | -| FENCE_CONFIG.OPENID_CONNECT.fence.mock | bool | `false` | bool: Whether to mock a successful login response for testing purposes if mock is true, will fake a successful login response for login WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) | -| FENCE_CONFIG.OPENID_CONNECT.fence.mock_default_user | string | `"test@example.com"` | str: Default user for mock login | -| FENCE_CONFIG.OPENID_CONNECT.fence.name | string | `""` | str: Name of the provider for consent screens Custom name to display for consent screens. If not provided, will use `fence`. If the other fence is using NIH Login, you should make name: `NIH Login` | -| FENCE_CONFIG.OPENID_CONNECT.fence.refresh_token_url | string | `"{{api_base_url}}/oauth2/token"` | str: URL for refresh token endpoint of the other fence | -| FENCE_CONFIG.OPENID_CONNECT.fence.shibboleth_discovery_url | string | `"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"` | str: URL of the shibboleth discovery endpoint if needed for InCommon login this is needed to enable InCommon login, if some LOGIN_OPTIONS are configured with idp=fence and a list of shib_idps: | +| FENCE_CONFIG.OPENID_CONNECT.fence.client_kwargs.redirect_uri | str | `"{{BASE_URL}}/login/fence/login"` | : The URL to which the other fence will redirect after logging in | +| FENCE_CONFIG.OPENID_CONNECT.fence.client_kwargs.scope | str | `"openid"` | : Space-separated string of scopes openid is required to use OIDC flow | +| FENCE_CONFIG.OPENID_CONNECT.fence.client_secret | str | `""` | : Secret of the client of this fence on the other fence | +| FENCE_CONFIG.OPENID_CONNECT.fence.mock | bool | `false` | : Whether to mock a successful login response for testing purposes if mock is true, will fake a successful login response for login WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) | +| FENCE_CONFIG.OPENID_CONNECT.fence.mock_default_user | str | `"test@example.com"` | : Default user for mock login | +| FENCE_CONFIG.OPENID_CONNECT.fence.name | str | `""` | : Name of the provider for consent screens Custom name to display for consent screens. If not provided, will use `fence`. If the other fence is using NIH Login, you should make name: `NIH Login` | +| FENCE_CONFIG.OPENID_CONNECT.fence.refresh_token_url | str | `"{{api_base_url}}/oauth2/token"` | : URL for refresh token endpoint of the other fence | +| FENCE_CONFIG.OPENID_CONNECT.fence.shibboleth_discovery_url | str | `"https://login.bionimbus.org/Shibboleth.sso/DiscoFeed"` | : URL of the shibboleth discovery endpoint if needed for InCommon login this is needed to enable InCommon login, if some LOGIN_OPTIONS are configured with idp=fence and a list of shib_idps: | | FENCE_CONFIG.OPENID_CONNECT.generic_oidc_idp.client_id | str | `""` | Client ID | | FENCE_CONFIG.OPENID_CONNECT.generic_oidc_idp.client_secret | str | `""` | Client secret | | FENCE_CONFIG.OPENID_CONNECT.generic_oidc_idp.discovery.authorization_endpoint | str | `""` | Authorization endpoint URL | @@ -70,73 +70,27 @@ A Helm chart for gen3 Fence | FENCE_CONFIG.USER_ALLOWED_SCOPES | list | `["fence","openid","user","data","admin","google_credentials","google_service_account","google_link","ga4gh_passport_v1"]` | these are the scopes that CAN be included in a user's own access_token | | FENCE_CONFIG.WTF_CSRF_SECRET_KEY | str | `"{{ENCRYPTION_KEY}}"` | signing key for WTForms to sign CSRF tokens with | | USER_YAML | string | `"cloud_providers: {}\ngroups: {}\nauthz:\n # policies automatically given to anyone, even if they haven't authenticated\n anonymous_policies: ['open_data_reader', 'full_open_access']\n\n # policies automatically given to authenticated users (in addition to their other\n # policies)\n all_users_policies: ['open_data_reader', 'authn_open_access']\n\n user_project_to_resource:\n QA: /programs/QA\n DEV: /programs/DEV\n test: /programs/QA/projects/test\n jenkins: /programs/jnkns/projects/jenkins\n jenkins2: /programs/jnkns/projects/jenkins2\n jnkns: /programs/jnkns\n\n policies:\n # General Access\n - id: 'workspace'\n description: 'be able to use workspace'\n resource_paths: ['/workspace']\n role_ids: ['workspace_user']\n - id: 'dashboard'\n description: 'be able to use the commons dashboard'\n resource_paths: ['/dashboard']\n role_ids: ['dashboard_user']\n - id: 'prometheus'\n description: 'be able to use prometheus'\n resource_paths: ['/prometheus']\n role_ids: ['prometheus_user']\n - id: 'ttyadmin'\n description: 'be able to use the admin tty'\n resource_paths: ['/ttyadmin']\n role_ids: ['ttyadmin_user']\n - id: 'mds_admin'\n description: 'be able to use metadata service'\n resource_paths: ['/mds_gateway']\n role_ids: ['mds_user']\n - id: 'data_upload'\n description: 'upload raw data files to S3'\n role_ids: ['file_uploader']\n resource_paths: ['/data_file']\n - description: be able to use sower job\n id: sower\n resource_paths: [/sower]\n role_ids: [sower_user]\n - id: 'mariner_admin'\n description: 'full access to mariner API'\n resource_paths: ['/mariner']\n role_ids: ['mariner_admin']\n - id: audit_reader\n role_ids:\n - audit_reader\n resource_paths:\n - /services/audit\n - id: audit_login_reader\n role_ids:\n - audit_reader\n resource_paths:\n - /services/audit/login\n - id: audit_presigned_url_reader\n role_ids:\n - audit_reader\n resource_paths:\n - /services/audit/presigned_url\n - id: requestor_admin\n role_ids:\n - requestor_admin\n resource_paths:\n - /programs\n - id: requestor_reader\n role_ids:\n - requestor_reader\n resource_paths:\n - /programs\n - id: requestor_creator\n role_ids:\n - requestor_creator\n resource_paths:\n - /programs\n - id: requestor_updater\n role_ids:\n - requestor_updater\n resource_paths:\n - /programs\n - id: requestor_deleter\n role_ids:\n - requestor_deleter\n resource_paths:\n - /programs\n # Data Access\n\n # All programs policy\n - id: 'all_programs_reader'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths: ['/programs']\n\n # # example if need access to write to storage\n # - id: 'programs.jnkns-storage_writer'\n # description: ''\n # role_ids:\n # - 'storage_writer'\n # resource_paths: ['/programs/jnkns']\n\n - id: 'programs.jnkns-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/programs/jnkns'\n - '/gen3/programs/jnkns'\n\n - id: 'programs.jnkns-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/programs/jnkns'\n - '/gen3/programs/jnkns'\n\n\n - id: 'programs.QA-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/programs/QA'\n - '/gen3/programs/QA'\n\n - id: 'programs.QA-admin-no-storage'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n resource_paths:\n - '/programs/QA'\n - '/gen3/programs/QA'\n\n - id: 'programs.QA-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/programs/QA'\n - '/gen3/programs/QA'\n\n - id: 'programs.DEV-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n - 'storage_writer'\n resource_paths:\n - '/programs/DEV'\n - '/gen3/programs/DEV'\n\n - id: 'programs.DEV-storage_writer'\n description: ''\n role_ids:\n - 'storage_writer'\n resource_paths: ['/programs/DEV']\n\n - id: 'programs.DEV-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/programs/DEV'\n - '/gen3/programs/DEV'\n\n - id: 'programs.test-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/programs/test'\n - '/gen3/programs/test'\n\n - id: 'programs.test-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/programs/test'\n - '/gen3/programs/test'\n\n - id: 'abc-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/abc'\n\n - id: 'gen3-admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/gen3'\n\n - id: 'gen3-hmb-researcher'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_reader'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n - '/consents/HMB'\n - '/gen3'\n\n - id: 'abc.programs.test_program.projects.test_project1-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/abc/programs/test_program/projects/test_project1'\n\n - id: 'abc.programs.test_program.projects.test_project2-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/abc/programs/test_program/projects/test_project2'\n\n - id: 'abc.programs.test_program2.projects.test_project3-viewer'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths:\n - '/abc/programs/test_program2/projects/test_project3'\n\n # Open data policies\n - id: 'authn_open_access'\n resource_paths: ['/programs/open/projects/authnRequired']\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n - id: 'full_open_access'\n resource_paths: ['/programs/open/projects/1000G']\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n - id: 'open_data_reader'\n description: ''\n role_ids:\n - 'reader'\n - 'storage_reader'\n resource_paths: ['/open']\n - id: 'open_data_admin'\n description: ''\n role_ids:\n - 'creator'\n - 'reader'\n - 'updater'\n - 'deleter'\n - 'storage_writer'\n - 'storage_reader'\n resource_paths: ['/open']\n\n # Consent Code Policies\n - id: 'not-for-profit-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NPU'\n\n - id: 'publication-required-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/PUB'\n\n - id: 'gru-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n\n - id: 'gru-cc-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n\n - id: 'hmb-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n - '/consents/HMB'\n\n - id: 'poa-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n - '/consents/POA'\n\n - id: 'ds-lung-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n - '/consents/HMB'\n - '/consents/DS_LungDisease'\n\n - id: 'ds-chronic-obstructive-pulmonary-disease-researcher'\n description: ''\n role_ids:\n - 'admin'\n resource_paths:\n - '/consents/NRES'\n - '/consents/GRU'\n - '/consents/GRU_CC'\n - '/consents/HMB'\n - '/consents/DS_ChronicObstructivePulmonaryDisease'\n\n - id: 'services.sheepdog-admin'\n description: 'CRUD access to programs and projects'\n role_ids:\n - 'sheepdog_admin'\n resource_paths:\n - '/services/sheepdog/submission/program'\n - '/services/sheepdog/submission/project'\n\n # indexd\n - id: 'indexd_admin'\n description: 'full access to indexd API'\n role_ids:\n - 'indexd_admin'\n resource_paths:\n - '/programs'\n - '/services/indexd/admin'\n # # TODO resource path '/' is not valid right now in arborist, trying to decide\n # # how to handle all resources\n # - id: 'indexd_admin'\n # description: ''\n # role_ids:\n # - 'indexd_record_creator'\n # - 'indexd_record_reader'\n # - 'indexd_record_updater'\n # - 'indexd_delete_record'\n # - 'indexd_storage_reader'\n # - 'indexd_storage_writer'\n # resource_paths: ['/']\n # - id: 'indexd_record_reader'\n # description: ''\n # role_ids:\n # - 'indexd_record_reader'\n # resource_paths: ['/']\n # - id: 'indexd_record_editor'\n # description: ''\n # role_ids:\n # - 'indexd_record_creator'\n # - 'indexd_record_reader'\n # - 'indexd_record_updater'\n # - 'indexd_delete_record'\n # resource_paths: ['/']\n # - id: 'indexd_storage_reader'\n # description: ''\n # role_ids:\n # - 'indexd_storage_reader'\n # resource_paths: ['/']\n # - id: 'indexd_storage_editor'\n # description: ''\n # role_ids:\n # - 'indexd_storage_reader'\n # - 'indexd_storage_writer'\n # resource_paths: ['/']\n\n # argo\n - id: argo\n description: be able to use argo\n resource_paths: [/argo]\n role_ids: [argo_user]\n\n resources:\n # General Access\n - name: 'data_file'\n description: 'data files, stored in S3'\n - name: 'dashboard'\n description: 'commons /dashboard'\n - name: 'mds_gateway'\n description: 'commons /mds-admin'\n - name: 'prometheus'\n description: 'commons /prometheus and /grafana'\n - name: 'ttyadmin'\n description: 'commons /ttyadmin'\n - name: 'workspace'\n - name: \"sower\"\n - name: 'mariner'\n description: 'workflow execution service'\n - name: argo\n\n # OLD Data\n - name: 'programs'\n subresources:\n - name: 'open'\n subresources:\n - name: 'projects'\n subresources:\n - name: '1000G'\n - name: 'authnRequired'\n - name: 'QA'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n - name: 'DEV'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n - name: 'jnkns'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'jenkins'\n - name: 'jenkins2'\n - name: 'test'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n\n # NEW Data WITH PREFIX\n - name: 'gen3'\n subresources:\n - name: 'programs'\n subresources:\n - name: 'QA'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n - name: 'DEV'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n - name: 'jnkns'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'jenkins'\n - name: 'jenkins2'\n - name: 'test'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test'\n\n # consents obtained from DUO and NIH\n # https://github.com/EBISPOT/DUO\n # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4721915/\n - name: 'consents'\n subresources:\n - name: 'NRES'\n description: 'no restriction'\n - name: 'GRU'\n description: 'general research use'\n - name: 'GRU_CC'\n description: 'general research use and clinical care'\n - name: 'HMB'\n description: 'health/medical/biomedical research'\n - name: 'POA'\n description: 'population origins or ancestry research'\n - name: 'NMDS'\n description: 'no general methods research'\n - name: 'NPU'\n description: 'not-for-profit use only'\n - name: 'PUB'\n description: 'publication required'\n - name: 'DS_LungDisease'\n description: 'disease-specific research for lung disease'\n - name: 'DS_ChronicObstructivePulmonaryDisease'\n description: 'disease-specific research for chronic obstructive pulmonary disease'\n\n - name: 'abc'\n subresources:\n - name: 'programs'\n subresources:\n - name: 'foo'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'bar'\n - name: 'test_program'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test_project1'\n - name: 'test_project2'\n - name: 'test_program2'\n subresources:\n - name: 'projects'\n subresources:\n - name: 'test_project3'\n\n\n # \"Sheepdog admin\" resources\n - name: 'services'\n subresources:\n - name: 'sheepdog'\n subresources:\n - name: 'submission'\n subresources:\n - name: 'program'\n - name: 'project'\n - name: 'indexd'\n subresources:\n - name: 'admin'\n - name: 'bundles'\n - name: audit\n subresources:\n - name: presigned_url\n - name: login\n\n\n - name: 'open'\n\n # action/methods:\n # create, read, update, delete, read-storage, write-storage,\n # file_upload, access\n roles:\n # General Access\n - id: 'file_uploader'\n description: 'can upload data files'\n permissions:\n - id: 'file_upload'\n action:\n service: '*'\n method: 'file_upload'\n - id: 'workspace_user'\n permissions:\n - id: 'workspace_access'\n action:\n service: 'jupyterhub'\n method: 'access'\n - id: 'dashboard_user'\n permissions:\n - id: 'dashboard_access'\n action:\n service: 'dashboard'\n method: 'access'\n - id: 'mds_user'\n permissions:\n - id: 'mds_access'\n action:\n service: 'mds_gateway'\n method: 'access'\n - id: 'prometheus_user'\n permissions:\n - id: 'prometheus_access'\n action:\n service: 'prometheus'\n method: 'access'\n - id: 'ttyadmin_user'\n permissions:\n - id: 'ttyadmin_access'\n action:\n service: 'ttyadmin'\n method: 'access'\n - id: 'sower_user'\n permissions:\n - id: 'sower_access'\n action:\n service: 'job'\n method: 'access'\n - id: 'mariner_admin'\n permissions:\n - id: 'mariner_access'\n action:\n service: 'mariner'\n method: 'access'\n - id: audit_reader\n permissions:\n - id: audit_reader_action\n action:\n service: audit\n method: read\n\n # All services\n - id: 'admin'\n description: ''\n permissions:\n - id: 'admin'\n action:\n service: '*'\n method: '*'\n - id: 'creator'\n description: ''\n permissions:\n - id: 'creator'\n action:\n service: '*'\n method: 'create'\n - id: 'reader'\n description: ''\n permissions:\n - id: 'reader'\n action:\n service: '*'\n method: 'read'\n - id: 'updater'\n description: ''\n permissions:\n - id: 'updater'\n action:\n service: '*'\n method: 'update'\n - id: 'deleter'\n description: ''\n permissions:\n - id: 'deleter'\n action:\n service: '*'\n method: 'delete'\n - id: 'storage_writer'\n description: ''\n permissions:\n - id: 'storage_writer'\n action:\n service: '*'\n method: 'write-storage'\n - id: 'storage_reader'\n description: ''\n permissions:\n - id: 'storage_reader'\n action:\n service: '*'\n method: 'read-storage'\n\n\n # Sheepdog admin role\n - id: 'sheepdog_admin'\n description: 'sheepdog admin role for program project crud'\n permissions:\n - id: 'sheepdog_admin_action'\n action:\n service: 'sheepdog'\n method: '*'\n\n\n # indexd\n - id: 'indexd_admin'\n # this only works if indexd.arborist is enabled in manifest!\n description: 'full access to indexd API'\n permissions:\n - id: 'indexd_admin'\n action:\n service: 'indexd'\n method: '*'\n - id: 'indexd_record_creator'\n description: ''\n permissions:\n - id: 'indexd_record_creator'\n action:\n service: 'indexd'\n method: 'create'\n - id: 'indexd_record_reader'\n description: ''\n permissions:\n - id: 'indexd_record_reader'\n action:\n service: 'indexd'\n method: 'read'\n - id: 'indexd_record_updater'\n description: ''\n permissions:\n - id: 'indexd_record_updater'\n action:\n service: 'indexd'\n method: 'update'\n - id: 'indexd_delete_record'\n description: ''\n permissions:\n - id: 'indexd_delete_record'\n action:\n service: 'indexd'\n method: 'delete'\n - id: 'indexd_storage_reader'\n description: ''\n permissions:\n - id: 'indexd_storage_reader'\n action:\n service: 'indexd'\n method: 'read-storage'\n - id: 'indexd_storage_writer'\n description: ''\n permissions:\n - id: 'indexd_storage_writer'\n action:\n service: 'indexd'\n method: 'write-storage'\n\n # arborist\n - id: 'arborist_creator'\n description: ''\n permissions:\n - id: 'arborist_creator'\n action:\n service: 'arborist'\n method: 'create'\n - id: 'arborist_reader'\n description: ''\n permissions:\n - id: 'arborist_reader'\n action:\n service: 'arborist'\n method: 'read'\n - id: 'arborist_updater'\n description: ''\n permissions:\n - id: 'arborist_updater'\n action:\n service: 'arborist'\n method: 'update'\n - id: 'arborist_deleter'\n description: ''\n permissions:\n - id: 'arborist_deleter'\n action:\n service: 'arborist'\n method: 'delete'\n\n # requestor\n - id: requestor_admin\n permissions:\n - id: requestor_admin_action\n action:\n service: requestor\n method: '*'\n - id: requestor_reader\n permissions:\n - id: requestor_reader_action\n action:\n service: requestor\n method: read\n - id: requestor_creator\n permissions:\n - id: requestor_creator_action\n action:\n service: requestor\n method: create\n - id: requestor_updater\n permissions:\n - id: requestor_updater_action\n action:\n service: requestor\n method: update\n - id: requestor_deleter\n permissions:\n - id: requestor_deleter_action\n action:\n service: requestor\n method: delete\n # argo\n - id: argo_user\n permissions:\n - id: argo_access\n action:\n service: argo\n method: access\n\nclients:\n basic-test-client:\n policies:\n - abc-admin\n - gen3-admin\n basic-test-abc-client:\n policies:\n - abc-admin\n wts:\n policies:\n - all_programs_reader\n - workspace\n\nusers:\n ### BEGIN INTERNS SECTION ###\n ### END INTERNS SECTION ###\n qureshi@uchicago.edu:\n admin: true\n policies:\n - data_upload\n - workspace\n - dashboard\n - mds_admin\n - prometheus\n - sower\n - services.sheepdog-admin\n - programs.QA-admin\n - programs.test-admin\n - programs.DEV-admin\n - programs.jnkns-admin\n - indexd_admin\n - ttyadmin\n projects:\n - auth_id: QA\n privilege: [create, read, update, delete, upload, read-storage]\n - auth_id: test\n privilege: [create, read, update, delete, upload, read-storage]\n - auth_id: DEV\n privilege: [create, read, update, delete, upload, read-storage]\n - auth_id: jenkins\n privilege: [create, read, update, delete, upload, read-storage]\n - auth_id: jenkins2\n privilege: [create, read, update, delete, upload, read-storage]\n - auth_id: jnkns\n privilege: [create, read, update, delete, upload, read-storage]\n"` | USER YAML. Passed in as a multiline string. | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"fence"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["fence"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["fence"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["fence"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["fence"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["fence"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | | autoscaling | map | `{"enabled":false,"maxReplicas":4,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | -| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | -| autoscaling.maxReplicas | int | `4` | Maximum number of replicas | -| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `4` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | | autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | | autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target Memory utilization percentage | -| env[0].name | string | `"DD_ENABLED"` | | -| env[0].valueFrom.configMapKeyRef.key | string | `"dd_enabled"` | | -| env[0].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | -| env[0].valueFrom.configMapKeyRef.optional | bool | `true` | | -| env[10].name | string | `"PYTHONPATH"` | | -| env[10].value | string | `"/var/www/fence"` | | -| env[11].name | string | `"GEN3_DEBUG"` | | -| env[11].value | string | `"False"` | | -| env[12].name | string | `"FENCE_PUBLIC_CONFIG"` | | -| env[12].valueFrom.configMapKeyRef.key | string | `"fence-config-public.yaml"` | | -| env[12].valueFrom.configMapKeyRef.name | string | `"manifest-fence"` | | -| env[12].valueFrom.configMapKeyRef.optional | bool | `true` | | -| env[13].name | string | `"PGHOST"` | | -| env[13].valueFrom.secretKeyRef.key | string | `"host"` | | -| env[13].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| env[13].valueFrom.secretKeyRef.optional | bool | `false` | | -| env[14].name | string | `"PGUSER"` | | -| env[14].valueFrom.secretKeyRef.key | string | `"username"` | | -| env[14].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| env[14].valueFrom.secretKeyRef.optional | bool | `false` | | -| env[15].name | string | `"PGPASSWORD"` | | -| env[15].valueFrom.secretKeyRef.key | string | `"password"` | | -| env[15].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| env[15].valueFrom.secretKeyRef.optional | bool | `false` | | -| env[16].name | string | `"PGDB"` | | -| env[16].valueFrom.secretKeyRef.key | string | `"database"` | | -| env[16].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| env[16].valueFrom.secretKeyRef.optional | bool | `false` | | -| env[17].name | string | `"DBREADY"` | | -| env[17].valueFrom.secretKeyRef.key | string | `"dbcreated"` | | -| env[17].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| env[17].valueFrom.secretKeyRef.optional | bool | `false` | | -| env[18].name | string | `"DB"` | | -| env[18].value | string | `"postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB)"` | | -| env[1].name | string | `"DD_ENV"` | | -| env[1].valueFrom.fieldRef.fieldPath | string | `"metadata.labels['tags.datadoghq.com/env']"` | | -| env[2].name | string | `"DD_SERVICE"` | | -| env[2].valueFrom.fieldRef.fieldPath | string | `"metadata.labels['tags.datadoghq.com/service']"` | | -| env[3].name | string | `"DD_VERSION"` | | -| env[3].valueFrom.fieldRef.fieldPath | string | `"metadata.labels['tags.datadoghq.com/version']"` | | -| env[4].name | string | `"DD_LOGS_INJECTION"` | | -| env[4].value | string | `"true"` | | -| env[5].name | string | `"DD_PROFILING_ENABLED"` | | -| env[5].value | string | `"true"` | | -| env[6].name | string | `"DD_TRACE_SAMPLE_RATE"` | | -| env[6].value | string | `"1"` | | -| env[7].name | string | `"GEN3_UWSGI_TIMEOUT"` | | -| env[7].valueFrom.configMapKeyRef.key | string | `"uwsgi-timeout"` | | -| env[7].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | -| env[7].valueFrom.configMapKeyRef.optional | bool | `true` | | -| env[8].name | string | `"DD_AGENT_HOST"` | | -| env[8].valueFrom.fieldRef.fieldPath | string | `"status.hostIP"` | | -| env[9].name | string | `"AWS_STS_REGIONAL_ENDPOINTS"` | | -| env[9].value | string | `"regional"` | | -| fullnameOverride | string | `""` | | -| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| env | list | `[{"name":"GEN3_UWSGI_TIMEOUT","valueFrom":{"configMapKeyRef":{"key":"uwsgi-timeout","name":"manifest-global","optional":true}}},{"name":"DD_AGENT_HOST","valueFrom":{"fieldRef":{"fieldPath":"status.hostIP"}}},{"name":"AWS_STS_REGIONAL_ENDPOINTS","value":"regional"},{"name":"PYTHONPATH","value":"/var/www/fence"},{"name":"GEN3_DEBUG","value":"False"},{"name":"FENCE_PUBLIC_CONFIG","valueFrom":{"configMapKeyRef":{"key":"fence-config-public.yaml","name":"manifest-fence","optional":true}}},{"name":"PGHOST","valueFrom":{"secretKeyRef":{"key":"host","name":"fence-dbcreds","optional":false}}},{"name":"PGUSER","valueFrom":{"secretKeyRef":{"key":"username","name":"fence-dbcreds","optional":false}}},{"name":"PGPASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"fence-dbcreds","optional":false}}},{"name":"PGDB","valueFrom":{"secretKeyRef":{"key":"database","name":"fence-dbcreds","optional":false}}},{"name":"DBREADY","valueFrom":{"secretKeyRef":{"key":"dbcreated","name":"fence-dbcreds","optional":false}}},{"name":"DB","value":"postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB)"},{"name":"INDEXD_PASSWORD","valueFrom":{"secretKeyRef":{"key":"fence","name":"indexd-service-creds"}}},{"name":"gen3Env","valueFrom":{"configMapKeyRef":{"key":"hostname","name":"manifest-global"}}}]` | Environment variables to pass to the container | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","tierAccessLimit":1000}` | Global configuration options. | | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | | global.dev | bool | `true` | Whether the deployment is for development purposes. | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | @@ -145,7 +99,9 @@ A Helm chart for gen3 Fence | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -157,68 +113,23 @@ A Helm chart for gen3 Fence | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | | global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| global.tierAccessLimit | int | `1000` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | | image.pullPolicy | string | `"Always"` | When to pull the image. This value should be "Always" to ensure the latest image is used. | | image.repository | string | `"quay.io/cdis/fence"` | The Docker image repository for the fence service | -| image.tag | string | `"master"` | The tag to use for the image. | -| imagePullSecrets | list | `[]` | | -| initEnv[0].name | string | `"PGHOST"` | | -| initEnv[0].valueFrom.secretKeyRef.key | string | `"host"` | | -| initEnv[0].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| initEnv[0].valueFrom.secretKeyRef.optional | bool | `false` | | -| initEnv[1].name | string | `"PGUSER"` | | -| initEnv[1].valueFrom.secretKeyRef.key | string | `"username"` | | -| initEnv[1].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| initEnv[1].valueFrom.secretKeyRef.optional | bool | `false` | | -| initEnv[2].name | string | `"PGPASSWORD"` | | -| initEnv[2].valueFrom.secretKeyRef.key | string | `"password"` | | -| initEnv[2].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| initEnv[2].valueFrom.secretKeyRef.optional | bool | `false` | | -| initEnv[3].name | string | `"PGDB"` | | -| initEnv[3].valueFrom.secretKeyRef.key | string | `"database"` | | -| initEnv[3].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| initEnv[3].valueFrom.secretKeyRef.optional | bool | `false` | | -| initEnv[4].name | string | `"DBREADY"` | | -| initEnv[4].valueFrom.secretKeyRef.key | string | `"dbcreated"` | | -| initEnv[4].valueFrom.secretKeyRef.name | string | `"fence-dbcreds"` | | -| initEnv[4].valueFrom.secretKeyRef.optional | bool | `false` | | -| initEnv[5].name | string | `"DB"` | | -| initEnv[5].value | string | `"postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB)"` | | -| initEnv[6].name | string | `"PYTHONPATH"` | | -| initEnv[6].value | string | `"/var/www/fence"` | | -| initEnv[7].name | string | `"FENCE_PUBLIC_CONFIG"` | | -| initEnv[7].valueFrom.configMapKeyRef.key | string | `"fence-config-public.yaml"` | | -| initEnv[7].valueFrom.configMapKeyRef.name | string | `"manifest-fence"` | | -| initEnv[7].valueFrom.configMapKeyRef.optional | bool | `true` | | -| initVolumeMounts[0].mountPath | string | `"/var/www/fence/fence-config.yaml"` | | -| initVolumeMounts[0].name | string | `"config-volume"` | | -| initVolumeMounts[0].readOnly | bool | `true` | | -| initVolumeMounts[0].subPath | string | `"fence-config.yaml"` | | -| initVolumeMounts[1].mountPath | string | `"/var/www/fence/yaml_merge.py"` | | -| initVolumeMounts[1].name | string | `"yaml-merge"` | | -| initVolumeMounts[1].readOnly | bool | `true` | | -| initVolumeMounts[1].subPath | string | `"yaml_merge.py"` | | -| initVolumeMounts[2].mountPath | string | `"/var/www/fence/fence_google_app_creds_secret.json"` | | -| initVolumeMounts[2].name | string | `"fence-google-app-creds-secret-volume"` | | -| initVolumeMounts[2].readOnly | bool | `true` | | -| initVolumeMounts[2].subPath | string | `"fence_google_app_creds_secret.json"` | | -| initVolumeMounts[3].mountPath | string | `"/var/www/fence/fence_google_storage_creds_secret.json"` | | -| initVolumeMounts[3].name | string | `"fence-google-storage-creds-secret-volume"` | | -| initVolumeMounts[3].readOnly | bool | `true` | | -| initVolumeMounts[3].subPath | string | `"fence_google_storage_creds_secret.json"` | | -| labels."tags.datadoghq.com/env" | string | `"anvilstaging"` | | -| labels."tags.datadoghq.com/service" | string | `"fence"` | | -| labels."tags.datadoghq.com/version" | float | `2021.12` | | -| labels.app | string | `"fence"` | | -| labels.authprovider | string | `"yes"` | | -| labels.netnolimit | string | `"yes"` | | -| labels.public | string | `"yes"` | | -| labels.release | string | `"production"` | | -| labels.userhelper | string | `"yes"` | | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| initEnv | list | `[{"name":"PGHOST","valueFrom":{"secretKeyRef":{"key":"host","name":"fence-dbcreds","optional":false}}},{"name":"PGUSER","valueFrom":{"secretKeyRef":{"key":"username","name":"fence-dbcreds","optional":false}}},{"name":"PGPASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"fence-dbcreds","optional":false}}},{"name":"PGDB","valueFrom":{"secretKeyRef":{"key":"database","name":"fence-dbcreds","optional":false}}},{"name":"DBREADY","valueFrom":{"secretKeyRef":{"key":"dbcreated","name":"fence-dbcreds","optional":false}}},{"name":"DB","value":"postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB)"},{"name":"FENCE_DB","value":"postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB)"},{"name":"PYTHONPATH","value":"/var/www/fence"},{"name":"FENCE_PUBLIC_CONFIG","valueFrom":{"configMapKeyRef":{"key":"fence-config-public.yaml","name":"manifest-fence","optional":true}}}]` | Volumes to attach to the init container. | +| initVolumeMounts | list | `[{"mountPath":"/var/www/fence/fence-config.yaml","name":"config-volume","readOnly":true,"subPath":"fence-config.yaml"},{"mountPath":"/var/www/fence/yaml_merge.py","name":"yaml-merge","readOnly":true,"subPath":"yaml_merge.py"},{"mountPath":"/var/www/fence/fence_google_app_creds_secret.json","name":"fence-google-app-creds-secret-volume","readOnly":true,"subPath":"fence_google_app_creds_secret.json"},{"mountPath":"/var/www/fence/fence_google_storage_creds_secret.json","name":"fence-google-storage-creds-secret-volume","readOnly":true,"subPath":"fence_google_storage_creds_secret.json"}]` | Volumes to mount to the init container. | +| labels | map | `{"authprovider":"yes","netnolimit":"yes","public":"yes","userhelper":"yes"}` | Labels to add to the pod. | +| labels.authprovider | string | `"yes"` | Grants egress from all pods to pods labeled with authrpovider=yes. For network policy selectors. | +| labels.netnolimit | string | `"yes"` | Grants egress from pods labeled with netnolimit=yes to any IP address. Use explicit proxy and AWS APIs | +| labels.public | string | `"yes"` | Grants ingress from the revproxy service for pods labeled with public=yes | +| labels.userhelper | string | `"yes"` | Grants ingress from pods in usercode namespaces for gen3 pods labeled with userhelper=yes | | logo | string | `nil` | | -| nameOverride | string | `""` | | +| nameOverride | string | `""` | Override the name of the chart. | | nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"Authentication"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | podAnnotations | map | `{}` | Annotations to add to the pod | | podSecurityContext | map | `{"fsGroup":101}` | Security context for the pod | | postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | @@ -227,8 +138,13 @@ A Helm chart for gen3 Fence | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | | privacy_policy | string | `nil` | | +| projects | string | `nil` | | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | replicaCount | int | `1` | Number of desired replicas | | resources | map | `{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":0.3,"memory":"128Mi"}}` | Resource requests and limits for the containers in the pod | | resources.limits | map | `{"cpu":1,"memory":"2Gi"}` | The maximum amount of resources that the container is allowed to use | @@ -238,84 +154,31 @@ A Helm chart for gen3 Fence | resources.requests.cpu | string | `0.3` | The amount of CPU requested | | resources.requests.memory | string | `"128Mi"` | The amount of memory requested | | securityContext | map | `{}` | Security context for the containers in the pod | -| selectorLabels.app | string | `"fence"` | | -| selectorLabels.release | string | `"production"` | | -| service | map | `{"port":80,"type":"ClusterIP"}` | Configuration for the service | -| service.port | int | `80` | Port on which the service is exposed | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | | service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{"eks.amazonaws.com/role-arn":null},"create":true,"name":"fence-sa"}` | Service account to use or create. | +| serviceAccount.annotations | map | `{"eks.amazonaws.com/role-arn":null}` | Annotations to add to the service account. | | serviceAccount.annotations."eks.amazonaws.com/role-arn" | string | `nil` | The Amazon Resource Name (ARN) of the role to associate with the service account | -| serviceAccount.create | bool | `true` | Whether to create a service account | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | | serviceAccount.name | string | `"fence-sa"` | The name of the service account | | tolerations | list | `[]` | Tolerations for the pods | -| volumeMounts[0].mountPath | string | `"/var/www/fence/local_settings.py"` | | -| volumeMounts[0].name | string | `"old-config-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"local_settings.py"` | | -| volumeMounts[10].mountPath | string | `"/fence/keys/key/jwt_private_key.pem"` | | -| volumeMounts[10].name | string | `"fence-jwt-keys"` | | -| volumeMounts[10].readOnly | bool | `true` | | -| volumeMounts[10].subPath | string | `"jwt_private_key.pem"` | | -| volumeMounts[1].mountPath | string | `"/var/www/fence/fence_credentials.json"` | | -| volumeMounts[1].name | string | `"json-secret-volume"` | | -| volumeMounts[1].readOnly | bool | `true` | | -| volumeMounts[1].subPath | string | `"fence_credentials.json"` | | -| volumeMounts[2].mountPath | string | `"/var/www/fence/creds.json"` | | -| volumeMounts[2].name | string | `"creds-volume"` | | -| volumeMounts[2].readOnly | bool | `true` | | -| volumeMounts[2].subPath | string | `"creds.json"` | | -| volumeMounts[3].mountPath | string | `"/var/www/fence/config_helper.py"` | | -| volumeMounts[3].name | string | `"config-helper"` | | -| volumeMounts[3].readOnly | bool | `true` | | -| volumeMounts[3].subPath | string | `"config_helper.py"` | | -| volumeMounts[4].mountPath | string | `"/fence/fence/static/img/logo.svg"` | | -| volumeMounts[4].name | string | `"logo-volume"` | | -| volumeMounts[4].readOnly | bool | `true` | | -| volumeMounts[4].subPath | string | `"logo.svg"` | | -| volumeMounts[5].mountPath | string | `"/fence/fence/static/privacy_policy.md"` | | -| volumeMounts[5].name | string | `"privacy-policy"` | | -| volumeMounts[5].readOnly | bool | `true` | | -| volumeMounts[5].subPath | string | `"privacy_policy.md"` | | -| volumeMounts[6].mountPath | string | `"/var/www/fence/fence-config.yaml"` | | -| volumeMounts[6].name | string | `"config-volume"` | | -| volumeMounts[6].readOnly | bool | `true` | | -| volumeMounts[6].subPath | string | `"fence-config.yaml"` | | -| volumeMounts[7].mountPath | string | `"/var/www/fence/yaml_merge.py"` | | -| volumeMounts[7].name | string | `"yaml-merge"` | | -| volumeMounts[7].readOnly | bool | `true` | | -| volumeMounts[7].subPath | string | `"yaml_merge.py"` | | -| volumeMounts[8].mountPath | string | `"/var/www/fence/fence_google_app_creds_secret.json"` | | -| volumeMounts[8].name | string | `"fence-google-app-creds-secret-volume"` | | -| volumeMounts[8].readOnly | bool | `true` | | -| volumeMounts[8].subPath | string | `"fence_google_app_creds_secret.json"` | | -| volumeMounts[9].mountPath | string | `"/var/www/fence/fence_google_storage_creds_secret.json"` | | -| volumeMounts[9].name | string | `"fence-google-storage-creds-secret-volume"` | | -| volumeMounts[9].readOnly | bool | `true` | | -| volumeMounts[9].subPath | string | `"fence_google_storage_creds_secret.json"` | | -| volumes[0].name | string | `"old-config-volume"` | | -| volumes[0].secret.secretName | string | `"fence-secret"` | | -| volumes[10].configMap.name | string | `"fence-yaml-merge"` | | -| volumes[10].configMap.optional | bool | `true` | | -| volumes[10].name | string | `"yaml-merge"` | | -| volumes[1].name | string | `"json-secret-volume"` | | -| volumes[1].secret.optional | bool | `true` | | -| volumes[1].secret.secretName | string | `"fence-json-secret"` | | -| volumes[2].name | string | `"creds-volume"` | | -| volumes[2].secret.secretName | string | `"fence-creds"` | | -| volumes[3].configMap.name | string | `"config-helper"` | | -| volumes[3].configMap.optional | bool | `true` | | -| volumes[3].name | string | `"config-helper"` | | -| volumes[4].configMap.name | string | `"logo-config"` | | -| volumes[4].name | string | `"logo-volume"` | | -| volumes[5].name | string | `"config-volume"` | | -| volumes[5].secret.secretName | string | `"fence-config"` | | -| volumes[6].name | string | `"fence-google-app-creds-secret-volume"` | | -| volumes[6].secret.secretName | string | `"fence-google-app-creds-secret"` | | -| volumes[7].name | string | `"fence-google-storage-creds-secret-volume"` | | -| volumes[7].secret.secretName | string | `"fence-google-storage-creds-secret"` | | -| volumes[8].name | string | `"fence-jwt-keys"` | | -| volumes[8].secret.secretName | string | `"fence-jwt-keys"` | | -| volumes[9].configMap.name | string | `"privacy-policy"` | | -| volumes[9].name | string | `"privacy-policy"` | | +| usersync | map | `{"addDbgap":false,"custom_image":null,"onlyDbgap":false,"schedule":"*/30 * * * *","secrets":{"awsAccessKeyId":"","awsSecretAccessKey":""},"slack_send_dbgap":false,"slack_webhook":"None","syncFromDbgap":false,"userYamlS3Path":"s3://cdis-gen3-users/helm-test/user.yaml","usersync":false}` | Configuration options for usersync cronjob. | +| usersync.addDbgap | bool | `false` | Force attempting a dbgap sync if "true", falls back on user.yaml | +| usersync.custom_image | string | `nil` | To set a custom image for pulling the user.yaml file from S3. Default is the Gen3 Awshelper image. | +| usersync.onlyDbgap | bool | `false` | Forces ONLY a dbgap sync if "true", IGNORING user.yaml | +| usersync.schedule | string | `"*/30 * * * *"` | The cron schedule expression to use in the usersync cronjob. Runs every 30 minutes by default. | +| usersync.secrets | map | `{"awsAccessKeyId":"","awsSecretAccessKey":""}` | Secret information | +| usersync.secrets.awsAccessKeyId | str | `""` | AWS access key ID for usersync S3 bucket | +| usersync.secrets.awsSecretAccessKey | str | `""` | AWS secret access key for usersync S3 bucket | +| usersync.slack_send_dbgap | bool | `false` | Will echo what files we are seeing on dbgap ftp to Slack. | +| usersync.slack_webhook | string | `"None"` | Slack webhook endpoint used with certain jobs. | +| usersync.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | +| usersync.userYamlS3Path | string | `"s3://cdis-gen3-users/helm-test/user.yaml"` | Path to the user.yaml file in S3. | +| usersync.usersync | bool | `false` | Whether to run Fence usersync or not. | +| volumeMounts | list | `[{"mountPath":"/var/www/fence/local_settings.py","name":"old-config-volume","readOnly":true,"subPath":"local_settings.py"},{"mountPath":"/var/www/fence/fence_credentials.json","name":"json-secret-volume","readOnly":true,"subPath":"fence_credentials.json"},{"mountPath":"/var/www/fence/creds.json","name":"creds-volume","readOnly":true,"subPath":"creds.json"},{"mountPath":"/var/www/fence/config_helper.py","name":"config-helper","readOnly":true,"subPath":"config_helper.py"},{"mountPath":"/fence/fence/static/img/logo.svg","name":"logo-volume","readOnly":true,"subPath":"logo.svg"},{"mountPath":"/fence/fence/static/privacy_policy.md","name":"privacy-policy","readOnly":true,"subPath":"privacy_policy.md"},{"mountPath":"/var/www/fence/fence-config.yaml","name":"config-volume","readOnly":true,"subPath":"fence-config.yaml"},{"mountPath":"/var/www/fence/yaml_merge.py","name":"yaml-merge","readOnly":true,"subPath":"yaml_merge.py"},{"mountPath":"/var/www/fence/fence_google_app_creds_secret.json","name":"fence-google-app-creds-secret-volume","readOnly":true,"subPath":"fence_google_app_creds_secret.json"},{"mountPath":"/var/www/fence/fence_google_storage_creds_secret.json","name":"fence-google-storage-creds-secret-volume","readOnly":true,"subPath":"fence_google_storage_creds_secret.json"},{"mountPath":"/fence/keys/key/jwt_private_key.pem","name":"fence-jwt-keys","readOnly":true,"subPath":"jwt_private_key.pem"}]` | Volumes to mount to the container. | +| volumes | list | `[{"name":"old-config-volume","secret":{"secretName":"fence-secret"}},{"name":"json-secret-volume","secret":{"optional":true,"secretName":"fence-json-secret"}},{"name":"creds-volume","secret":{"secretName":"fence-creds"}},{"configMap":{"name":"config-helper","optional":true},"name":"config-helper"},{"configMap":{"name":"logo-config"},"name":"logo-volume"},{"name":"config-volume","secret":{"secretName":"fence-config"}},{"name":"fence-google-app-creds-secret-volume","secret":{"secretName":"fence-google-app-creds-secret"}},{"name":"fence-google-storage-creds-secret-volume","secret":{"secretName":"fence-google-storage-creds-secret"}},{"name":"fence-jwt-keys","secret":{"secretName":"fence-jwt-keys"}},{"configMap":{"name":"privacy-policy"},"name":"privacy-policy"},{"configMap":{"name":"fence-yaml-merge","optional":true},"name":"yaml-merge"}]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/fence/fence-google-creds/fence_google_app_creds_secret.json b/helm/fence/fence-google-creds/fence_google_app_creds_secret.json new file mode 100644 index 00000000..e69de29b diff --git a/helm/fence/fence-google-creds/fence_google_storage_creds_secret.json b/helm/fence/fence-google-creds/fence_google_storage_creds_secret.json new file mode 100644 index 00000000..e69de29b diff --git a/helm/fence/projects/projects.yaml b/helm/fence/projects/projects.yaml new file mode 100644 index 00000000..e69de29b diff --git a/helm/fence/templates/_helpers.tpl b/helm/fence/templates/_helpers.tpl index 1eeaac06..f7011103 100644 --- a/helm/fence/templates/_helpers.tpl +++ b/helm/fence/templates/_helpers.tpl @@ -5,6 +5,24 @@ Expand the name of the chart. {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} + + +{{/* +a function to generate or get the jwt keys +*/}} + +{{- define "getOrCreatePrivateKey" -}} +{{- $secretName := "fence-jwt-keys" }} +{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace $secretName) }} +{{- if $existingSecret }} +{{- index $existingSecret.data "jwt_private_key.pem" }} +{{- else }} +{{- genPrivateKey "rsa" | b64enc }} +{{- end }} +{{- end -}} + + + {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). @@ -34,20 +52,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "fence.labels" -}} -helm.sh/chart: {{ include "fence.chart" . }} -{{ include "fence.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "fence.selectorLabels" -}} -app.kubernetes.io/name: {{ include "fence.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/fence/templates/aws-userysnc-creds.yaml b/helm/fence/templates/aws-userysnc-creds.yaml new file mode 100644 index 00000000..c6baff53 --- /dev/null +++ b/helm/fence/templates/aws-userysnc-creds.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: aws-config-fence +type: Opaque +stringData: + credentials: | + [default] + aws_access_key_id={{.Values.usersync.secrets.awsAccessKeyId}} + aws_secret_access_key={{.Values.usersync.secrets.awsSecretAccessKey}} \ No newline at end of file diff --git a/helm/fence/templates/fence-config.yaml b/helm/fence/templates/fence-config.yaml index 2ff0eadc..29d0df2e 100644 --- a/helm/fence/templates/fence-config.yaml +++ b/helm/fence/templates/fence-config.yaml @@ -8,4 +8,4 @@ stringData: {{- with .Values.FENCE_CONFIG }} {{- toYaml . | nindent 4 }} {{ end }} - \ No newline at end of file +--- \ No newline at end of file diff --git a/helm/fence/templates/fence-deployment.yaml b/helm/fence/templates/fence-deployment.yaml index 112bcac3..33dcf530 100644 --- a/helm/fence/templates/fence-deployment.yaml +++ b/helm/fence/templates/fence-deployment.yaml @@ -4,6 +4,9 @@ metadata: name: fence-deployment labels: {{- include "fence.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -18,15 +21,19 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: - {{- include "fence.labels" . | nindent 8 }} + {{- include "fence.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: + enableServiceLinks: false serviceAccountName: {{ include "fence.serviceAccountName" . }} volumes: {{- toYaml .Values.volumes | nindent 8 }} containers: - name: fence image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: Always + imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 @@ -66,6 +73,9 @@ spec: fi bash /fence/dockerrun.bash && if [[ -f /dockerrun.sh ]]; then bash /dockerrun.sh; fi env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- toYaml .Values.env | nindent 12 }} volumeMounts: {{- toYaml .Values.volumeMounts | nindent 12 }} diff --git a/helm/fence/templates/jwt-keys.yaml b/helm/fence/templates/jwt-keys.yaml index fca35e12..893a4488 100644 --- a/helm/fence/templates/jwt-keys.yaml +++ b/helm/fence/templates/jwt-keys.yaml @@ -4,4 +4,4 @@ metadata: name: fence-jwt-keys type: Opaque data: - jwt_private_key.pem: {{ genPrivateKey "rsa" | b64enc }} + jwt_private_key.pem: {{ include "getOrCreatePrivateKey" . }} diff --git a/helm/fence/templates/pdb.yaml b/helm/fence/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/fence/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/fence/templates/projects-config.yaml b/helm/fence/templates/projects-config.yaml new file mode 100644 index 00000000..f963fb6d --- /dev/null +++ b/helm/fence/templates/projects-config.yaml @@ -0,0 +1,8 @@ +{{- with .Values.usersync.projects }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: projects +data: + projects.yaml: {{ . }} +{{- end }} diff --git a/helm/fence/templates/serviceaccount.yaml b/helm/fence/templates/serviceaccount.yaml index 8193f704..48c24f36 100644 --- a/helm/fence/templates/serviceaccount.yaml +++ b/helm/fence/templates/serviceaccount.yaml @@ -9,4 +9,4 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/fence/templates/tests/test-connection.yaml b/helm/fence/templates/tests/test-connection.yaml index 2a65b670..7986768b 100644 --- a/helm/fence/templates/tests/test-connection.yaml +++ b/helm/fence/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "fence.fullname" . }}-test-connection" + name: "fence-test-connection" labels: {{- include "fence.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "fence.fullname" . }}:{{ .Values.service.port }}'] + args: ['fence-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/fence/templates/usersync-cron.yaml b/helm/fence/templates/usersync-cron.yaml index e69de29b..ef683944 100644 --- a/helm/fence/templates/usersync-cron.yaml +++ b/helm/fence/templates/usersync-cron.yaml @@ -0,0 +1,211 @@ +{{- if .Values.usersync.usersync -}} +# +# run with: +# gen3 job run usersync +# +# Optional Arguments: +# ADD_DBGAP Force attempting a dbgap sync if "true", falls back on user.yaml +# by defualt. i.e. this isn't required for a dbGaP sync to happen +# default: "false" - fall back on user.yaml +# +# ONLY_DBGAP Forces ONLY a dbgap sync if "true", IGNORING user.yaml +# default: "false" +# +apiVersion: batch/v1 +kind: CronJob +metadata: + name: usersync +spec: + schedule: {{ .Values.usersync.schedule | quote }} + jobTemplate: + spec: + template: + metadata: + labels: + app: gen3job + spec: + serviceAccountName: usersync-job + volumes: + - name: user-yaml + configMap: + name: useryaml + items: + - key: useryaml + path: user.yaml + - name: config-volume + secret: + secretName: "fence-config" + - name: creds-volume + secret: + secretName: "fence-creds" + - name: projects + configMap: + name: "projects" + optional: true + - name: fence-google-app-creds-secret-volume + secret: + secretName: "fence-google-app-creds-secret" + - name: fence-google-storage-creds-secret-volume + secret: + secretName: "fence-google-storage-creds-secret" + - name: shared-data + emptyDir: {} + - name: cred-volume + secret: + secretName: aws-config-fence + initContainers: + - name: wait-for-fence + image: curlimages/curl:latest + command: ["/bin/sh","-c"] + args: ["while [ $(curl -sw '%{http_code}' http://fence-service -o /dev/null) -ne 200 ]; do sleep 5; echo 'Waiting for fence...'; done"] + containers: + - name: usersync + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: Always + env: + - name: SYNC_FROM_DBGAP + value: {{ .Values.usersync.syncFromDbgap | quote }} + - name: ADD_DBGAP + value: {{ .Values.usersync.addDbgap | quote }} + - name: ONLY_DBGAP + value: {{ .Values.usersync.onlyDbgap | quote }} + - name: SLACK_SEND_DBGAP + value: {{ .Values.usersync.slack_send_dbgap | quote }} + - name: slackWebHook + value: {{ .Values.usersync.slack_webhook | quote }} + {{- toYaml .Values.env | nindent 12 }} + volumeMounts: + - name: shared-data + mountPath: /mnt/shared + - name: "config-volume" + readOnly: true + mountPath: "/var/www/fence/fence-config.yaml" + subPath: fence-config.yaml + - name: "creds-volume" + readOnly: true + mountPath: "/var/www/fence/creds.json" + subPath: creds.json + - name: "projects" + mountPath: "/var/www/fence/projects.yaml" + subPath: "projects.yaml" + - name: "fence-google-app-creds-secret-volume" + readOnly: true + mountPath: "/var/www/fence/fence_google_app_creds_secret.json" + subPath: fence_google_app_creds_secret.json + - name: "fence-google-storage-creds-secret-volume" + readOnly: true + mountPath: "/var/www/fence/fence_google_storage_creds_secret.json" + subPath: fence_google_storage_creds_secret.json + command: ["/bin/bash" ] + args: + - "-c" + # Script always succeeds if it runs (echo exits with 0) + - | + echo 'options use-vc' >> /etc/resolv.conf + pip3 install SQLAlchemy==1.3.6 + # can be removed once this is merged: https://github.com/uc-cdis/fence/pull/1096 + if [[ "$SYNC_FROM_DBGAP" != "true" && "$ADD_DBGAP" != "true" ]]; then + if [[ -f /mnt/shared/user.yaml ]]; then + echo "running fence-create" + time fence-create sync --arborist http://arborist-service --yaml /mnt/shared/user.yaml + else + echo "/mnt/shared/user.yaml did not appear within timeout :-(" + false # non-zero exit code + fi + exitcode=$? + else + output=$(mktemp "/tmp/fence-create-output_XXXXXX") + if [[ -f /mnt/shared/user.yaml && "$ONLY_DBGAP" != "true" ]]; then + echo "Running fence-create dbgap-sync with user.yaml - see $output" + time fence-create sync --arborist http://arborist-service --sync_from_dbgap "True" --projects /var/www/fence/projects.yaml --yaml /mnt/shared/user.yaml 2>&1 | tee "$output" + else + echo "Running fence-create dbgap-sync without user.yaml - see $output" + time fence-create sync --arborist http://arborist-service --sync_from_dbgap "True" --projects /var/www/fence/projects.yaml 2>&1 | tee "$output" + fi + exitcode="${PIPESTATUS[0]}" + echo "$output" + # Echo what files we are seeing on dbgap ftp to Slack + # We only do this step every 12 hours and not on weekends to reduce noise + if [[ -n "$SLACK_SEND_DBGAP" && "$SLACK_SEND_DBGAP" = "true" ]]; then + files=$(grep "Reading file" "$output") + let hour=$(date -u +10#%H) + let dow=$(date -u +10#%u) + if ! (( hour % 12 )) && (( dow < 6 )); then + if [ "${slackWebHook}" != 'None' ]; then + curl -X POST --data-urlencode "payload={\"text\": \"FenceHelper: \n\`\`\`\n${files}\n\`\`\`\"}" "${slackWebHook}" + fi + fi + fi + fi + if [[ $exitcode -ne 0 && "${slackWebHook}" != 'None' ]]; then + emptyfile=$(grep "EnvironmentError:" "$output") + if [ ! -z "$emptyfile" ]; then + curl -X POST --data-urlencode "payload={\"text\": \"JOBSKIPPED: User sync skipped on ${gen3Env} ${emptyfile}\"}" "${slackWebHook}"; + else + curl -X POST --data-urlencode "payload={\"text\": \"JOBFAIL: User sync failed on ${gen3Env}\"}" "${slackWebHook}" + fi + fi + echo "Exit code: $exitcode" + exit "$exitcode" + - name: awshelper + image: {{ .Values.usersync.custom_image | default "quay.io/cdis/awshelper:master" }} + imagePullPolicy: Always + env: + - name: gen3Env + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: userYamlS3Path + value: {{ .Values.usersync.userYamlS3Path | quote }} + - name: slackWebHook + value: {{ .Values.usersync.slack_webhook | quote }} + volumeMounts: + - name: user-yaml + mountPath: /var/www/fence + - name: shared-data + mountPath: /mnt/shared + - name: cred-volume + mountPath: "/home/ubuntu/.aws/credentials" + subPath: credentials + command: ["/bin/bash" ] + args: + - "-c" + - | + GEN3_HOME=/home/ubuntu/cloud-automation + source "${GEN3_HOME}/gen3/lib/utils.sh" + gen3_load "gen3/gen3setup" + + if [ "${userYamlS3Path}" = 'none' ]; then + echo "using local user.yaml" + cp /var/www/fence/user.yaml /mnt/shared/user.yaml + else + # ----------------- + echo "awshelper downloading ${userYamlS3Path} to /mnt/shared/user.yaml" + n=0 + until [ $n -ge 5 ]; do + echo "Download attempt $n" + aws s3 cp "${userYamlS3Path}" /mnt/shared/user.yaml && break + n=$[$n+1] + sleep 2 + echo "test 1" + done + fi + if [[ ! -f /mnt/shared/user.yaml ]]; then + echo "awshelper failed to retrieve /mnt/shared/user.yaml" + exit 1 + fi + #----------- + echo "awshelper updating etl configmap" + if ! gen3 gitops etl-convert < /mnt/shared/user.yaml > /tmp/user.yaml; then + echo "ERROR: failed to generate ETL config" + exit 1 + fi + # kubectl delete configmap fence > /dev/null 2>&1 + # kubectl create configmap fence --from-file=/tmp/user.yaml + if [ "${slackWebHook}" != 'None' ]; then + curl -X POST --data-urlencode "payload={\"text\": \"AWSHelper: Syncing users on ${gen3Env}\"}" "${slackWebHook}" + fi + echo "Helper exit ok" + restartPolicy: "Never" +{{- end }} \ No newline at end of file diff --git a/helm/fence/templates/usersync-sa.yaml b/helm/fence/templates/usersync-sa.yaml new file mode 100644 index 00000000..379271ce --- /dev/null +++ b/helm/fence/templates/usersync-sa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.usersync.usersync -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: usersync-job +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: usersync-job-role +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch", "create", "update", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: usersync-job-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: usersync-job-role +subjects: + - kind: ServiceAccount + name: usersync-job + namespace: default +{{- end }} \ No newline at end of file diff --git a/helm/fence/templates/useryaml-job.yaml b/helm/fence/templates/useryaml-job.yaml index 394e511b..70260516 100644 --- a/helm/fence/templates/useryaml-job.yaml +++ b/helm/fence/templates/useryaml-job.yaml @@ -44,5 +44,7 @@ spec: - "-c" # Script always succeeds if it runs (echo exits with 0) - | + pip3 install SQLAlchemy==1.3.6 + # can be removed once this is merged: https://github.com/uc-cdis/fence/pull/1096 fence-create sync --arborist http://arborist-service --yaml /var/www/fence/user.yaml restartPolicy: OnFailure diff --git a/helm/fence/values.yaml b/helm/fence/values.yaml index ee1d35f9..e7d65301 100644 --- a/helm/fence/values.yaml +++ b/helm/fence/values.yaml @@ -36,18 +36,22 @@ global: logsBucket: logs-gen3 # -- (bool) Whether to sync data from dbGaP. syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: 1000 # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -65,8 +69,44 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. separate: false +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false + +# -- (map) Configuration options for usersync cronjob. +usersync: + # -- (bool) Whether to run Fence usersync or not. + usersync: false + # -- (map) Secret information + secrets: + # -- (str) AWS access key ID for usersync S3 bucket + awsAccessKeyId: "" + # -- (str) AWS secret access key for usersync S3 bucket + awsSecretAccessKey: "" + # -- (string) The cron schedule expression to use in the usersync cronjob. Runs every 30 minutes by default. + schedule: "*/30 * * * *" + # -- (string) To set a custom image for pulling the user.yaml file from S3. Default is the Gen3 Awshelper image. + custom_image: + # -- (bool) Whether to sync data from dbGaP. + syncFromDbgap: false + # -- (bool) Force attempting a dbgap sync if "true", falls back on user.yaml + addDbgap: false + # -- (bool) Forces ONLY a dbgap sync if "true", IGNORING user.yaml + onlyDbgap: false + # -- (string) Path to the user.yaml file in S3. + userYamlS3Path: s3://cdis-gen3-users/helm-test/user.yaml + # -- (string) Slack webhook endpoint used with certain jobs. + slack_webhook: None + # -- (bool) Will echo what files we are seeing on dbgap ftp to Slack. + slack_send_dbgap: false + # -- (int) Number of desired replicas replicaCount: 1 @@ -75,24 +115,26 @@ image: repository: quay.io/cdis/fence # -- (string) When to pull the image. This value should be "Always" to ensure the latest image is used. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. - # -- (string) The tag to use for the image. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "master" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" -fullnameOverride: "" +# -- (string) Override the full name of the deployment. +fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created - # -- (bool) Whether to create a service account + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: # -- (string) The Amazon Resource Name (ARN) of the role to associate with the service account eks.amazonaws.com/role-arn: - # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template # -- (string) The name of the service account name: "fence-sa" @@ -113,11 +155,11 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 -# -- (map) Configuration for the service +# -- (map) Kubernetes service information. service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP - # -- (int) Port on which the service is exposed + # -- (int) The port number that the service exposes. port: 80 # -- (map) Resource requests and limits for the containers in the pod @@ -138,11 +180,11 @@ resources: # -- (map) Configuration for autoscaling the number of replicas autoscaling: - # -- (bool) Whether autoscaling is enabled or not + # -- (bool) Whether autoscaling is enabled enabled: false - # -- (int) Minimum number of replicas + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 - # -- (int) Maximum number of replicas + # -- (int) The maximum number of replicas to scale up to maxReplicas: 4 # -- (int) Target CPU utilization percentage targetCPUUtilizationPercentage: 80 @@ -155,57 +197,39 @@ nodeSelector: {} # -- (list) Tolerations for the pods tolerations: [] +# -- (map) Labels to add to the pod. labels: - app: fence - release: production - # for network policy selectors + # -- (string) Grants egress from all pods to pods labeled with authrpovider=yes. For network policy selectors. authprovider: "yes" - # uses explicit proxy and AWS APIs + # -- (string) Grants egress from pods labeled with netnolimit=yes to any IP address. Use explicit proxy and AWS APIs netnolimit: "yes" + # -- (string) Grants ingress from the revproxy service for pods labeled with public=yes public: "yes" + # -- (string) Grants ingress from pods in usercode namespaces for gen3 pods labeled with userhelper=yes userhelper: "yes" - tags.datadoghq.com/service: "fence" - tags.datadoghq.com/env: anvilstaging - tags.datadoghq.com/version: 2021.12 +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - fence + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (list) Environment variables to pass to the container env: - - name: DD_ENABLED - valueFrom: - configMapKeyRef: - name: manifest-global - key: dd_enabled - optional: true - - name: DD_ENV - valueFrom: - fieldRef: - fieldPath: metadata.labels['tags.datadoghq.com/env'] - - name: DD_SERVICE - valueFrom: - fieldRef: - fieldPath: metadata.labels['tags.datadoghq.com/service'] - - name: DD_VERSION - valueFrom: - fieldRef: - fieldPath: metadata.labels['tags.datadoghq.com/version'] - - name: DD_LOGS_INJECTION - value: "true" - - name: DD_PROFILING_ENABLED - value: "true" - - name: DD_TRACE_SAMPLE_RATE - value: "1" - name: GEN3_UWSGI_TIMEOUT valueFrom: configMapKeyRef: @@ -254,7 +278,18 @@ env: optional: false - name: DB value: postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB) + - name: INDEXD_PASSWORD + valueFrom: + secretKeyRef: + name: indexd-service-creds + key: fence + - name: gen3Env + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname +# -- (list) Volumes to attach to the container. volumes: - name: old-config-volume secret: @@ -292,6 +327,8 @@ volumes: - name: useryaml configMap: name: useryaml + +# -- (list) Volumes to mount to the container. volumeMounts: - name: "useryaml" mountPath: "/var/www/fence/user.yaml" @@ -345,6 +382,7 @@ volumeMounts: mountPath: "/fence/keys/key/jwt_private_key.pem" subPath: "jwt_private_key.pem" +# -- (list) Volumes to mount to the init container. initVolumeMounts: - name: "config-volume" readOnly: true @@ -363,6 +401,7 @@ initVolumeMounts: mountPath: "/var/www/fence/fence_google_storage_creds_secret" subPath: google-storage-credentials.json +# -- (list) Volumes to attach to the init container. initEnv: - name: PGHOST valueFrom: @@ -396,17 +435,34 @@ initEnv: optional: false - name: DB value: postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB) + - name: FENCE_DB + value: postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):5432/$(PGDB) - name: PYTHONPATH value: /var/www/fence +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Authentication" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl selectorLabels: - app: fence - release: production +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 logo: privacy_policy: - +projects: # USER_SYNC_CRON: # LOCATION: @@ -1411,58 +1467,58 @@ FENCE_CONFIG: # -- (str) Optional; defaults to 'test@example.com' mock_default_user: 'test@example.com' - # -- dict: Contains multi-tenant Fence configuration + # -- (dict): Contains multi-tenant Fence configuration # Support for multi-tenant fence (another fence is this fence's IDP) # If this fence instance is a client of another fence, fill this cfg out. # REMOVE if not needed fence: - # -- str: Root URL for the other fence + # -- (str): Root URL for the other fence # this api_base_url should be the root url for the OTHER fence # something like: https://example.com api_base_url: '' - # -- str: ID of the client of this fence on the other fence + # -- (str): ID of the client of this fence on the other fence # this client_id and client_secret should be obtained by registering THIS fence as # a new client of the OTHER fence client_id: '' - # -- str: Secret of the client of this fence on the other fence + # -- (str): Secret of the client of this fence on the other fence client_secret: '' # -- dict: Additional client parameters client_kwargs: - # -- str: Space-separated string of scopes + # -- (str): Space-separated string of scopes # openid is required to use OIDC flow scope: 'openid' - # -- str: The URL to which the other fence will redirect after logging in + # -- (str): The URL to which the other fence will redirect after logging in redirect_uri: '{{BASE_URL}}/login/fence/login' - # -- str: URL for authorization endpoint of the other fence + # -- (str): URL for authorization endpoint of the other fence # The next 3 should not need to be changed if the provider is following # Oauth2 endpoint naming conventions authorize_url: '{{api_base_url}}/oauth2/authorize' - # -- str: URL for access token endpoint of the other fence + # -- (str): URL for access token endpoint of the other fence access_token_url: '{{api_base_url}}/oauth2/token' - # -- str: URL for refresh token endpoint of the other fence + # -- (str): URL for refresh token endpoint of the other fence refresh_token_url: '{{api_base_url}}/oauth2/token' - # -- str: Name of the provider for consent screens + # -- (str): Name of the provider for consent screens # Custom name to display for consent screens. If not provided, will use `fence`. # If the other fence is using NIH Login, you should make name: `NIH Login` name: '' - # -- bool: Whether to mock a successful login response for testing purposes + # -- (bool): Whether to mock a successful login response for testing purposes # if mock is true, will fake a successful login response for login # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) mock: false - # -- str: Default user for mock login + # -- (str): Default user for mock login mock_default_user: 'test@example.com' - # -- str: URL of the shibboleth discovery endpoint if needed for InCommon login + # -- (str): URL of the shibboleth discovery endpoint if needed for InCommon login # this is needed to enable InCommon login, if some LOGIN_OPTIONS are configured with idp=fence and a list of shib_idps: shibboleth_discovery_url: 'https://login.bionimbus.org/Shibboleth.sso/DiscoFeed' @@ -1781,7 +1837,6 @@ FENCE_CONFIG: password: '' port: 22 proxy: '' - proxy_user: '' protocol: 'sftp' decrypt_key: '' # parse out the consent from the dbgap accession number such that something @@ -1924,7 +1979,7 @@ FENCE_CONFIG: # url where indexd microservice is running (for signed urls primarily) # NOTE: Leaving as null will force fence to default to {{BASE_URL}}/index # example value: 'https://example.com/index' - INDEXD: null + INDEXD: http://indexd-service # this is the username which fence uses to make authenticated requests to indexd INDEXD_USERNAME: 'fence' @@ -1946,7 +2001,7 @@ FENCE_CONFIG: AZ_BLOB_CONTAINER_URL: 'https://myfakeblob.blob.core.windows.net/my-fake-container/' # url where authz microservice is running - ARBORIST: null + ARBORIST: http://arborist-service # url where the audit-service is running AUDIT_SERVICE: 'http://audit-service' @@ -2182,7 +2237,7 @@ FENCE_CONFIG: # None(Default): Allow per client i.e. a fence client can pick whether or not to sync their visas during login with parse_visas param in /authorization endpoint # True: Parse for all clients i.e. a fence client will always sync their visas during login # False: Parse for no clients i.e. a fence client will not be able to sync visas during login even with parse_visas param - GLOBAL_PARSE_VISAS_ON_LOGIN: + GLOBAL_PARSE_VISAS_ON_LOGIN: false # Settings for usersync with visas USERSYNC: sync_from_visas: false diff --git a/helm/gen3/Chart.yaml b/helm/gen3/Chart.yaml index 4fa42c7d..23275c61 100644 --- a/helm/gen3/Chart.yaml +++ b/helm/gen3/Chart.yaml @@ -5,82 +5,90 @@ description: Helm chart to deploy Gen3 Data Commons # Dependancies dependencies: - name: ambassador - version: "0.1.3" + version: 0.1.9 repository: "file://../ambassador" condition: ambassador.enabled - name: arborist - version: "0.1.3" + version: 0.1.9 repository: "file://../arborist" condition: arborist.enabled - name: argo-wrapper - version: "0.1.0" + version: 0.1.5 repository: "file://../argo-wrapper" condition: argo-wrapper.enabled - name: audit - version: "0.1.3" + version: 0.1.10 repository: "file://../audit" condition: audit.enabled - name: aws-es-proxy - version: "0.1.2" + version: 0.1.7 repository: "file://../aws-es-proxy" condition: aws-es-proxy.enabled - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: etl + version: 0.1.0 + repository: file://../etl + condition: etl.enabled - name: fence - version: "0.1.3" + version: 0.1.14 repository: "file://../fence" condition: fence.enabled - name: guppy - version: "0.1.3" + version: 0.1.9 repository: "file://../guppy" condition: guppy.enabled - name: hatchery - version: "0.1.2" + version: 0.1.7 repository: "file://../hatchery" condition: hatchery.enabled - name: indexd - version: "0.1.3" + version: 0.1.11 repository: "file://../indexd" condition: indexd.enabled - name: manifestservice - version: "0.1.2" + version: 0.1.11 repository: "file://../manifestservice" condition: manifestservice.enabled - name: metadata - version: "0.1.3" + version: 0.1.9 repository: "file://../metadata" condition: metadata.enabled - name: peregrine - version: "0.1.3" + version: 0.1.10 repository: "file://../peregrine" condition: peregrine.enabled - name: pidgin - version: "0.1.3" + version: 0.1.8 repository: "file://../pidgin" condition: pidgin.enabled - name: portal - version: "0.1.1" + version: 0.1.8 repository: "file://../portal" condition: portal.enabled - name: requestor - version: "0.1.3" + version: 0.1.9 repository: "file://../requestor" condition: requestor.enabled - name: revproxy - version: "0.1.3" + version: 0.1.12 repository: "file://../revproxy" condition: revproxy.enabled - name: sheepdog - version: "0.1.3" + version: 0.1.11 repository: "file://../sheepdog" condition: sheepdog.enabled - name: ssjdispatcher - version: "0.1.1" + version: 0.1.7 repository: "file://../ssjdispatcher" condition: ssjdispatcher.enabled +- name: sower + version: 0.1.7 + condition: sower.enabled + repository: "file://../sower" - name: wts - version: "0.1.4" + version: 0.1.11 repository: "file://../wts" condition: wts.enabled @@ -99,8 +107,8 @@ dependencies: - name: elasticsearch - version: "0.1.1" - repository: "file://../elasticsearch" + version: 7.10.2 + repository: "https://helm.elastic.co" condition: global.dev - name: postgresql version: 11.9.13 @@ -120,7 +128,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.4 +version: 0.1.21 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm/gen3/README.md b/helm/gen3/README.md index a093724a..220a9cc4 100644 --- a/helm/gen3/README.md +++ b/helm/gen3/README.md @@ -1,6 +1,6 @@ # gen3 -![Version: 0.1.4](https://img.shields.io/badge/Version-0.1.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.21](https://img.shields.io/badge/Version-0.1.21-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) Helm chart to deploy Gen3 Data Commons @@ -18,118 +18,202 @@ Helm chart to deploy Gen3 Data Commons | Repository | Name | Version | |------------|------|---------| -| file://../ambassador | ambassador | 0.1.3 | -| file://../arborist | arborist | 0.1.3 | -| file://../argo-wrapper | argo-wrapper | 0.1.0 | -| file://../audit | audit | 0.1.3 | -| file://../aws-es-proxy | aws-es-proxy | 0.1.2 | -| file://../common | common | 0.1.3 | -| file://../elasticsearch | elasticsearch | 0.1.1 | -| file://../fence | fence | 0.1.3 | -| file://../guppy | guppy | 0.1.3 | -| file://../hatchery | hatchery | 0.1.2 | -| file://../indexd | indexd | 0.1.3 | -| file://../manifestservice | manifestservice | 0.1.2 | -| file://../metadata | metadata | 0.1.3 | -| file://../peregrine | peregrine | 0.1.3 | -| file://../pidgin | pidgin | 0.1.3 | -| file://../portal | portal | 0.1.1 | -| file://../requestor | requestor | 0.1.3 | -| file://../revproxy | revproxy | 0.1.3 | -| file://../sheepdog | sheepdog | 0.1.3 | -| file://../ssjdispatcher | ssjdispatcher | 0.1.1 | -| file://../wts | wts | 0.1.4 | +| file://../ambassador | ambassador | 0.1.9 | +| file://../arborist | arborist | 0.1.9 | +| file://../argo-wrapper | argo-wrapper | 0.1.5 | +| file://../audit | audit | 0.1.10 | +| file://../aws-es-proxy | aws-es-proxy | 0.1.7 | +| file://../common | common | 0.1.8 | +| file://../etl | etl | 0.1.0 | +| file://../fence | fence | 0.1.14 | +| file://../guppy | guppy | 0.1.9 | +| file://../hatchery | hatchery | 0.1.7 | +| file://../indexd | indexd | 0.1.11 | +| file://../manifestservice | manifestservice | 0.1.11 | +| file://../metadata | metadata | 0.1.9 | +| file://../peregrine | peregrine | 0.1.10 | +| file://../pidgin | pidgin | 0.1.8 | +| file://../portal | portal | 0.1.8 | +| file://../requestor | requestor | 0.1.9 | +| file://../revproxy | revproxy | 0.1.12 | +| file://../sheepdog | sheepdog | 0.1.11 | +| file://../sower | sower | 0.1.7 | +| file://../ssjdispatcher | ssjdispatcher | 0.1.7 | +| file://../wts | wts | 0.1.11 | | https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | +| https://helm.elastic.co | elasticsearch | 7.10.2 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| ambassador.enabled | bool | `true` | | -| ambassador.image.repository | string | `nil` | | -| ambassador.image.tag | string | `nil` | | -| arborist.enabled | bool | `true` | | -| arborist.image.repository | string | `nil` | | -| arborist.image.tag | string | `nil` | | -| argo-wrapper.enabled | bool | `true` | | -| argo-wrapper.image.repository | string | `nil` | | -| argo-wrapper.image.tag | string | `nil` | | -| audit.enabled | bool | `true` | | -| audit.image.repository | string | `nil` | | -| audit.image.tag | string | `nil` | | -| aws-es-proxy.enabled | bool | `false` | | -| fence.enabled | bool | `true` | | -| fence.image.repository | string | `nil` | | -| fence.image.tag | string | `nil` | | -| global | map | `{"aws":{"account":{"aws_access_key_id":null,"aws_secret_access_key":null},"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","gcp":true,"hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","tls":{"cert":null,"key":null},"userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | -| global.aws.account | map | `{"aws_access_key_id":null,"aws_secret_access_key":null}` | Credentials for AWS | -| global.aws.enabled | bool | `false` | Set to true if deploying to AWS. Controls ingress annotations. | +| ambassador.enabled | bool | `true` | Whether to deploy the ambassador subchart. | +| ambassador.image.repository | string | `nil` | The Docker image repository for the ambassador service. | +| ambassador.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| arborist.enabled | bool | `true` | Whether to deploy the arborist subchart. | +| arborist.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| arborist.image.repository | string | `nil` | The Docker image repository for the arborist service. | +| arborist.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| argo-wrapper.enabled | bool | `true` | Whether to deploy the argo-wrapper subchart. | +| argo-wrapper.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| argo-wrapper.image.repository | string | `nil` | The Docker image repository for the argo-wrapper service. | +| argo-wrapper.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| audit.enabled | bool | `true` | Whether to deploy the audit subchart. | +| audit.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| audit.image.repository | string | `nil` | The Docker image repository for the audit service. | +| audit.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| aws-es-proxy.enabled | bool | `false` | Whether to deploy the aws-es-proxy subchart. | +| aws-es-proxy.esEndpoint | str | `"test.us-east-1.es.amazonaws.com"` | Elasticsearch endpoint in AWS | +| aws-es-proxy.secrets | map | `{"awsAccessKeyId":"","awsSecretAccessKey":""}` | Secret information | +| aws-es-proxy.secrets.awsAccessKeyId | str | `""` | AWS access key ID for aws-es-proxy | +| aws-es-proxy.secrets.awsSecretAccessKey | str | `""` | AWS secret access key for aws-es-proxy | +| elasticsearch.clusterHealthCheckParams | string | `"wait_for_status=yellow&timeout=1s"` | | +| elasticsearch.clusterName | string | `"gen3-elasticsearch"` | | +| elasticsearch.esConfig."elasticsearch.yml" | string | `"# Here we can add elasticsearch config\n"` | | +| elasticsearch.maxUnavailable | int | `0` | | +| elasticsearch.replicas | int | `1` | | +| elasticsearch.singleNode | bool | `true` | | +| etl.enabled | bool | `true` | Whether to deploy the etl subchart. | +| fence.FENCE_CONFIG | map | `nil` | Configuration settings for Fence app | +| fence.USER_YAML | string | `nil` | USER YAML. Passed in as a multiline string. | +| fence.enabled | bool | `true` | Whether to deploy the fence subchart. | +| fence.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| fence.image.repository | string | `nil` | The Docker image repository for the fence service. | +| fence.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| fence.usersync | map | `{"addDbgap":false,"custom_image":null,"onlyDbgap":false,"schedule":"*/30 * * * *","secrets":{"awsAccessKeyId":"","awsSecretAccessKey":""},"slack_send_dbgap":false,"slack_webhook":"None","syncFromDbgap":false,"userYamlS3Path":"s3://cdis-gen3-users/helm-test/user.yaml","usersync":false}` | Configuration options for usersync cronjob. | +| fence.usersync.addDbgap | bool | `false` | Force attempting a dbgap sync if "true", falls back on user.yaml | +| fence.usersync.custom_image | string | `nil` | To set a custom image for pulling the user.yaml file from S3. Default is the Gen3 Awshelper image. | +| fence.usersync.onlyDbgap | bool | `false` | Forces ONLY a dbgap sync if "true", IGNORING user.yaml | +| fence.usersync.schedule | string | `"*/30 * * * *"` | The cron schedule expression to use in the usersync cronjob. Runs every 30 minutes by default. | +| fence.usersync.secrets | map | `{"awsAccessKeyId":"","awsSecretAccessKey":""}` | Secret information | +| fence.usersync.secrets.awsAccessKeyId | str | `""` | AWS access key ID for usersync S3 bucket | +| fence.usersync.secrets.awsSecretAccessKey | str | `""` | AWS secret access key for usersync S3 bucket | +| fence.usersync.slack_send_dbgap | bool | `false` | Will echo what files we are seeing on dbgap ftp to Slack. | +| fence.usersync.slack_webhook | string | `"None"` | Slack webhook endpoint used with certain jobs. | +| fence.usersync.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | +| fence.usersync.userYamlS3Path | string | `"s3://cdis-gen3-users/helm-test/user.yaml"` | Path to the user.yaml file in S3. | +| fence.usersync.usersync | bool | `false` | Whether to run Fence usersync or not. | +| gitops.createdby | string | `nil` | - createdby.png - base64 | +| gitops.css | string | `nil` | - multiline string - gitops.css | +| gitops.favicon | string | `nil` | - favicon in base64 | +| gitops.json | string | `nil` | multiline string - gitops.json | +| gitops.logo | string | `nil` | - logo in base64 | +| gitops.sponsors | string | `nil` | | +| global.aws | map | `{"enabled":false}` | AWS configuration | | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | -| global.dev | bool | `true` | Whether the deployment is for development purposes. | +| global.dev | bool | `true` | Deploys postgres/elasticsearch for dev | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | | global.dispatcherJobNum | int | `10` | Number of dispatcher jobs. | -| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | -| global.gcp | map | `true` | AWS configuration | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces in same cluster. | | global.hostname | string | `"localhost"` | Hostname for the deployment. | -| global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | -| global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | | global.portalApp | string | `"gitops"` | Portal application name. | -| global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | -| global.postgres.dbCreate | bool | `true` | Whether the database should be created. | -| global.postgres.master | map | `{"host":null,"password":null,"port":"5432","username":"postgres"}` | Master credentials to postgres. This is going to be the default postgres server being used for each service, unless each service specifies their own postgres | -| global.postgres.master.host | string | `nil` | hostname of postgres server | -| global.postgres.master.password | string | `nil` | password for superuser in postgres. This is used to create or restore databases | -| global.postgres.master.port | string | `"5432"` | Port for Postgres. | -| global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | +| global.postgres.dbCreate | bool | `true` | Whether the database create job should run. | +| global.postgres.master.host | string | `nil` | global postgres master host | +| global.postgres.master.password | string | `nil` | global postgres master password | +| global.postgres.master.port | string | `"5432"` | global postgres master port | +| global.postgres.master.username | string | `"postgres"` | global postgres master username | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| guppy.enabled | bool | `false` | | -| guppy.image.repository | string | `nil` | | -| guppy.image.tag | string | `nil` | | -| hatchery.enabled | bool | `true` | | -| hatchery.image.repository | string | `nil` | | -| hatchery.image.tag | string | `nil` | | -| indexd.enabled | bool | `true` | | -| indexd.image.repository | string | `nil` | | -| indexd.image.tag | string | `nil` | | -| manifestservice.enabled | bool | `true` | | -| manifestservice.image.repository | string | `nil` | | -| manifestservice.image.tag | string | `nil` | | -| metadata.enabled | bool | `true` | | -| metadata.image.repository | string | `nil` | | -| metadata.image.tag | string | `nil` | | -| peregrine.enabled | bool | `true` | | -| peregrine.image.repository | string | `nil` | | -| peregrine.image.tag | string | `nil` | | -| pidgin.enabled | bool | `true` | | -| pidgin.image.repository | string | `nil` | | -| pidgin.image.tag | string | `nil` | | -| portal.enabled | bool | `true` | | -| portal.image.repository | string | `nil` | | -| portal.image.tag | string | `nil` | | -| postgresql.primary.persistence.enabled | bool | `false` | | -| requestor.enabled | bool | `false` | | -| requestor.image.repository | string | `nil` | | -| requestor.image.tag | string | `nil` | | -| revproxy.enabled | bool | `true` | | -| revproxy.image.repository | string | `nil` | | -| revproxy.image.tag | string | `nil` | | -| secrets.awsAccessKeyId | string | `"test"` | | -| secrets.awsSecretAccessKey | string | `"test"` | | -| sheepdog.enabled | bool | `true` | | -| sheepdog.image.repository | string | `nil` | | -| sheepdog.image.tag | string | `nil` | | -| ssjdispatcher.enabled | bool | `false` | | -| ssjdispatcher.image.repository | string | `nil` | | -| ssjdispatcher.image.tag | string | `nil` | | -| tags.dev | bool | `false` | | -| wts.enabled | bool | `true` | | -| wts.image.repository | string | `nil` | | -| wts.image.tag | string | `nil` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| global.tierAccessLimit | int | `"1000"` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | +| guppy.enabled | bool | `false` | Whether to deploy the guppy subchart. | +| guppy.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| guppy.image.repository | string | `nil` | The Docker image repository for the guppy service. | +| guppy.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| hatchery.enabled | bool | `true` | Whether to deploy the hatchery subchart. | +| hatchery.hatchery.containers[0].args[0] | string | `"--NotebookApp.base_url=/lw-workspace/proxy/"` | | +| hatchery.hatchery.containers[0].args[1] | string | `"--NotebookApp.default_url=/lab"` | | +| hatchery.hatchery.containers[0].args[2] | string | `"--NotebookApp.password=''"` | | +| hatchery.hatchery.containers[0].args[3] | string | `"--NotebookApp.token=''"` | | +| hatchery.hatchery.containers[0].args[4] | string | `"--NotebookApp.shutdown_no_activity_timeout=5400"` | | +| hatchery.hatchery.containers[0].args[5] | string | `"--NotebookApp.quit_button=False"` | | +| hatchery.hatchery.containers[0].command[0] | string | `"start-notebook.sh"` | | +| hatchery.hatchery.containers[0].cpu-limit | string | `"1.0"` | cpu limit of workspace container | +| hatchery.hatchery.containers[0].env | object | `{"FRAME_ANCESTORS":"https://{{ .Values.global.hostname }}"}` | environment variables for workspace container | +| hatchery.hatchery.containers[0].fs-gid | int | `100` | | +| hatchery.hatchery.containers[0].gen3-volume-location | string | `"/home/jovyan/.gen3"` | | +| hatchery.hatchery.containers[0].image | string | `"quay.io/cdis/heal-notebooks:combined_tutorials__latest"` | docker image for workspace | +| hatchery.hatchery.containers[0].lifecycle-post-start[0] | string | `"/bin/sh"` | | +| hatchery.hatchery.containers[0].lifecycle-post-start[1] | string | `"-c"` | | +| hatchery.hatchery.containers[0].lifecycle-post-start[2] | string | `"export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; ln -s /data /home/$IAM/pd/; true"` | | +| hatchery.hatchery.containers[0].memory-limit | string | `"2Gi"` | memory limit of workspace container | +| hatchery.hatchery.containers[0].name | string | `"(Tutorials) Example Analysis Jupyter Lab Notebooks"` | name of workspace | +| hatchery.hatchery.containers[0].path-rewrite | string | `"/lw-workspace/proxy/"` | | +| hatchery.hatchery.containers[0].ready-probe | string | `"/lw-workspace/proxy/"` | | +| hatchery.hatchery.containers[0].target-port | int | `8888` | port to proxy traffic to in docker contaniner | +| hatchery.hatchery.containers[0].use-tls | string | `"false"` | | +| hatchery.hatchery.containers[0].user-uid | int | `1000` | | +| hatchery.hatchery.containers[0].user-volume-location | string | `"/home/jovyan/pd"` | | +| hatchery.hatchery.sidecarContainer.args | list | `[]` | Arguments to pass to the sidecare container. | +| hatchery.hatchery.sidecarContainer.command | list | `["/bin/bash","./sidecar.sh"]` | Commands to run for the sidecar container. | +| hatchery.hatchery.sidecarContainer.cpu-limit | string | `"0.1"` | The maximum amount of CPU the sidecar container can use | +| hatchery.hatchery.sidecarContainer.env | map | `{"HOSTNAME":"{{ .Values.global.hostname }}","NAMESPACE":"{{ .Release.Namespace }}"}` | Environment variables to pass to the sidecar container | +| hatchery.hatchery.sidecarContainer.image | string | `"quay.io/cdis/ecs-ws-sidecar:master"` | The sidecar image. | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[0] | string | `"su"` | | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[1] | string | `"-c"` | | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[2] | string | `"echo test"` | | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[3] | string | `"-s"` | | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[4] | string | `"/bin/sh"` | | +| hatchery.hatchery.sidecarContainer.lifecycle-pre-stop[5] | string | `"root"` | | +| hatchery.hatchery.sidecarContainer.memory-limit | string | `"256Mi"` | The maximum amount of memory the sidecar container can use | +| hatchery.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| hatchery.image.repository | string | `nil` | The Docker image repository for the hatchery service. | +| hatchery.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| indexd.defaultPrefix | string | `"PREFIX/"` | the default prefix for indexd records | +| indexd.enabled | bool | `true` | Whether to deploy the indexd subchart. | +| indexd.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| indexd.image.repository | string | `nil` | The Docker image repository for the indexd service. | +| indexd.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| manifestservice.enabled | bool | `true` | Whether to deploy the manifest service subchart. | +| manifestservice.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| manifestservice.image.repository | string | `nil` | The Docker image repository for the manifest service service. | +| manifestservice.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| metadata.enabled | bool | `true` | Whether to deploy the metadata subchart. | +| metadata.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| metadata.image.repository | string | `nil` | The Docker image repository for the metadata service. | +| metadata.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| peregrine.enabled | bool | `true` | Whether to deploy the peregrine subchart. | +| peregrine.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| peregrine.image.repository | string | `nil` | The Docker image repository for the peregrine service. | +| peregrine.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| pidgin.enabled | bool | `true` | Whether to deploy the pidgin subchart. | +| pidgin.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| pidgin.image.repository | string | `nil` | The Docker image repository for the pidgin service. | +| pidgin.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| portal.enabled | bool | `true` | Whether to deploy the portal subchart. | +| portal.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| portal.image.repository | string | `nil` | The Docker image repository for the portal service. | +| portal.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| requestor.enabled | bool | `false` | Whether to deploy the requestor subchart. | +| requestor.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| requestor.image.repository | string | `nil` | The Docker image repository for the requestor service. | +| requestor.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| revproxy.enabled | bool | `true` | Whether to deploy the revproxy subchart. | +| revproxy.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| revproxy.image.repository | string | `nil` | The Docker image repository for the revproxy service. | +| revproxy.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| revproxy.ingress.annotations | map | `{}` | Annotations to add to the ingress. | +| revproxy.ingress.className | string | `""` | The ingress class name. | +| revproxy.ingress.enabled | bool | `false` | Whether to create the custom revproxy ingress | +| revproxy.ingress.hosts | list | `[{"host":"chart-example.local"}]` | Where to route the traffic. | +| revproxy.ingress.tls | list | `[]` | To secure an Ingress by specifying a secret that contains a TLS private key and certificate. | +| secrets | map | `{"awsAccessKeyId":"test","awsSecretAccessKey":"test"}` | AWS credentials to access the db restore job S3 bucket | +| secrets.awsAccessKeyId | string | `"test"` | AWS access key. | +| secrets.awsSecretAccessKey | string | `"test"` | AWS secret access key. | +| sheepdog.enabled | bool | `true` | Whether to deploy the sheepdog subchart. | +| sheepdog.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| sheepdog.image.repository | string | `nil` | The Docker image repository for the sheepdog service. | +| sheepdog.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| ssjdispatcher.enabled | bool | `false` | Whether to deploy the ssjdispatcher subchart. | +| ssjdispatcher.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| ssjdispatcher.image.repository | string | `nil` | The Docker image repository for the ssjdispatcher service. | +| ssjdispatcher.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | +| wts.enabled | bool | `true` | Whether to deploy the wts subchart. | +| wts.image | map | `{"repository":null,"tag":null}` | Docker image information. | +| wts.image.repository | string | `nil` | The Docker image repository for the wts service. | +| wts.image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/gen3/ci/portal-values.yaml b/helm/gen3/ci/portal-values.yaml new file mode 100644 index 00000000..7b5bee17 --- /dev/null +++ b/helm/gen3/ci/portal-values.yaml @@ -0,0 +1,9 @@ +portal: + image: + repository: quay.io/cdis/data-portal-prebuilt + tag: "toxcommons.com-master" + + resources: + requests: + cpu: "0.2" + memory: 100Mi diff --git a/helm/gen3/templates/global-manifest.yaml b/helm/gen3/templates/global-manifest.yaml index bdc3950e..945088d5 100644 --- a/helm/gen3/templates/global-manifest.yaml +++ b/helm/gen3/templates/global-manifest.yaml @@ -8,12 +8,9 @@ data: "revproxy_arn": {{ .Values.global.revproxyArn | quote }} "dictionary_url": {{ .Values.global.dictionaryUrl | quote }} "portal_app": {{ .Values.global.portalApp | quote }} - "kube_bucket": {{ .Values.global.kubeBucket | quote }} - "logs_bucket": {{ .Values.global.logsBucket | quote }} - "sync_from_dbgap": {{ .Values.global.syncFromDbgap | quote }} - "useryaml_s3path": {{ .Values.global.userYamlS3Path | quote }} "public_datasets": {{ .Values.global.publicDataSets | quote }} "tier_access_level": {{ .Values.global.tierAccessLevel | quote }} + "tier_access_limit": {{ .Values.global.tierAccessLimit | quote }} "netpolicy": {{ .Values.global.netPolicy | quote }} "dispatcher_job_num": {{ .Values.global.dispatcherJobNum | quote }} "dd_enabled": {{ .Values.global.ddEnabled | quote }} diff --git a/helm/gen3/values.yaml b/helm/gen3/values.yaml index 8a8c6fc2..1eb38ab6 100644 --- a/helm/gen3/values.yaml +++ b/helm/gen3/values.yaml @@ -2,38 +2,33 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -# -- (map) Global configuration options. global: # -- (map) AWS configuration - gcp: true - tls: - cert: - key: aws: - # -- (bool) Set to true if deploying to AWS. Controls ingress annotations. enabled: false - # -- (map) Credentials for AWS - account: - # Prep move of these keys here. - aws_access_key_id: - aws_secret_access_key: - # -- (bool) Whether the deployment is for development purposes. + # # -- (map) Credentials for AWS + # account: + # # Prep move of these keys here. + # aws_access_key_id: + # aws_secret_access_key: + + # -- (bool) Deploys postgres/elasticsearch for dev dev: true - # -- (map) Postgres database configuration. postgres: - # -- (bool) Whether the database should be created. + # -- (bool) Whether the database create job should run. dbCreate: true - # -- (map) Master credentials to postgres. This is going to be the default postgres server being used for each service, unless each service specifies their own postgres master: - # -- (string) hostname of postgres server - host: - # -- (string) username of superuser in postgres. This is used to create or restore databases + # -- global postgres master username username: postgres - # -- (string) password for superuser in postgres. This is used to create or restore databases + # -- global postgres master password password: - # -- (string) Port for Postgres. + # -- global postgres master host + host: + # -- global postgres master port port: "5432" - # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + # -- (string) Environment name. + # This should be the same as vpcname if you're doing an AWS deployment. + # Currently this is being used to share ALB's if you have multiple namespaces in same cluster. environment: default # -- (string) Hostname for the deployment. hostname: localhost @@ -43,18 +38,12 @@ global: dictionaryUrl: https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json # -- (string) Portal application name. portalApp: gitops - # -- (string) S3 bucket name for Kubernetes manifest files. - kubeBucket: kube-gen3 - # -- (string) S3 bucket name for log files. - logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: "1000" # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. @@ -63,130 +52,350 @@ global: ddEnabled: false -tags: - # controls whether or not to deploy postgres/ elasticsearch as local services. - dev: false - # Dependancy Charts + ambassador: + # -- (bool) Whether to deploy the ambassador subchart. enabled: true image: + # -- (string) The Docker image repository for the ambassador service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: arborist: + # -- (bool) Whether to deploy the arborist subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the arborist service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: argo-wrapper: + # -- (bool) Whether to deploy the argo-wrapper subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the argo-wrapper service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: audit: + # -- (bool) Whether to deploy the audit subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the audit service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + aws-es-proxy: + # -- (bool) Whether to deploy the aws-es-proxy subchart. enabled: false + # -- (str) Elasticsearch endpoint in AWS + esEndpoint: test.us-east-1.es.amazonaws.com + # -- (map) Secret information + secrets: + # -- (str) AWS access key ID for aws-es-proxy + awsAccessKeyId: "" + # -- (str) AWS secret access key for aws-es-proxy + awsSecretAccessKey: "" + +etl: + # -- (bool) Whether to deploy the etl subchart. + enabled: true fence: + # -- (bool) Whether to deploy the fence subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the fence service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + # -- (map) Configuration settings for Fence app + FENCE_CONFIG: + # -- (string) USER YAML. Passed in as a multiline string. + USER_YAML: + # -- (map) Configuration options for usersync cronjob. + usersync: + # -- (bool) Whether to run Fence usersync or not. + usersync: false + # -- (map) Secret information + secrets: + # -- (str) AWS access key ID for usersync S3 bucket + awsAccessKeyId: "" + # -- (str) AWS secret access key for usersync S3 bucket + awsSecretAccessKey: "" + # -- (string) The cron schedule expression to use in the usersync cronjob. Runs every 30 minutes by default. + schedule: "*/30 * * * *" + # -- (string) To set a custom image for pulling the user.yaml file from S3. Default is the Gen3 Awshelper image. + custom_image: + # -- (bool) Whether to sync data from dbGaP. + syncFromDbgap: false + # -- (bool) Force attempting a dbgap sync if "true", falls back on user.yaml + addDbgap: false + # -- (bool) Forces ONLY a dbgap sync if "true", IGNORING user.yaml + onlyDbgap: false + # -- (string) Path to the user.yaml file in S3. + userYamlS3Path: s3://cdis-gen3-users/helm-test/user.yaml + # -- (string) Slack webhook endpoint used with certain jobs. + slack_webhook: None + # -- (bool) Will echo what files we are seeing on dbgap ftp to Slack. + slack_send_dbgap: false guppy: + # -- (bool) Whether to deploy the guppy subchart. enabled: false + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the guppy service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: hatchery: + # -- (bool) Whether to deploy the hatchery subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the hatchery service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + hatchery: + sidecarContainer: + # -- (string) The maximum amount of CPU the sidecar container can use + cpu-limit: '0.1' + # -- (string) The maximum amount of memory the sidecar container can use + memory-limit: 256Mi + # -- (string) The sidecar image. + image: quay.io/cdis/ecs-ws-sidecar:master + # -- (map) Environment variables to pass to the sidecar container + env: + NAMESPACE: "{{ .Release.Namespace }}" + HOSTNAME: "{{ .Values.global.hostname }}" + # -- (list) Arguments to pass to the sidecare container. + args: [] + # -- (list) Commands to run for the sidecar container. + command: + - "/bin/bash" + - "./sidecar.sh" + lifecycle-pre-stop: + - su + - "-c" + - echo test + - "-s" + - "/bin/sh" + - root + containers: + - + # -- (int) port to proxy traffic to in docker contaniner + target-port: 8888 + # -- (string) cpu limit of workspace container + cpu-limit: '1.0' + # -- (string) memory limit of workspace container + memory-limit: 2Gi + # -- (string) name of workspace + name: "(Tutorials) Example Analysis Jupyter Lab Notebooks" + # -- (string) docker image for workspace + image: quay.io/cdis/heal-notebooks:combined_tutorials__latest + # -- environment variables for workspace container + env: + FRAME_ANCESTORS: https://{{ .Values.global.hostname }} + args: + - "--NotebookApp.base_url=/lw-workspace/proxy/" + - "--NotebookApp.default_url=/lab" + - "--NotebookApp.password=''" + - "--NotebookApp.token=''" + - "--NotebookApp.shutdown_no_activity_timeout=5400" + - "--NotebookApp.quit_button=False" + command: + - start-notebook.sh + path-rewrite: "/lw-workspace/proxy/" + use-tls: 'false' + ready-probe: "/lw-workspace/proxy/" + lifecycle-post-start: + - "/bin/sh" + - "-c" + - export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; + ln -s /data /home/$IAM/pd/; true + user-uid: 1000 + fs-gid: 100 + user-volume-location: "/home/jovyan/pd" + gen3-volume-location: "/home/jovyan/.gen3" + + indexd: + # -- (bool) Whether to deploy the indexd subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the indexd service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + # -- (string) the default prefix for indexd records + defaultPrefix: "PREFIX/" + manifestservice: + # -- (bool) Whether to deploy the manifest service subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the manifest service service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: metadata: + # -- (bool) Whether to deploy the metadata subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the metadata service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: peregrine: + # -- (bool) Whether to deploy the peregrine subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the peregrine service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: pidgin: + # -- (bool) Whether to deploy the pidgin subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the pidgin service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: portal: + # -- (bool) Whether to deploy the portal subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the portal service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + # -- (map) GitOps configuration for portal + gitops: + # -- (string) multiline string - gitops.json + json: + # -- (string) - favicon in base64 + favicon: + # -- (string) - multiline string - gitops.css + css: + # -- (string) - logo in base64 + logo: + # -- (string) - createdby.png - base64 + createdby: + sponsors: + requestor: + # -- (bool) Whether to deploy the requestor subchart. enabled: false + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the requestor service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: revproxy: + # -- (bool) Whether to deploy the revproxy subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the revproxy service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + ingress: + # -- (bool) Whether to create the custom revproxy ingress + enabled: false + # -- (string) The ingress class name. + className: "" + # -- (map) Annotations to add to the ingress. + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # -- (list) Where to route the traffic. + hosts: + - host: chart-example.local + # -- (list) To secure an Ingress by specifying a secret that contains a TLS private key and certificate. + tls: [] + sheepdog: + # -- (bool) Whether to deploy the sheepdog subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the sheepdog service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: ssjdispatcher: + # -- (bool) Whether to deploy the ssjdispatcher subchart. enabled: false + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the ssjdispatcher service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: + wts: + # -- (bool) Whether to deploy the wts subchart. enabled: true + # -- (map) Docker image information. image: + # -- (string) The Docker image repository for the wts service. repository: + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: -# The db restore job requires AWS credentials to access the S3 bucket +# -- (map) AWS credentials to access the db restore job S3 bucket secrets: + # -- (string) AWS access key. awsAccessKeyId: test + # -- (string) AWS secret access key. awsSecretAccessKey: test -# This is to configure postgresql subchart # Disable persistence by default so we can spin up and down ephemeral environments postgresql: primary: persistence: + # -- (bool) Option to persist the dbs data. enabled: false + +elasticsearch: + clusterName: gen3-elasticsearch + maxUnavailable: 0 + singleNode: true + replicas: 1 + clusterHealthCheckParams: "wait_for_status=yellow&timeout=1s" + esConfig: + elasticsearch.yml: | + # Here we can add elasticsearch config diff --git a/helm/guppy/Chart.yaml b/helm/guppy/Chart.yaml index 851d1a1d..8243c558 100644 --- a/helm/guppy/Chart.yaml +++ b/helm/guppy/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,5 +25,5 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common diff --git a/helm/guppy/README.md b/helm/guppy/README.md index c9ed4c9f..a785aca3 100644 --- a/helm/guppy/README.md +++ b/helm/guppy/README.md @@ -1,6 +1,6 @@ # guppy -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.9](https://img.shields.io/badge/Version-0.1.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Guppy Service @@ -8,41 +8,39 @@ A Helm chart for gen3 Guppy Service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"guppy"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| arboristUrl | string | `"http://arborist-service"` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["guppy"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["guppy"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["guppy"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["guppy"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["guppy"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| arboristUrl | string | `"http://arborist-service"` | Arborist service URL. | | authFilterField | string | `"auth_resource_path"` | The field used for access control and authorization filters | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | | configIndex | string | `"dev_case-array-config"` | The Elasticsearch configuration index | -| containerPort[0].containerPort | int | `8000` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | -| dbRestore | bool | `true` | Whether or not to restore elasticsearch indices from a snapshot in s3 | -| ddEnv | string | `nil` | | -| ddLogsInjection | string | `nil` | | -| ddProfilingEnabled | string | `nil` | | -| ddService | string | `nil` | | -| ddTraceAgentHostname | string | `nil` | | -| ddTraceEnabled | string | `nil` | | -| ddTraceSampleRate | string | `nil` | | -| ddVersion | string | `nil` | | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| dataDog | bool | `{"enabled":false,"env":"dev"}` | Whether Datadog is enabled. | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| dbRestore | bool | `false` | Whether or not to restore elasticsearch indices from a snapshot in s3 | | enableEncryptWhitelist | bool | `true` | Whether or not to enable encryption for specified fields | | encryptWhitelist | string | `"test1"` | A comma-separated list of fields to encrypt | -| esEndpoint | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| esEndpoint | string | `"gen3-elasticsearch-master:9200"` | Elasticsearch endpoint. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":"10","environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre","tierAccessLimit":"1000"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -50,12 +48,14 @@ A Helm chart for gen3 Guppy Service | global.ddEnabled | bool | `false` | Whether Datadog is enabled. | | global.dev | bool | `true` | Whether the deployment is for development purposes. | | global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | -| global.dispatcherJobNum | int | `10` | Number of dispatcher jobs. | +| global.dispatcherJobNum | int | `"10"` | Number of dispatcher jobs. | | global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -66,46 +66,36 @@ A Helm chart for gen3 Guppy Service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | | global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/guppy"` | | -| image.tag | string | `""` | | +| global.tierAccessLimit | int | `"1000"` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/guppy","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/guppy"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | | indices | list | `[{"index":"dev_case","type":"case"},{"index":"dev_file","type":"file"}]` | Elasticsearch index configurations | -| livenessProbe.httpGet.path | string | `"/_status"` | | -| livenessProbe.httpGet.port | int | `8000` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| readinessProbe.httpGet.path | string | `"/_status"` | | -| readinessProbe.httpGet.port | int | `8000` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | int | `1` | | -| resources.limits.memory | string | `"2Gi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"500Mi"` | | -| revisionHistoryLimit | int | `2` | | -| secrets.awsAccessKeyId | string | `nil` | | -| secrets.awsSecretAccessKey | string | `nil` | | -| service.port[0].name | string | `"http"` | | -| service.port[0].port | int | `80` | | -| service.port[0].protocol | string | `"TCP"` | | -| service.port[0].targetPort | int | `8000` | | -| service.type | string | `"ClusterIP"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| tierAccessLevel | string | `"regular"` | | -| tierAccessLimit | int | `1000` | | -| volumeMounts[0].mountPath | string | `"/guppy/guppy_config.json"` | | -| volumeMounts[0].name | string | `"guppy-config"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"guppy_config.json"` | | -| volumes[0].configMap.items[0].key | string | `"guppy_config.json"` | | -| volumes[0].configMap.items[0].path | string | `"guppy_config.json"` | | -| volumes[0].configMap.name | string | `"manifest-guppy"` | | -| volumes[0].name | string | `"guppy-config"` | | +| partOf | string | `"Explorer-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":0.1,"memory":"500Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"2Gi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"2Gi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"500Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"500Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| secrets | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null}` | AWS credentials to access the db restore job S3 bucket | +| secrets.awsAccessKeyId | string | `nil` | AWS access key. | +| secrets.awsSecretAccessKey | string | `nil` | AWS secret access key. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":[{"name":"http","port":80,"protocol":"TCP","targetPort":8000}],"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `[{"name":"http","port":80,"protocol":"TCP","targetPort":8000}]` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| volumeMounts | list | `[{"mountPath":"/guppy/guppy_config.json","name":"guppy-config","readOnly":true,"subPath":"guppy_config.json"}]` | Volumes to mount to the container. | +| volumes | list | `[{"configMap":{"items":[{"key":"guppy_config.json","path":"guppy_config.json"}],"name":"manifest-guppy"},"name":"guppy-config"}]` | Volumes to attach to the pod. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/guppy/templates/_helpers.tpl b/helm/guppy/templates/_helpers.tpl index 82776b04..751dc6a7 100644 --- a/helm/guppy/templates/_helpers.tpl +++ b/helm/guppy/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "guppy.labels" -}} -helm.sh/chart: {{ include "guppy.chart" . }} -{{ include "guppy.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "guppy.selectorLabels" -}} -app.kubernetes.io/name: {{ include "guppy.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "guppy.name" . }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -61,25 +66,3 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} - -{{/* -Define tierAccessLevel -*/}} -{{- define "guppy.tierAccessLevel" -}} -{{- if .Values.global }} -{{- .Values.global.tierAccessLevel }} -{{- else}} -{{- .Values.tierAccessLevel }} -{{- end }} -{{- end }} - -{{/* -Define ddEnabled -*/}} -{{- define "guppy.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/helm/guppy/templates/deployment.yaml b/helm/guppy/templates/deployment.yaml index 22717ced..8a1e16d8 100644 --- a/helm/guppy/templates/deployment.yaml +++ b/helm/guppy/templates/deployment.yaml @@ -2,6 +2,11 @@ apiVersion: apps/v1 kind: Deployment metadata: name: guppy-deployment + labels: + {{- include "guppy.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -17,16 +22,13 @@ spec: template: metadata: labels: + {{- include "guppy.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' - {{- if eq (include "guppy.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "guppy" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "guppy.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -40,69 +42,38 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.readinessProbe }} + httpGet: + path: /_status + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.containerPort}} + readinessProbe: + httpGet: + path: /_status + port: 8000 ports: - {{- toYaml . | nindent 10}} - {{- end }} + - containerPort: 8000 env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: GUPPY_PORT value: "8000" - name: GUPPY_CONFIG_FILEPATH value: /guppy/guppy_config.json - name: GEN3_ES_ENDPOINT - value: {{ default "elasticsearch:9200" .Values.esEndpoint }} + value: {{ default "gen3-elasticsearch-master:9200" .Values.esEndpoint }} {{- with .Values.arboristUrl }} - name: GEN3_ARBORIST_ENDPOINT value: {{ . }} {{- end }} - name: TIER_ACCESS_LEVEL - value: {{ include "guppy.tierAccessLevel" . }} - {{- with .Values.tierAccessLimit }} + value: {{ .Values.global.tierAccessLevel }} - name: TIER_ACCESS_LIMIT - value: {{ . | quote }} - {{- end }} - {{- with .Values.ddTraceEnabled }} - - name: DD_TRACE_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddEnv }} - - name: DD_ENV - value: {{ . }} - {{- end }} - {{- with .Values.ddService }} - - name: DD_SERVICE - value: {{ . }} - {{- end }} - {{- with .Values.ddVersion }} - - name: DD_VERSION - value: {{ . }} - {{- end }} - {{- with .Values.ddLogsInjection }} - - name: DD_LOGS_INJECTION - value: {{ . }} - {{- end }} - {{- with .Values.ddProfilingEnabled }} - - name: DD_PROFILING_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceSampleRate }} - - name: DD_TRACE_SAMPLE_RATE - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceAgentHostname }} - - name: DD_TRACE_AGENT_HOSTNAME - value: {{ . }} - {{- end }} - - + value: {{ .Values.global.tierAccessLimit | quote }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/guppy/templates/pdb.yaml b/helm/guppy/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/guppy/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/guppy/values.yaml b/helm/guppy/values.yaml index b11d671b..a8465f80 100644 --- a/helm/guppy/values.yaml +++ b/helm/guppy/values.yaml @@ -42,60 +42,82 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true # -- (string) Access level for tiers. tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: "1000" # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. - dispatcherJobNum: 10 + dispatcherJobNum: "10" # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 - -# Deployment +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (map) AWS credentials to access the db restore job S3 bucket secrets: + # -- (string) AWS access key. awsAccessKeyId: + # -- (string) AWS secret access key. awsSecretAccessKey: +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 +# -- (bool) Whether Datadog is enabled. dataDog: enabled: false env: dev +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - guppy + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (list) Volumes to attach to the pod. volumes: - name: guppy-config configMap: @@ -104,68 +126,53 @@ volumes: - key: guppy_config.json path: guppy_config.json +# -- (bool) Automount the default service account token automountServiceAccountToken: false +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/guppy + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" # Environment Variables -esEndpoint: "" +# -- (string) Elasticsearch endpoint. +esEndpoint: "gen3-elasticsearch-master:9200" +# -- (string) Arborist service URL. arboristUrl: http://arborist-service -tierAccessLevel: regular -# acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` -tierAccessLimit: 1000 -# Placeholders for datadog -ddTraceEnabled: -ddEnv: -ddService: -ddVersion: -ddLogsInjection: -ddProfilingEnabled: -ddTraceSampleRate: -ddTraceAgentHostname: - - -livenessProbe: - httpGet: - path: /_status - port: 8000 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 -readinessProbe: - httpGet: - path: /_status - port: 8000 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 - -containerPort: - - containerPort: 8000 +# -- (list) Volumes to mount to the container. volumeMounts: - name: guppy-config readOnly: true mountPath: /guppy/guppy_config.json subPath: guppy_config.json +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 500Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1 + # -- (string) The maximum amount of memory the container can use memory: 2Gi -# Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: - protocol: TCP port: 80 @@ -189,4 +196,24 @@ enableEncryptWhitelist: true encryptWhitelist: test1 # -- (bool) Whether or not to restore elasticsearch indices from a snapshot in s3 -dbRestore: true +dbRestore: false + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Explorer-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/hatchery/Chart.yaml b/helm/hatchery/Chart.yaml index b37e71d6..bb311c95 100644 --- a/helm/hatchery/Chart.yaml +++ b/helm/hatchery/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 +version: 0.1.7 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/hatchery/README.md b/helm/hatchery/README.md index b48e16c8..4e7bfe3c 100644 --- a/helm/hatchery/README.md +++ b/helm/hatchery/README.md @@ -1,24 +1,33 @@ # hatchery -![Version: 0.1.2](https://img.shields.io/badge/Version-0.1.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.7](https://img.shields.io/badge/Version-0.1.7-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Hatchery +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| env[0].name | string | `"HTTP_PORT"` | | -| env[0].value | string | `"8000"` | | -| env[1].name | string | `"POD_NAMESPACE"` | | -| env[1].valueFrom.fieldRef.fieldPath | string | `"metadata.namespace"` | | -| fullnameOverride | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{}` | Affinity to use for the deployment. | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| env | list | `[{"name":"HTTP_PORT","value":"8000"},{"name":"POD_NAMESPACE","valueFrom":{"fieldRef":{"fieldPath":"metadata.namespace"}}}]` | Environment variables to pass to the container | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -31,7 +40,9 @@ A Helm chart for gen3 Hatchery | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -42,66 +53,39 @@ A Helm chart for gen3 Hatchery | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| hatchery.containers[0].args[0] | string | `"--NotebookApp.base_url=/lw-workspace/proxy/"` | | -| hatchery.containers[0].args[1] | string | `"--NotebookApp.default_url=/lab"` | | -| hatchery.containers[0].args[2] | string | `"--NotebookApp.password=''"` | | -| hatchery.containers[0].args[3] | string | `"--NotebookApp.token=''"` | | -| hatchery.containers[0].args[4] | string | `"--NotebookApp.shutdown_no_activity_timeout=5400"` | | -| hatchery.containers[0].args[5] | string | `"--NotebookApp.quit_button=False"` | | -| hatchery.containers[0].command[0] | string | `"start-notebook.sh"` | | -| hatchery.containers[0].cpu-limit | string | `"1.0"` | | -| hatchery.containers[0].env.FRAME_ANCESTORS | string | `"https://{{ .Values.global.hostname }}"` | | -| hatchery.containers[0].fs-gid | int | `100` | | -| hatchery.containers[0].gen3-volume-location | string | `"/home/jovyan/.gen3"` | | -| hatchery.containers[0].image | string | `"quay.io/cdis/heal-notebooks:combined_tutorials__latest"` | | -| hatchery.containers[0].lifecycle-post-start[0] | string | `"/bin/sh"` | | -| hatchery.containers[0].lifecycle-post-start[1] | string | `"-c"` | | -| hatchery.containers[0].lifecycle-post-start[2] | string | `"export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; ln -s /data /home/$IAM/pd/; true"` | | -| hatchery.containers[0].memory-limit | string | `"2Gi"` | | -| hatchery.containers[0].name | string | `"(Tutorials) Example Analysis Jupyter Lab Notebooks"` | | -| hatchery.containers[0].path-rewrite | string | `"/lw-workspace/proxy/"` | | -| hatchery.containers[0].ready-probe | string | `"/lw-workspace/proxy/"` | | -| hatchery.containers[0].target-port | int | `8888` | | -| hatchery.containers[0].use-tls | string | `"false"` | | -| hatchery.containers[0].user-uid | int | `1000` | | -| hatchery.containers[0].user-volume-location | string | `"/home/jovyan/pd"` | | -| hatchery.sidecarContainer.args | list | `[]` | | -| hatchery.sidecarContainer.command[0] | string | `"/bin/bash"` | | -| hatchery.sidecarContainer.command[1] | string | `"./sidecar.sh"` | | -| hatchery.sidecarContainer.cpu-limit | string | `"0.1"` | | -| hatchery.sidecarContainer.env.HOSTNAME | string | `"{{ .Values.global.hostname }}"` | | -| hatchery.sidecarContainer.env.NAMESPACE | string | `"{{ .Release.Namespace }}"` | | -| hatchery.sidecarContainer.image | string | `"quay.io/cdis/ecs-ws-sidecar:master"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[0] | string | `"su"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[1] | string | `"-c"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[2] | string | `"echo test"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[3] | string | `"-s"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[4] | string | `"/bin/sh"` | | -| hatchery.sidecarContainer.lifecycle-pre-stop[5] | string | `"root"` | | -| hatchery.sidecarContainer.memory-limit | string | `"256Mi"` | | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"quay.io/cdis/hatchery"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| tolerations | list | `[]` | | -| volumeMounts[0].mountPath | string | `"/hatchery.json"` | | -| volumeMounts[0].name | string | `"hatchery-config"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"json"` | | -| volumes[0].configMap.name | string | `"manifest-hatchery"` | | -| volumes[0].name | string | `"hatchery-config"` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| hatchery | map | `{"containers":[{"args":["--NotebookApp.base_url=/lw-workspace/proxy/","--NotebookApp.default_url=/lab","--NotebookApp.password=''","--NotebookApp.token=''","--NotebookApp.shutdown_no_activity_timeout=5400","--NotebookApp.quit_button=False"],"command":["start-notebook.sh"],"cpu-limit":"1.0","env":{"FRAME_ANCESTORS":"https://{{ .Values.global.hostname }}"},"fs-gid":100,"gen3-volume-location":"/home/jovyan/.gen3","image":"quay.io/cdis/heal-notebooks:combined_tutorials__latest","lifecycle-post-start":["/bin/sh","-c","export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; ln -s /data /home/$IAM/pd/; true"],"memory-limit":"2Gi","name":"(Tutorials) Example Analysis Jupyter Lab Notebooks","path-rewrite":"/lw-workspace/proxy/","ready-probe":"/lw-workspace/proxy/","target-port":8888,"use-tls":"false","user-uid":1000,"user-volume-location":"/home/jovyan/pd"}],"sidecarContainer":{"args":[],"command":["/bin/bash","./sidecar.sh"],"cpu-limit":"0.1","env":{"HOSTNAME":"{{ .Values.global.hostname }}","NAMESPACE":"{{ .Release.Namespace }}"},"image":"quay.io/cdis/ecs-ws-sidecar:master","lifecycle-pre-stop":["su","-c","echo test","-s","/bin/sh","root"],"memory-limit":"256Mi"}}` | Hatchery sidcar container configuration. | +| hatchery.sidecarContainer.args | list | `[]` | Arguments to pass to the sidecare container. | +| hatchery.sidecarContainer.command | list | `["/bin/bash","./sidecar.sh"]` | Commands to run for the sidecar container. | +| hatchery.sidecarContainer.cpu-limit | string | `"0.1"` | The maximum amount of CPU the sidecar container can use | +| hatchery.sidecarContainer.env | map | `{"HOSTNAME":"{{ .Values.global.hostname }}","NAMESPACE":"{{ .Release.Namespace }}"}` | Environment variables to pass to the sidecar container | +| hatchery.sidecarContainer.image | string | `"quay.io/cdis/ecs-ws-sidecar:master"` | The sidecar image. | +| hatchery.sidecarContainer.lifecycle-pre-stop | list | `["su","-c","echo test","-s","/bin/sh","root"]` | Commands that are run before the container is stopped. | +| hatchery.sidecarContainer.memory-limit | string | `"256Mi"` | The maximum amount of memory the sidecar container can use | +| image | map | `{"pullPolicy":"IfNotPresent","repository":"quay.io/cdis/hatchery","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"IfNotPresent"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/hatchery"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node selector labels. | +| partOf | string | `"Workspace-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of cpu the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| tolerations | list | `[]` | Tolerations to use for the deployment. | +| volumeMounts | list | `[{"mountPath":"/hatchery.json","name":"hatchery-config","readOnly":true,"subPath":"json"}]` | Volumes to mount to the container. | +| volumes | list | `[{"configMap":{"name":"manifest-hatchery"},"name":"hatchery-config"}]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/hatchery/templates/_helpers.tpl b/helm/hatchery/templates/_helpers.tpl index 7ea986c8..03655ba2 100644 --- a/helm/hatchery/templates/_helpers.tpl +++ b/helm/hatchery/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "hatchery.labels" -}} -helm.sh/chart: {{ include "hatchery.chart" . }} -{{ include "hatchery.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "hatchery.selectorLabels" -}} -app.kubernetes.io/name: {{ include "hatchery.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/hatchery/templates/deployment.yaml b/helm/hatchery/templates/deployment.yaml index 40d02752..4e9401e2 100644 --- a/helm/hatchery/templates/deployment.yaml +++ b/helm/hatchery/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: hatchery-deployment labels: {{- include "hatchery.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -19,6 +22,9 @@ spec: {{- end }} labels: {{- include "hatchery.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -50,6 +56,9 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- toYaml .Values.env | nindent 12 }} - name: GEN3_ENDPOINT value: {{ .Values.global.hostname }} diff --git a/helm/hatchery/templates/pdb.yaml b/helm/hatchery/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/hatchery/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/hatchery/templates/tests/test-connection.yaml b/helm/hatchery/templates/tests/test-connection.yaml index 606b6519..141c6c25 100644 --- a/helm/hatchery/templates/tests/test-connection.yaml +++ b/helm/hatchery/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "hatchery.fullname" . }}-test-connection" + name: "hatchery-test-connection" labels: {{- include "hatchery.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "hatchery.fullname" . }}:{{ .Values.service.port }}'] + args: ['hatchery-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/hatchery/values.yaml b/helm/hatchery/values.yaml index 0daf5f7e..8916bb2d 100644 --- a/helm/hatchery/values.yaml +++ b/helm/hatchery/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,46 +52,76 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 - +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/hatchery + # -- (string) Docker pull policy. pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" -fullnameOverride: "" +# -- (string) Override the full name of the deployment. +fullnameOverride: "" +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of cpu the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled or not enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node selector labels. nodeSelector: {} +# -- (list) Tolerations to use for the deployment. tolerations: [] +# -- (map) Affinity to use for the deployment. affinity: {} +# -- (list) Environment variables to pass to the container env: - name: HTTP_PORT value: "8000" @@ -104,31 +130,39 @@ env: fieldRef: fieldPath: metadata.namespace - +# -- (list) Volumes to attach to the container. volumes: - name: hatchery-config configMap: name: manifest-hatchery +# -- (list) Volumes to mount to the container. volumeMounts: - name: hatchery-config readOnly: true mountPath: /hatchery.json subPath: json - +# -- (map) Hatchery sidcar container configuration. hatchery: sidecarContainer: + # -- (string) The maximum amount of CPU the sidecar container can use cpu-limit: '0.1' + # -- (string) The maximum amount of memory the sidecar container can use memory-limit: 256Mi + # -- (string) The sidecar image. image: quay.io/cdis/ecs-ws-sidecar:master + # -- (map) Environment variables to pass to the sidecar container env: NAMESPACE: "{{ .Release.Namespace }}" HOSTNAME: "{{ .Values.global.hostname }}" + # -- (list) Arguments to pass to the sidecare container. args: [] + # -- (list) Commands to run for the sidecar container. command: - "/bin/bash" - "./sidecar.sh" + # -- (list) Commands that are run before the container is stopped. lifecycle-pre-stop: - su - "-c" @@ -137,6 +171,7 @@ hatchery: - "/bin/sh" - root +# -- (list) Notebook configuration. containers: - target-port: 8888 cpu-limit: '300m' @@ -166,3 +201,23 @@ hatchery: fs-gid: 100 user-volume-location: "/home/jovyan/pd" gen3-volume-location: "/home/jovyan/.gen3" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Workspace-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/indexd/Chart.yaml b/helm/indexd/Chart.yaml index 08f97802..c7b2b223 100644 --- a/helm/indexd/Chart.yaml +++ b/helm/indexd/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.11 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -26,5 +26,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/indexd/README.md b/helm/indexd/README.md index e6dc596b..353637e2 100644 --- a/helm/indexd/README.md +++ b/helm/indexd/README.md @@ -1,6 +1,6 @@ # indexd -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.11](https://img.shields.io/badge/Version-0.1.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 indexd @@ -8,21 +8,27 @@ A Helm chart for gen3 indexd | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| env[0].name | string | `"GEN3_DEBUG"` | | -| env[0].value | string | `"False"` | | -| fullnameOverride | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{}` | Affinity to use for the deployment. | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Autoscaling options. | +| autoscaling.maxReplicas | int | `100` | Maximum number of replicas | +| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| defaultPrefix | string | `"PREFIX/"` | default prefix for indexd | +| env | list | `[{"name":"ARBORIST","value":"true"},{"name":"GEN3_DEBUG","value":"False"}]` | Environment variables to pass to the container | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre","tierAccessLimit":1000}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -35,7 +41,9 @@ A Helm chart for gen3 indexd | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -46,47 +54,51 @@ A Helm chart for gen3 indexd | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"quay.io/cdis/indexd"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| global.tierAccessLimit | int | `1000` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | +| image | map | `{"pullPolicy":"IfNotPresent","repository":"quay.io/cdis/indexd","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"IfNotPresent"` | When to pull the image. | +| image.repository | string | `"quay.io/cdis/indexd"` | The Docker image repository for the indexd service | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"S3-GS"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{}` | Annotations to add to the pod | +| podSecurityContext | map | `{}` | Security context for the pod | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| secrets.userdb.fence | string | `"test"` | | -| secrets.userdb.gateway | string | `nil` | | -| secrets.userdb.gdcapi | string | `nil` | | -| securityContext | object | `{}` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `false` | | -| serviceAccount.name | string | `""` | | -| tolerations | list | `[]` | | -| volumeMounts[0].mountPath | string | `"/var/www/indexd/local_settings.py"` | | -| volumeMounts[0].name | string | `"config-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"local_settings.py"` | | -| volumes[0].name | string | `"config-volume"` | | -| volumes[0].secret.secretName | string | `"indexd-settings"` | | -| volumes[1].name | string | `"creds-volume"` | | -| volumes[1].secret.secretName | string | `"indexd-creds"` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of desired replicas | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| secrets | map | `{"userdb":{"fence":null,"sheepdog":null}}` | Values for indexd secret. | +| securityContext | map | `{}` | Security context for the containers in the pod | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":false,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `false` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account | +| tolerations | list | `[]` | Tolerations for the pods | +| uwsgi | map | `{"listen":1024}` | Values for overriding uwsgi settings | +| volumeMounts | list | `[{"mountPath":"/var/www/indexd/local_settings.py","name":"config-volume","readOnly":true,"subPath":"local_settings.py"}]` | Volumes to mount to the container. | +| volumes | list | `[{"configMap":{"name":"indexd-uwsgi"},"name":"uwsgi-config"},{"name":"config-volume","secret":{"secretName":"indexd-settings"}}]` | Volumes to attach to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/indexd/indexd-settings/local_settings.py b/helm/indexd/indexd-settings/local_settings.py index cc30e482..42d74247 100644 --- a/helm/indexd/indexd-settings/local_settings.py +++ b/helm/indexd/indexd-settings/local_settings.py @@ -17,7 +17,7 @@ # TODO: FIX THIS TO READ FROM ENV VARS index_config = { - "DEFAULT_PREFIX": environ.get("DEFAULT_PREFIX", "testprefix"), + "DEFAULT_PREFIX": environ.get("DEFAULT_PREFIX", "testprefix/"), "PREPEND_PREFIX": environ.get("PREPEND_PREFIX", True), } diff --git a/helm/indexd/templates/_helpers.tpl b/helm/indexd/templates/_helpers.tpl index dcb848be..0f83473c 100644 --- a/helm/indexd/templates/_helpers.tpl +++ b/helm/indexd/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "indexd.labels" -}} -helm.sh/chart: {{ include "indexd.chart" . }} -{{ include "indexd.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "indexd.selectorLabels" -}} -app.kubernetes.io/name: {{ include "indexd.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -75,9 +81,6 @@ Create the name of the service account to use {{- end }} -# fence: {{ default (randAlphaNum 32) .Values.secrets.userdb.fence | quote }}, -# gdcapi: {{ default (randAlphaNum 32) .Values.secrets.userdb.gdcapi | quote }}, -# gateway: {{ default (randAlphaNum 32) .Values.secrets.userdb.gateway | quote }} {{/* Indexd Fence Creds diff --git a/helm/indexd/templates/deployment.yaml b/helm/indexd/templates/deployment.yaml index 901fec52..931a93fc 100644 --- a/helm/indexd/templates/deployment.yaml +++ b/helm/indexd/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: indexd-deployment labels: {{- include "indexd.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -19,6 +22,9 @@ spec: {{- end }} labels: {{- include "indexd.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.volumes }} volumes: @@ -45,6 +51,9 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: PGHOST valueFrom: secretKeyRef: @@ -75,12 +84,13 @@ spec: name: indexd-dbcreds key: dbcreated optional: false - - name: ARBORIST - value: "False" - - name: HOSTNAME - value: "https://heal.krum.app/" + - name: DEFAULT_PREFIX + value: {{ .Values.defaultPrefix }} {{- toYaml .Values.env | nindent 12 }} volumeMounts: + - name: "uwsgi-config" + mountPath: "/etc/uwsgi/uwsgi.ini" + subPath: uwsgi.ini - name: "config-volume" readOnly: true mountPath: "/var/www/indexd/local_settings.py" diff --git a/helm/indexd/templates/indexd-secret.yaml b/helm/indexd/templates/indexd-secret.yaml index 79034c5a..7c7ca648 100644 --- a/helm/indexd/templates/indexd-secret.yaml +++ b/helm/indexd/templates/indexd-secret.yaml @@ -8,32 +8,9 @@ data: --- apiVersion: v1 kind: Secret -metadata: - name: indexd-creds -type: Opaque -stringData: - creds.json: |- - { - "db_host": "{{ include "gen3.service-postgres" (dict "key" "host" "service" $.Chart.Name "context" $) }}", - "db_username": "{{include "gen3.service-postgres" (dict "key" "username" "service" $.Chart.Name "context" $) }}", - "db_password": "{{include "gen3.service-postgres" (dict "key" "password" "service" $.Chart.Name "context" $) }}", - "db_database": "{{ include "gen3.service-postgres" (dict "key" "database" "service" $.Chart.Name "context" $)}}", - "indexd_password": "test", - "user_db": { - "fence": {{ include "indexd-fence-creds" . | quote }}, - "gdcapi": {{ include "indexd-sheepdog-creds" . | quote }}, - "gateway": {{ include "indexd-gateway-creds" . | quote }} - } - } ---- -apiVersion: v1 -kind: Secret metadata: name: indexd-service-creds type: Opaque -stringData: +data: fence: {{ include "common.getOrGenSecret" (list .Values.secrets.userdb.fence "indexd-service-creds" "fence" 20 .Release.Namespace) }} - gdcapi: {{ include "common.getOrGenSecret" (list .Values.secrets.userdb.gdcapi "indexd-service-creds" "gdcapi" 20 .Release.Namespace) }} - gateway: {{ include "common.getOrGenSecret" (list .Values.secrets.userdb.gateway "indexd-service-creds" "gateway" 20 .Release.Namespace) }} - - + sheepdog: {{ include "common.getOrGenSecret" (list .Values.secrets.userdb.sheepdog "indexd-service-creds" "sheepdog" 20 .Release.Namespace) }} diff --git a/helm/indexd/templates/pdb.yaml b/helm/indexd/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/indexd/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/indexd/templates/pre-install.yaml b/helm/indexd/templates/pre-install.yaml index bd3b7b1e..a6f6cd9f 100644 --- a/helm/indexd/templates/pre-install.yaml +++ b/helm/indexd/templates/pre-install.yaml @@ -19,6 +19,11 @@ spec: volumes: {{- toYaml . | nindent 8 }} {{- end }} + initContainers: + - name: wait-for-indexd + image: curlimages/curl:latest + command: ["/bin/sh","-c"] + args: ["while [ $(curl -sw '%{http_code}' http://indexd-service/index -o /dev/null) -ne 200 ]; do sleep 5; echo 'Waiting for indexd...'; done"] containers: - name: indexd image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" @@ -68,7 +73,7 @@ spec: valueFrom: secretKeyRef: name: indexd-service-creds - key: fence + key: sheepdog optional: false imagePullPolicy: Always command: ["/bin/bash" ] diff --git a/helm/indexd/templates/tests/test-connection.yaml b/helm/indexd/templates/tests/test-connection.yaml index a33cf51f..fc5d3935 100644 --- a/helm/indexd/templates/tests/test-connection.yaml +++ b/helm/indexd/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "indexd.fullname" . }}-test-connection" + name: "indexd-test-connection" labels: {{- include "indexd.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "indexd.fullname" . }}:{{ .Values.service.port }}'] + args: ['indexd-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/indexd/templates/uwsgi.yaml b/helm/indexd/templates/uwsgi.yaml new file mode 100644 index 00000000..a6eef58c --- /dev/null +++ b/helm/indexd/templates/uwsgi.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: indexd-uwsgi +data: + uwsgi.ini: | + [uwsgi] + protocol = uwsgi + socket = /var/run/gen3/uwsgi.sock + buffer-size = 32768 + uid = nginx + gid = nginx + chown-socket = nginx:nginx + chmod-socket = 666 + master = true + harakiri-verbose = true + # No global HARAKIRI, using only user HARAKIRI, because export overwrites it + # Cannot overwrite global HARAKIRI with user's: https://git.io/fjYuD + # harakiri = 45 + ; If VIRTUAL_ENV is set then use its value to specify the virtualenv directory + if-env = VIRTUAL_ENV + virtualenv = %(_) + endif = + http-timeout = 45 + socket-timeout = 45 + worker-reload-mercy = 45 + reload-mercy = 45 + mule-reload-mercy = 45 + disable-logging = true + wsgi-file=/indexd/wsgi.py + plugins = python3 + vacuum = true + pythonpath = /indexd/ + stats = 127.0.0.1:9191 + stats-http = true + env = prometheus_multiproc_dir=/var/tmp/uwsgi_flask_metrics + exec-asap = /indexd/clear_prometheus_multiproc /var/tmp/uwsgi_flask_metrics + # Initialize application in worker processes, not master. This prevents the + # workers from all trying to open the same database connections at startup. + lazy = true + lazy-apps = true + listen = {{ .Values.uwsgi.listen }} diff --git a/helm/indexd/values.yaml b/helm/indexd/values.yaml index f5ed2166..cf2937cf 100644 --- a/helm/indexd/values.yaml +++ b/helm/indexd/values.yaml @@ -42,20 +42,22 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: 1000 # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -73,33 +75,56 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: - + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false + +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false + +# -- (int) Number of desired replicas replicaCount: 1 +# -- (map) Docker image information. image: + # -- (string) The Docker image repository for the indexd service repository: quay.io/cdis/indexd + # -- (string) When to pull the image. pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: false - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template + # -- (string) The name of the service account name: "" +# -- (map) Annotations to add to the pod podAnnotations: {} +# -- (map) Security context for the pod podSecurityContext: {} # fsGroup: 2000 +# -- (map) Security context for the containers in the pod securityContext: {} # capabilities: # drop: @@ -108,54 +133,103 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 - +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Autoscaling options. autoscaling: enabled: false + # -- (int) Minimum number of replicas minReplicas: 1 + # -- (int) Maximum number of replicas maxReplicas: 100 + # -- (int) Target CPU utilization percentage targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node Selector for the pods nodeSelector: {} +# -- (list) Tolerations for the pods tolerations: [] +# -- (map) Affinity to use for the deployment. affinity: {} - +# -- (list) Volumes to attach to the pod volumes: +- name: uwsgi-config + configMap: + name: indexd-uwsgi - name: config-volume secret: secretName: "indexd-settings" -- name: creds-volume - secret: - secretName: "indexd-creds" - +# -- (list) Volumes to mount to the container. volumeMounts: - name: "config-volume" readOnly: true mountPath: "/var/www/indexd/local_settings.py" subPath: "local_settings.py" +# -- (list) Environment variables to pass to the container env: - - name: "GEN3_DEBUG" + - name: ARBORIST + value: "true" + - name: GEN3_DEBUG value: "False" + - name: HOSTNAME + value: "https://heal.krum.app/" +# -- (map) Values for indexd secret. secrets: userdb: - fence: test - gdcapi: - gateway: + fence: + sheepdog: + # gateway: + +# -- (map) Values for overriding uwsgi settings +uwsgi: + listen: 1024 + +# -- (string) default prefix for indexd +defaultPrefix: "PREFIX/" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "S3-GS" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/manifestservice/Chart.yaml b/helm/manifestservice/Chart.yaml index b3963629..3b5eee3a 100644 --- a/helm/manifestservice/Chart.yaml +++ b/helm/manifestservice/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 +version: 0.1.11 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/manifestservice/README.md b/helm/manifestservice/README.md index 60340744..501ee639 100644 --- a/helm/manifestservice/README.md +++ b/helm/manifestservice/README.md @@ -1,58 +1,77 @@ # manifestservice -![Version: 0.1.2](https://img.shields.io/badge/Version-0.1.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.11](https://img.shields.io/badge/Version-0.1.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for Kubernetes +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"manifestservice"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| env[0].name | string | `"REQUESTS_CA_BUNDLE"` | | -| env[0].value | string | `"/etc/ssl/certs/ca-certificates.crt"` | | -| env[1].name | string | `"MANIFEST_SERVICE_CONFIG_PATH"` | | -| env[1].value | string | `"/var/gen3/config/config.json"` | | -| env[2].name | string | `"GEN3_DEBUG"` | | -| env[2].value | string | `"False"` | | -| labels.public | string | `"yes"` | | -| labels.s3 | string | `"yes"` | | -| labels.userhelper | string | `"yes"` | | -| manifestserviceG3auto.awsaccesskey | string | `""` | | -| manifestserviceG3auto.awssecretkey | string | `""` | | -| manifestserviceG3auto.bucketName | string | `"testbucket"` | | -| manifestserviceG3auto.hostname | string | `"testinstall"` | | -| manifestserviceG3auto.prefix | string | `"test"` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| revisionHistoryLimit | int | `2` | | -| selectorLabels.app | string | `"manifestservice"` | | -| selectorLabels.release | string | `"production"` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| terminationGracePeriodSeconds | int | `50` | | -| volumeMounts[0].mountPath | string | `"/var/gen3/config/"` | | -| volumeMounts[0].name | string | `"config-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumes[0].name | string | `"config-volume"` | | -| volumes[0].secret.secretName | string | `"manifestservice-g3auto"` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["manifestservice"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["manifestservice"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["manifestservice"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["manifestservice"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["manifestservice"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| env | list | `[{"name":"REQUESTS_CA_BUNDLE","value":"/etc/ssl/certs/ca-certificates.crt"},{"name":"MANIFEST_SERVICE_CONFIG_PATH","value":"/var/gen3/config/config.json"},{"name":"GEN3_DEBUG","value":"False"}]` | Environment variables to pass to the container | +| global | map | `{"ddEnabled":false,"environment":"default","minAvialable":1,"pdb":false}` | Global configuration options. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/manifestservice","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/manifestservice"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| manifestserviceG3auto | map | `{"awsaccesskey":"","awssecretkey":"","bucketName":"testbucket","hostname":"testinstall","prefix":"test"}` | Values for manifestservice secret. | +| manifestserviceG3auto.awsaccesskey | string | `""` | AWS access key. | +| manifestserviceG3auto.awssecretkey | string | `""` | AWS secret access key. | +| manifestserviceG3auto.bucketName | string | `"testbucket"` | Bucket for the manifestservice to read and write to. | +| manifestserviceG3auto.prefix | string | `"test"` | Directory name to use within the s3 bucket. | +| partOf | string | `"Workspace-tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| terminationGracePeriodSeconds | int | `50` | Grace period that applies to the total time it takes for both the PreStop hook to execute and for the Container to stop normally. | +| volumeMounts | list | `[{"mountPath":"/var/gen3/config/","name":"config-volume","readOnly":true}]` | Volumes to mount to the container. | +| volumes | list | `[{"name":"config-volume","secret":{"secretName":"manifestservice-g3auto"}}]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/manifestservice/templates/_helpers.tpl b/helm/manifestservice/templates/_helpers.tpl index c790a343..d0d72644 100644 --- a/helm/manifestservice/templates/_helpers.tpl +++ b/helm/manifestservice/templates/_helpers.tpl @@ -30,24 +30,29 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} -{{/* Common labels */}} {{- define "manifestservice.labels" -}} -helm.sh/chart: {{ include "manifestservice.chart" . }} -{{ include "manifestservice.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "manifestservice.selectorLabels" -}} -app.kubernetes.io/name: {{ include "manifestservice.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/manifestservice/templates/deployment.yaml b/helm/manifestservice/templates/deployment.yaml index fefcc49d..6923a5c0 100644 --- a/helm/manifestservice/templates/deployment.yaml +++ b/helm/manifestservice/templates/deployment.yaml @@ -2,24 +2,31 @@ apiVersion: apps/v1 kind: Deployment metadata: name: manifestservice-deployment + labels: + {{- include "manifestservice.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} selector: - {{- with .Values.selectorLabels }} - matchLabels: - {{- toYaml . | nindent 8 }} - {{- end }} + matchLabels: + {{- include "manifestservice.selectorLabels" . | nindent 6 }} revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} strategy: {{- toYaml .Values.strategy | nindent 8 }} template: metadata: - {{- with .Values.labels }} labels: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.selectorLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} + {{- include "manifestservice.selectorLabels" . | nindent 8 }} + s3: "yes" + public: "yes" + userhelper: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -31,9 +38,12 @@ spec: terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds}} containers: - name: manifestservice - image: "quay.io/cdis/manifestservice:2022.09" - imagePullPolicy: Always + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- toYaml .Values.env | nindent 12 }} volumeMounts: {{- toYaml .Values.volumeMounts | nindent 12 }} diff --git a/helm/manifestservice/templates/pdb.yaml b/helm/manifestservice/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/manifestservice/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/manifestservice/templates/service.yaml b/helm/manifestservice/templates/service.yaml index 7223757c..4475d19b 100644 --- a/helm/manifestservice/templates/service.yaml +++ b/helm/manifestservice/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: 80 protocol: TCP name: http selector: diff --git a/helm/manifestservice/templates/tests/test-connection.yaml b/helm/manifestservice/templates/tests/test-connection.yaml index e54df7be..3d4b1d87 100644 --- a/helm/manifestservice/templates/tests/test-connection.yaml +++ b/helm/manifestservice/templates/tests/test-connection.yaml @@ -1,9 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "manifestservice.fullname" . }}-test-connection" - labels: - {{- include "manifestservice.labels" . | nindent 4 }} + name: "manifestservice-test-connection" annotations: "helm.sh/hook": test spec: @@ -11,5 +9,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "manifestservice.fullname" . }}:{{ .Values.service.port }}'] + args: ['manifestservice-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/manifestservice/values.yaml b/helm/manifestservice/values.yaml index 0af4b397..1b9d9e64 100644 --- a/helm/manifestservice/values.yaml +++ b/helm/manifestservice/values.yaml @@ -2,63 +2,102 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -selectorLabels: - app: manifestservice - release: production - +# -- (map) Global configuration options. +global: + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 + +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (int) Number of replicas for the deployment. +replicaCount: 1 + +# -- (map) Docker image information. +image: + # -- (string) Docker repository. + repository: quay.io/cdis/manifestservice + # -- (string) Docker pull policy. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "" + +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true + # -- (map) Annotations to add to the service account. annotations: {} + # -- (string) The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template name: "" - +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 -labels: - s3: "yes" - public: "yes" - userhelper: "yes" - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - manifestservice + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false - +# -- (list) Volumes to attach to the container. volumes: - name: config-volume secret: secretName: "manifestservice-g3auto" +# -- (int) Grace period that applies to the total time it takes for both the PreStop hook to execute and for the Container to stop normally. terminationGracePeriodSeconds: 50 +# -- (list) Environment variables to pass to the container env: - name: REQUESTS_CA_BUNDLE value: /etc/ssl/certs/ca-certificates.crt @@ -67,23 +106,55 @@ env: - name: GEN3_DEBUG value: "False" +# -- (list) Volumes to mount to the container. volumeMounts: - name: "config-volume" readOnly: true mountPath: "/var/gen3/config/" - +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Values for manifestservice secret. manifestserviceG3auto: hostname: testinstall + # -- (string) Bucket for the manifestservice to read and write to. bucketName: testbucket + # -- (string) Directory name to use within the s3 bucket. prefix: test + # -- (string) AWS access key. awsaccesskey: "" + # -- (string) AWS secret access key. awssecretkey: "" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Workspace-tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/metadata/Chart.yaml b/helm/metadata/Chart.yaml index 6cea2e87..b0730575 100644 --- a/helm/metadata/Chart.yaml +++ b/helm/metadata/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,5 +25,13 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate +- name: elasticsearch + version: "7.17.1" + repository: "https://helm.elastic.co" + condition: elasticsearch.separate diff --git a/helm/metadata/README.md b/helm/metadata/README.md index 8fe0b122..151ea2f8 100644 --- a/helm/metadata/README.md +++ b/helm/metadata/README.md @@ -1,6 +1,6 @@ # metadata -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.9](https://img.shields.io/badge/Version-0.1.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Metadata Service @@ -8,32 +8,45 @@ A Helm chart for gen3 Metadata Service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | +| https://helm.elastic.co | elasticsearch | 7.17.1 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"metadata"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| aggMdsNamespace | string | `nil` | | -| args[0] | string | `"-c"` | | -| args[1] | string | `"/env/bin/alembic upgrade head\n"` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| command[0] | string | `"/bin/sh"` | | -| containerPort[0].containerPort | int | `80` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["metadata"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["metadata"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["metadata"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["metadata"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["metadata"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| aggMdsConfig | string | `"{\n \"configuration\": {\n \"schema\": {\n \"_subjects_count\": {\n \"type\": \"integer\"\n },\n \"__manifest\": {\n \"description\": \"an array of filename (usually DRS ids and its size\",\n \"type\": \"array\",\n \"properties\": {\n \"file_name\": {\n \"type\": \"string\"\n },\n \"file_size\": {\n \"type\": \"integer\"\n }\n }\n },\n \"tags\": {\n \"type\": \"array\"\n },\n \"_unique_id\": {},\n \"study_description\": {},\n \"study_id\": {},\n \"study_url\": {},\n \"project_id\": {},\n \"short_name\": {\n \"default\": \"not_set\"\n },\n \"year\": {\n \"default\": \"not_set\"\n },\n \"full_name\": {},\n \"commons_url\": {},\n \"commons\": {}\n },\n \"settings\": {\n \"cache_drs\": true\n }\n },\n \"adapter_commons\": {\n \"Gen3\": {\n \"mds_url\": \"https://gen3.datacommons.io/\",\n \"commons_url\": \"gen3.datacommons.io/\",\n \"adapter\": \"gen3\",\n \"config\": {\n \"guid_type\": \"discovery_metadata\",\n \"study_field\": \"gen3_discovery\"\n },\n \"keep_original_fields\": false,\n \"field_mappings\": {\n \"tags\": \"path:tags\",\n \"_unique_id\": \"path:_unique_id\",\n \"study_description\": \"path:summary\",\n \"full_name\": \"path:study_title\",\n \"short_name\": \"path:short_name\",\n \"year\": \"path:year\",\n \"accession_number\": \"path:accession_number\",\n \"commons\": \"Gen3 Data Commons\",\n \"study_url\": {\n \"path\": \"link\",\n \"default\": \"unknown\"\n }\n }\n }\n }\n}\n"` | | +| aggMdsNamespace | string | `"default"` | Namespae to use if AggMds is enabled. | +| args | list | `["-c","/env/bin/alembic upgrade head\n"]` | Arguments to pass to the init container. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| command | list | `["/bin/sh"]` | Command to run for the init container. | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | | debug | bool | `false` | | -| esEndpoint | string | `"elasticsearch:9200"` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| elasticsearch.clusterName | string | `"gen3-elasticsearch"` | | +| elasticsearch.esConfig."elasticsearch.yml" | string | `"# Here we can add elasticsearch config\n"` | | +| elasticsearch.maxUnavailable | int | `0` | | +| elasticsearch.replicas | int | `1` | | +| elasticsearch.separate | bool | `false` | | +| elasticsearch.singleNode | bool | `true` | | +| esEndpoint | string | `"http://gen3-elasticsearch-master:9200"` | Elasticsearch endpoint. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -46,7 +59,9 @@ A Helm chart for gen3 Metadata Service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -57,62 +72,48 @@ A Helm chart for gen3 Metadata Service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/metadata-service"` | | -| image.tag | string | `"master"` | | -| initContainerName | string | `"metadata-db-migrate"` | | -| initResources.limits.cpu | float | `0.8` | | -| initResources.limits.memory | string | `"512Mi"` | | -| initVolumeMounts[0].mountPath | string | `"/src/.env"` | | -| initVolumeMounts[0].name | string | `"config-volume-g3auto"` | | -| initVolumeMounts[0].readOnly | bool | `true` | | -| initVolumeMounts[0].subPath | string | `"metadata.env"` | | -| livenessProbe.httpGet.path | string | `"/_status"` | | -| livenessProbe.httpGet.port | int | `80` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/metadata-service","tag":"feat_es-7"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/metadata-service"` | Docker repository. | +| image.tag | string | `"feat_es-7"` | Overrides the image tag whose default is the chart appVersion. | +| initContainerName | string | `"metadata-db-migrate"` | Name of the init container. | +| initResources | map | `{"limits":{"cpu":0.8,"memory":"512Mi"}}` | Resource limits for the init container. | +| initResources.limits | map | `{"cpu":0.8,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| initResources.limits.cpu | string | `0.8` | The maximum amount of CPU the container can use | +| initResources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| initVolumeMounts | list | `[{"mountPath":"/src/.env","name":"config-volume-g3auto","readOnly":true,"subPath":"metadata.env"}]` | Volumes to mount to the init container. | +| partOf | string | `"Discovery-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| readinessProbe.httpGet.path | string | `"/_status"` | | -| readinessProbe.httpGet.port | int | `80` | | -| releaseLabel | string | `"production"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| revisionHistoryLimit | int | `2` | | -| service.port[0].name | string | `"http"` | | -| service.port[0].port | int | `80` | | -| service.port[0].protocol | string | `"TCP"` | | -| service.port[0].targetPort | int | `80` | | -| service.type | string | `"ClusterIP"` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":[{"name":"http","port":80,"protocol":"TCP","targetPort":80}],"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `[{"name":"http","port":80,"protocol":"TCP","targetPort":80}]` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | | serviceAnnotations."getambassador.io/config" | string | `"---\napiVersion: ambassador/v1\nambassador_id: \"gen3\"\nkind: Mapping\nname: metadata_mapping\nprefix: /index/\nservice: http://metadata-service:80\n"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| useAggMds | string | `nil` | | -| volumeMounts[0].mountPath | string | `"/src/.env"` | | -| volumeMounts[0].name | string | `"config-volume-g3auto"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"metadata.env"` | | -| volumeMounts[1].mountPath | string | `"/aggregate_config.json"` | | -| volumeMounts[1].name | string | `"config-volume"` | | -| volumeMounts[1].readOnly | bool | `true` | | -| volumeMounts[1].subPath | string | `"aggregate_config.json"` | | -| volumeMounts[2].mountPath | string | `"/metadata.json"` | | -| volumeMounts[2].name | string | `"config-manifest"` | | -| volumeMounts[2].readOnly | bool | `true` | | -| volumeMounts[2].subPath | string | `"json"` | | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| useAggMds | bool | `"True"` | Set to true to aggregate metadata from multiple other Metadata Service instances. | +| volumeMounts | list | `[{"mountPath":"/src/.env","name":"config-volume-g3auto","readOnly":true,"subPath":"metadata.env"},{"mountPath":"/aggregate_config.json","name":"config-volume","readOnly":true,"subPath":"aggregate_config.json"},{"mountPath":"/metadata.json","name":"config-manifest","readOnly":true,"subPath":"json"}]` | Volumes to mount to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/metadata/templates/_helpers.tpl b/helm/metadata/templates/_helpers.tpl index 8e99ad6d..f8424983 100644 --- a/helm/metadata/templates/_helpers.tpl +++ b/helm/metadata/templates/_helpers.tpl @@ -30,26 +30,29 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} -{{/* Common labels */}} {{- define "metadata.labels" -}} -helm.sh/chart: {{ include "metadata.chart" . }} -{{ include "metadata.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "metadata.selectorLabels" -}} -app.kubernetes.io/name: {{ include "metadata.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "metadata.name" . }} -release: {{ .Values.releaseLabel }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -74,14 +77,3 @@ Create the name of the service account to use {{- default .Values.postgres.password }} {{- end }} {{- end }} - -{{/* -Define ddEnabled -*/}} -{{- define "metadata.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/helm/metadata/templates/agg-mds-sync.yaml b/helm/metadata/templates/agg-mds-sync.yaml new file mode 100644 index 00000000..08754b40 --- /dev/null +++ b/helm/metadata/templates/agg-mds-sync.yaml @@ -0,0 +1,160 @@ +{{- if .Values.useAggMds }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: agg-mds-config +data: + aggregate_config.json: | + {{ .Values.aggMdsConfig | default "{}" | nindent 4 }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: metadata-aggregate-sync +spec: + schedule: "0 0 1 1 */5" + jobTemplate: + spec: + template: + metadata: + labels: + app: gen3job + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: karpenter.sh/capacity-type + operator: In + values: + - on-demand + - weight: 99 + preference: + matchExpressions: + - key: eks.amazonaws.com/capacityType + operator: In + values: + - ONDEMAND + volumes: + - name: config-volume + configMap: + name: agg-mds-config + - name: shared-data + emptyDir: {} + initContainers: + - name: wait-for-es + image: alpine/curl + env: + - name: GEN3_ES_ENDPOINT + value: {{ .Values.esEndpoint | default "http://gen3-elasticsearch-master:9200" }} + imagePullPolicy: IfNotPresent + command: ["/bin/sh"] + args: + - "-c" + - | + echo "Waiting for Elasticsearch to be ready..." + until curl -s -XGET $GEN3_ES_ENDPOINT; do + echo "Elasticsearch is not ready yet..." + sleep 5 + done + echo "Elasticsearch is ready!" + - name: wait-for-metadata + image: alpine/curl + env: + - name: GEN3_ES_ENDPOINT + value: {{ .Values.esEndpoint | default "http://gen3-elasticsearch-master:9200" }} + imagePullPolicy: IfNotPresent + command: ["/bin/sh"] + args: + - "-c" + - | + echo "Waiting for metadata service to be ready" + until curl -s -XGET http://metadata-service; do + echo "Metadata service is not ready yet..." + sleep 5 + done + containers: + - name: metadata-sync + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + volumeMounts: + # - name: config-volume-g3auto + # readOnly: true + # mountPath: /src/.env + # subPath: metadata.env + - name: config-volume + readOnly: true + mountPath: /aggregate_config.json + subPath: aggregate_config.json + # - name: config-manifest + # readOnly: true + # mountPath: /metadata.json + # subPath: json + - name: shared-data + mountPath: /mnt/shared + env: + - name: GEN3_DEBUG + value: "False" + - name: GEN3_ES_ENDPOINT + value: {{ .Values.esEndpoint | default "http://gen3-elasticsearch-master:9200" }} + - name: USE_AGG_MDS + value: {{ (.Values.useAggMds | quote | default "True") }} + - name: AGG_MDS_NAMESPACE + value: {{ .Values.aggMdsNamespace | default .Release.Name }} + imagePullPolicy: Always + command: ["/bin/sh"] + args: + - "-c" + - | + cat /aggregate_config.json + /env/bin/python /src/src/mds/populate.py --config /aggregate_config.json + if [ $? -ne 0 ]; then + echo "WARNING: non zero exit code: $?" + echo "WARNING: non zero exit code: $?" > /mnt/shared/status + else + echo "Success" > /mnt/shared/status + fi + - name: slack-alert + env: + - name: slackWebHook + valueFrom: + configMapKeyRef: + name: global + key: slack_webhook + optional: true + - name: gen3Env + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + optional: true + image: quay.io/cdis/awshelper:master + volumeMounts: + - name: shared-data + mountPath: /mnt/shared + command: ["/bin/bash"] + args: + - "-c" + - | + if [[ ! "$slackWebHook" =~ ^http ]]; then + echo "Slack webhook not set" + exit 0 + fi + while [ ! -f /mnt/shared/status ]; do + echo "Waiting for status file..." + sleep 5 + done + if ! [[ $(cat /mnt/shared/status) =~ "Success" ]]; then + success="SUCCESS" + color="2EB67D" + else + success="FAILED" + color="FF0000" + fi + echo "Sending ${success} message to slack..." + payload="{\"attachments\": [{\"fallback\": \"JOB ${success}: metadata-aggregate-sync cronjob on ${gen3Env}\",\"color\": \"#${color}\",\"title\": \"JOB ${success}: metadata-aggregate-sync cronjob on ${gen3Env}\",\"text\": \"Pod name: ${HOSTNAME}\",\"ts\": \"$(date +%s)\"}]}" + echo "Payload=${payload}" + curl -X POST --data-urlencode "payload=${payload}" "${slackWebHook}" + restartPolicy: Never +{{- end}} \ No newline at end of file diff --git a/helm/metadata/templates/deployment.yaml b/helm/metadata/templates/deployment.yaml index 0d22c28e..2a093387 100644 --- a/helm/metadata/templates/deployment.yaml +++ b/helm/metadata/templates/deployment.yaml @@ -2,6 +2,11 @@ apiVersion: apps/v1 kind: Deployment metadata: name: metadata-deployment + labels: + {{- include "metadata.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -17,17 +22,14 @@ spec: template: metadata: labels: + {{- include "metadata.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' userhelper: 'yes' - {{- if eq (include "metadata.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "guppy" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "metadata.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -50,6 +52,9 @@ spec: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: GEN3_DEBUG value: {{ .Values.GEN3_DEBUG | quote }} - name: GEN3_ES_ENDPOINT @@ -84,23 +89,30 @@ spec: name: metadata-dbcreds key: dbcreated optional: false + {{- with .Values.useAggMds }} - name: USE_AGG_MDS - value: {{ .Values.useAggMds | quote }} + value: {{ . | quote }} + {{- end }} + {{- with .Values.aggMdsNamespace}} + - name: AGG_MDS_NAMESPACE + value: {{ . }} + {{- end }} - name: AGG_MDS_NAMESPACE value: {{ .Values.aggMdsNamespace | quote }} imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.readinessProbe }} + httpGet: + path: /_status + port: 80 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.containerPort}} + httpGet: + path: /_status + port: 80 ports: - {{- toYaml . | nindent 12}} - {{- end }} + - containerPort: 80 {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/metadata/templates/pdb.yaml b/helm/metadata/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/metadata/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/metadata/templates/tests/test-connection.yaml b/helm/metadata/templates/tests/test-connection.yaml index 007dd312..4bafd3c8 100644 --- a/helm/metadata/templates/tests/test-connection.yaml +++ b/helm/metadata/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "metadata.fullname" . }}:{{ .Values.service.port }}'] + args: ['metadata-service:80/_status'] restartPolicy: Never diff --git a/helm/metadata/values.yaml b/helm/metadata/values.yaml index e0c340c1..876853a6 100644 --- a/helm/metadata/values.yaml +++ b/helm/metadata/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,6 +52,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -73,73 +73,156 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false -# Deployment -releaseLabel: "production" +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 -dataDog: - enabled: false - env: dev - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - metadata + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/metadata-service + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. - tag: "master" + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "feat_es-7" debug: false # Environment Variables -esEndpoint: elasticsearch:9200 -useAggMds: -aggMdsNamespace: - -livenessProbe: - httpGet: - path: /_status - port: 80 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 -readinessProbe: - httpGet: - path: /_status - port: 80 - -containerPort: - - containerPort: 80 +# -- (string) Elasticsearch endpoint. +esEndpoint: http://gen3-elasticsearch-master:9200 +# -- (bool) Set to true to aggregate metadata from multiple other Metadata Service instances. +useAggMds: "True" +# -- (string) Namespae to use if AggMds is enabled. +aggMdsNamespace: default + +aggMdsConfig: | + { + "configuration": { + "schema": { + "_subjects_count": { + "type": "integer" + }, + "__manifest": { + "description": "an array of filename (usually DRS ids and its size", + "type": "array", + "properties": { + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + }, + "tags": { + "type": "array" + }, + "_unique_id": {}, + "study_description": {}, + "study_id": {}, + "study_url": {}, + "project_id": {}, + "short_name": { + "default": "not_set" + }, + "year": { + "default": "not_set" + }, + "full_name": {}, + "commons_url": {}, + "commons": {} + }, + "settings": { + "cache_drs": true + } + }, + "adapter_commons": { + "Gen3": { + "mds_url": "https://gen3.datacommons.io/", + "commons_url": "gen3.datacommons.io/", + "adapter": "gen3", + "config": { + "guid_type": "discovery_metadata", + "study_field": "gen3_discovery" + }, + "keep_original_fields": false, + "field_mappings": { + "tags": "path:tags", + "_unique_id": "path:_unique_id", + "study_description": "path:summary", + "full_name": "path:study_title", + "short_name": "path:short_name", + "year": "path:year", + "accession_number": "path:accession_number", + "commons": "Gen3 Data Commons", + "study_url": { + "path": "link", + "default": "unknown" + } + } + } + } + } + +# -- (list) Volumes to mount to the container. volumeMounts: - name: config-volume-g3auto readOnly: true @@ -154,30 +237,41 @@ volumeMounts: mountPath: /metadata.json subPath: json +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi # Init Container +# -- (string) Name of the init container. initContainerName: metadata-db-migrate - +# -- (list) Volumes to mount to the init container. initVolumeMounts: - name: config-volume-g3auto readOnly: true mountPath: /src/.env subPath: metadata.env - +# -- (map) Resource limits for the init container. initResources: + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 0.8 + # -- (string) The maximum amount of memory the container can use memory: 512Mi - +# -- (list) Command to run for the init container. command: ["/bin/sh"] - +# -- (list) Arguments to pass to the init container. args: - "-c" - | @@ -194,10 +288,43 @@ serviceAnnotations: prefix: /index/ service: http://metadata-service:80 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: - protocol: TCP port: 80 targetPort: 80 name: http + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Discovery-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 + +elasticsearch: + separate: false + clusterName: gen3-elasticsearch + maxUnavailable: 0 + singleNode: true + replicas: 1 + esConfig: + elasticsearch.yml: | + # Here we can add elasticsearch config diff --git a/helm/peregrine/Chart.yaml b/helm/peregrine/Chart.yaml index 7ec77d70..6fea8732 100644 --- a/helm/peregrine/Chart.yaml +++ b/helm/peregrine/Chart.yaml @@ -15,16 +15,20 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.10 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "master" +appVersion: "2023.01" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/peregrine/README.md b/helm/peregrine/README.md index 8340b674..e7c8244d 100644 --- a/helm/peregrine/README.md +++ b/helm/peregrine/README.md @@ -1,6 +1,6 @@ # peregrine -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.10](https://img.shields.io/badge/Version-0.1.10-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2023.01](https://img.shields.io/badge/AppVersion-2023.01-informational?style=flat-square) A Helm chart for gen3 Peregrine service @@ -8,21 +8,28 @@ A Helm chart for gen3 Peregrine service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | | -| arboristUrl | string | `nil` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| env | string | `nil` | | -| fullnameOverride | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{}` | Affinity to use for the deployment. | +| arboristUrl | string | `nil` | URL for the arborist service | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| env | list | `nil` | Environment variables to pass to the container | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -35,7 +42,9 @@ A Helm chart for gen3 Peregrine service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -44,43 +53,47 @@ A Helm chart for gen3 Peregrine service | global.postgres.master.password | string | `nil` | password for superuser in postgres. This is used to create or restore databases | | global.postgres.master.port | string | `"5432"` | Port for Postgres. | | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | -| global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"quay.io/cdis/peregrine"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image.pullPolicy | string | `"IfNotPresent"` | When to pull the image. | +| image.repository | string | `"quay.io/cdis/peregrine"` | The Docker image repository for the fence service | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"Core-Service"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{}` | Annotations to add to the pod | +| podSecurityContext | map | `{}` | Security context for the pod | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| securityContext | object | `{}` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| tolerations | list | `[]` | | -| volumeMounts | string | `nil` | | -| volumes[0].emptyDir | object | `{}` | | -| volumes[0].name | string | `"shared-data"` | | -| volumes[1].name | string | `"config-volume"` | | -| volumes[1].secret.secretName | string | `"peregrine-secret"` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of desired replicas | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| securityContext | map | `{}` | Security context for the containers in the pod | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account | +| tolerations | list | `[]` | Tolerations for the pods | +| volumeMounts | list | `nil` | Volumes to mount to the container. | +| volumes | list | `[{"emptyDir":{},"name":"shared-data"},{"name":"config-volume","secret":{"secretName":"peregrine-secret"}}]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/peregrine/templates/_helpers.tpl b/helm/peregrine/templates/_helpers.tpl index 1f786d38..1674a02f 100644 --- a/helm/peregrine/templates/_helpers.tpl +++ b/helm/peregrine/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "peregrine.labels" -}} -helm.sh/chart: {{ include "peregrine.chart" . }} -{{ include "peregrine.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "peregrine.selectorLabels" -}} -app.kubernetes.io/name: {{ include "peregrine.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/peregrine/templates/deployment.yaml b/helm/peregrine/templates/deployment.yaml index 01e44ca8..26795d38 100644 --- a/helm/peregrine/templates/deployment.yaml +++ b/helm/peregrine/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: peregrine-deployment labels: {{- include "peregrine.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -19,6 +22,9 @@ spec: {{- end }} labels: {{- include "peregrine.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.volumes }} volumes: @@ -38,6 +44,9 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: FENCE_DB_USER valueFrom: secretKeyRef: @@ -142,7 +151,7 @@ spec: volumeMounts: - name: "config-volume" readOnly: true - mountPath: "/var/www/peregrine/settings.py" + mountPath: "/var/www/peregrine/wsgi.py" subPath: "settings.py" ports: - name: http diff --git a/helm/peregrine/templates/pdb.yaml b/helm/peregrine/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/peregrine/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/peregrine/templates/tests/test-connection.yaml b/helm/peregrine/templates/tests/test-connection.yaml index 91f03791..56bcf4b5 100644 --- a/helm/peregrine/templates/tests/test-connection.yaml +++ b/helm/peregrine/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "peregrine.fullname" . }}-test-connection" + name: peregrine-test-connection labels: {{- include "peregrine.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "peregrine.fullname" . }}:{{ .Values.service.port }}'] + args: ['peregrine-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/peregrine/values.yaml b/helm/peregrine/values.yaml index 29e9cd3a..90cfd449 100644 --- a/helm/peregrine/values.yaml +++ b/helm/peregrine/values.yaml @@ -41,13 +41,7 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml - # -- (bool) Whether public datasets are enabled. - publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -55,6 +49,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -72,35 +70,57 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + enabled: false + +# -- (string) URL for the arborist service arboristUrl: +# -- (int) Number of desired replicas replicaCount: 1 image: + # -- (string) The Docker image repository for the fence service repository: quay.io/cdis/peregrine + # -- (string) When to pull the image. pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template + # -- (string) The name of the service account name: "" +# -- (map) Annotations to add to the pod podAnnotations: {} +# -- (map) Security context for the pod podSecurityContext: {} # fsGroup: 2000 +# -- (map) Security context for the containers in the pod securityContext: {} # capabilities: # drop: @@ -109,36 +129,54 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 - +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) Target CPU utilization percentage targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node Selector for the pods nodeSelector: {} +# -- (list) Tolerations for the pods tolerations: [] +# -- (map) Affinity to use for the deployment. affinity: {} - +# -- (list) Environment variables to pass to the container env: CONF_HOSTNAME: localhost +# -- (list) Volumes to attach to the container. volumes: - name: shared-data emptyDir: {} @@ -146,4 +184,25 @@ volumes: secret: secretName: "peregrine-secret" +# -- (list) Volumes to mount to the container. volumeMounts: + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Core-Service" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/pidgin/Chart.yaml b/helm/pidgin/Chart.yaml index c8984e1e..c871f126 100644 --- a/helm/pidgin/Chart.yaml +++ b/helm/pidgin/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.8 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -23,8 +23,7 @@ version: 0.1.3 # It is recommended to use it with quotes. appVersion: "master" - dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common diff --git a/helm/pidgin/README.md b/helm/pidgin/README.md index 1d8677ca..72b5f9bc 100644 --- a/helm/pidgin/README.md +++ b/helm/pidgin/README.md @@ -1,6 +1,6 @@ # pidgin -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.8](https://img.shields.io/badge/Version-0.1.8-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Pidgin Service @@ -8,35 +8,32 @@ A Helm chart for gen3 Pidgin Service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"pidgin"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| containerPort[0].containerPort | int | `80` | | -| containerPort[1].containerPort | int | `443` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | -| ddEnv | string | `nil` | | -| ddLogsInjection | string | `nil` | | -| ddProfilingEnabled | string | `nil` | | -| ddService | string | `nil` | | -| ddTraceAgentHostname | string | `nil` | | -| ddTraceEnabled | string | `nil` | | -| ddTraceSampleRate | string | `nil` | | -| ddVersion | string | `nil` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["pidgin"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["pidgin"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["pidgin"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["pidgin"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["pidgin"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| dataDog | bool | `{"enabled":false,"env":"dev"}` | Whether Datadog is enabled. | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -49,7 +46,9 @@ A Helm chart for gen3 Pidgin Service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -60,17 +59,11 @@ A Helm chart for gen3 Pidgin Service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/pidgin"` | | -| image.tag | string | `""` | | -| livenessProbe.httpGet.path | string | `"/_status"` | | -| livenessProbe.httpGet.port | int | `80` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image.pullPolicy | string | `"Always"` | When to pull the image. | +| image.repository | string | `"quay.io/cdis/pidgin"` | The Docker image repository for the fence service | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| partOf | string | `"Peregrine"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | | postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | @@ -78,23 +71,17 @@ A Helm chart for gen3 Pidgin Service | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| readinessProbe.httpGet.path | string | `"/_status"` | | -| readinessProbe.httpGet.port | int | `80` | | -| replicaCount | int | `1` | | -| resources | string | `nil` | | -| revisionHistoryLimit | int | `2` | | -| service.port[0].name | string | `"http"` | | -| service.port[0].port | int | `80` | | -| service.port[0].protocol | string | `"TCP"` | | -| service.port[0].targetPort | int | `80` | | -| service.port[1].name | string | `"https"` | | -| service.port[1].port | int | `443` | | -| service.port[1].protocol | string | `"TCP"` | | -| service.port[1].targetPort | int | `443` | | -| service.type | string | `"ClusterIP"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of desired replicas | +| resources | map | `nil` | Resource requests and limits for the containers in the pod | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":[{"name":"http","port":80,"protocol":"TCP","targetPort":80},{"name":"https","port":443,"protocol":"TCP","targetPort":443}],"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | list | `[{"name":"http","port":80,"protocol":"TCP","targetPort":80},{"name":"https","port":443,"protocol":"TCP","targetPort":443}]` | The port numbers that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/pidgin/templates/_helpers.tpl b/helm/pidgin/templates/_helpers.tpl index 388e3197..9a3571d9 100644 --- a/helm/pidgin/templates/_helpers.tpl +++ b/helm/pidgin/templates/_helpers.tpl @@ -34,21 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "pidgin.labels" -}} -helm.sh/chart: {{ include "pidgin.chart" . }} -{{ include "pidgin.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "pidgin.selectorLabels" -}} -app.kubernetes.io/name: {{ include "pidgin.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "pidgin.name" . }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -61,14 +66,3 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} - -{{/* -Define ddEnabled -*/}} -{{- define "pidgin.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/helm/pidgin/templates/deployment.yaml b/helm/pidgin/templates/deployment.yaml index fd3e8e1b..90d52d48 100644 --- a/helm/pidgin/templates/deployment.yaml +++ b/helm/pidgin/templates/deployment.yaml @@ -2,6 +2,11 @@ apiVersion: apps/v1 kind: Deployment metadata: name: pidgin-deployment + labels: + {{- include "pidgin.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -17,16 +22,13 @@ spec: template: metadata: labels: + {{- include "pidgin.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' - {{- if eq (include "pidgin.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "pidgin" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "pidgin.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -36,53 +38,26 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - env: + env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: GEN3_DEBUG value: "False" - {{- with .Values.ddTraceEnabled }} - - name: DD_TRACE_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddEnv }} - - name: DD_ENV - value: {{ . }} - {{- end }} - {{- with .Values.ddService }} - - name: DD_SERVICE - value: {{ . }} - {{- end }} - {{- with .Values.ddVersion }} - - name: DD_VERSION - value: {{ . }} - {{- end }} - {{- with .Values.ddLogsInjection }} - - name: DD_LOGS_INJECTION - value: {{ . }} - {{- end }} - {{- with .Values.ddProfilingEnabled }} - - name: DD_PROFILING_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceSampleRate }} - - name: DD_TRACE_SAMPLE_RATE - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceAgentHostname }} - - name: DD_TRACE_AGENT_HOSTNAME - value: {{ . }} - {{- end }} - {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.readinessProbe }} + httpGet: + path: /_status + port: 80 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.containerPort}} + httpGet: + path: /_status + port: 80 ports: - {{- toYaml . | nindent 10}} - {{- end }} + - containerPort: 80 + - containerPort: 443 imagePullPolicy: {{ .Values.image.pullPolicy }} resources: {{- toYaml .Values.resources | nindent 12 }} \ No newline at end of file diff --git a/helm/pidgin/templates/pdb.yaml b/helm/pidgin/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/pidgin/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/pidgin/templates/tests/test-connection.yaml b/helm/pidgin/templates/tests/test-connection.yaml index 0f00775d..0fc4b8f4 100644 --- a/helm/pidgin/templates/tests/test-connection.yaml +++ b/helm/pidgin/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "pidgin.fullname" . }}:{{ .Values.service.port }}'] + args: ['pidgin-service:80/_status'] restartPolicy: Never diff --git a/helm/pidgin/values.yaml b/helm/pidgin/values.yaml index 086390bd..fb683716 100644 --- a/helm/pidgin/values.yaml +++ b/helm/pidgin/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,6 +52,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -75,82 +75,80 @@ postgres: password: # Deployment +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of desired replicas replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 +# -- (bool) Whether Datadog is enabled. dataDog: enabled: false env: dev +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - pidgin + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false image: + # -- (string) The Docker image repository for the fence service repository: quay.io/cdis/pidgin + # -- (string) When to pull the image. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "" -# Environment Variables -# Placeholders for datadog -ddTraceEnabled: -ddEnv: -ddService: -ddVersion: -ddLogsInjection: -ddProfilingEnabled: -ddTraceSampleRate: -ddTraceAgentHostname: - -livenessProbe: - httpGet: - path: /_status - port: 80 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 -readinessProbe: - httpGet: - path: /_status - port: 80 - -containerPort: - - containerPort: 80 - - containerPort: 443 - +# -- (map) Resource requests and limits for the containers in the pod resources: # limits: # cpu: 1 # memory: 512Mi # Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (list) The port numbers that the service exposes. port: - protocol: TCP port: 80 @@ -160,3 +158,23 @@ service: port: 443 targetPort: 443 name: https + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Peregrine" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/portal/Chart.yaml b/helm/portal/Chart.yaml index c7070814..1a992152 100644 --- a/helm/portal/Chart.yaml +++ b/helm/portal/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.8 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/portal/README.md b/helm/portal/README.md index 00db1a2e..11381d2b 100644 --- a/helm/portal/README.md +++ b/helm/portal/README.md @@ -1,31 +1,45 @@ # portal -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.8](https://img.shields.io/badge/Version-0.1.8-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 data-portal +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"portal"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| fullnameOverride | string | `""` | | -| gitops | map | `{"createdby":"iVBORw0KGgoAAAANSUhEUgAAAfQAAACxCAYAAAAyNE/hAAAAAXNSR0IArs4c6QAAQABJREFUeAHtnQe8FcX1xwVFsHfsBcUudrErKvau2ILGHnuP0fw1xlhi7LG3REXsjSjYC1gRe0ssqFQVFHtB+v/7e9x9zJ03u3f3lvduOefz+b2dOXPmzMxv9+6Zmd17X7uZSpRp06YthYsNSnRj1RuTgQHt2rX7NW7oXFu6rpaMKy+gfwHfYwrYWLExYAwYA3XDwCxlGMmm+Li9DH7MReMx0IUhD08Y9smU7ZVQnlS0A4WPJRlUYxmTmJnpV3ewfe64MMfOYCEwGXwHvgSvg1fBI0xcxnI0MQYyM8D1tjKVTgQ6rgjmA7rGPgBXcG3142hSIwyUI6DXyFCtm8ZA9TLAjXVxencG2BfophqSDihnA4uBdcARYAp1B3L8JzffRziaGAMFGeCaaYeRrrczQUevgiaQwhBgAd0jp5qz7au5c9Y3Y6DeGeDGugC4lHF+Ao4CccE8jgqt6HuCAfh5GWiVZWIMFGJgPwzOBX4wL1TPyquYAVuhV/HJsa7VNwME37UY4X+A3kMph2yIkzfxewqr9evL4bDWfcDF6YxheWccz8HNbU6+4ZJw0olBX9BwA2+AAVtAb4CTbEOsPga4qWpr/WagLfRCMhGDr4C23OfPHTkEZXa01+F/K44HELx+C1o1jnInhrqxM9wppBs6oDN+7eiEJpH/y3Gj621d8DkwqSEG2jqgaxWhD5c+ZCb1zcDUwPC+COhclVZXF7qKDOmhGWxb1ZRgexAN3pLQqF5Kuh/ombje1v82sqWunn2uAvQyql4Y7AHaA196oZgf+x2oP8EvtHxDM7BsYPR6wXIzu1YCzDSSihtGb1CsbN1IXNlYjQE+KJuBCTEfmPHo/wHmTcsUtsuDu0CcHJfWVz3aQcqLHjE31uM4s4wJPv7pcaLsHll8mG11MhCa2bdmT/U1HBNjoCEY4Kapr+k9CGYNDHgUug1ZIZ0Ovg+UB1XYDgV6welA8ItnpDeUr/N0ljUGFg1QoJcyTWqcgbYO6DVOn3XfGMjEwGVYLxCo8Q669QjMbwfKUqmoexuGeiku+jEdvWy3D/pGnzTrBTCTfAZC9/3QI7H8WparegZCJ7bqO20dNAZqjQFW53oxa7dAv8ei25nAq2NJgo/3cKDHWHeAvclPKslhjVeG8w4MYdkaH4Z13xhIzUBbvxSXuqNmaAzUOAOhl/umMaZeBF5tt5dF8PU+jvYvxRmBUBN9BcJFgL4Xr5fyNOH4FP/qc9mFNvWyn34wR+3OBfRWvx4n/MAxs+T8nUNF9b/sgn/dO5cD+iW/ecA4MIb+DuNYEcmdlyVwLo70bYfnaU/tVo3QxznpzDJA14440o7RSPrZ/GIn+bIL7eociJclwTjae7nsjTSCQ4gs5aW4LRqBIxtjYzPAZ6QLCMm91cQMHdwY3Aq+DnUW3VegL9ggS7+xvxO856Ep0KJbEVwOxgBfJqF4HuwNFPATBZvzgdp5H/wAQvItSr8vhyY6dgqpuy24G3wPQjIa5U2gm1OtYBL7p4Dbr7eiSujXAtcD9d2VzSKbpCMV9G0H13eIm489G9mflOQ3KsNuFnAIeAKEXvicin4wOBVospZasP8dcPuu9AFywHF2cDx4FbjSP3UDZpjPACz2dpnMmLaAnk+n5eqQAT4Tx8V8LlaohuHSt6VAv5g+xqkfokCrsIKC3ZCAk8XR6Y3+iYGykEpvq+vnSGOF8j6hiil0p8U6zRXgY2XwTApfkYl+kvc2oJVjQcFuZFQxd5zEcQ6g3xSQr5CkDeidQ5VT6EK7Snljwcf24MMUviKTsST+kOckIYNt6LNzIvodwAgQkoYN6O0TuLQiY8AYKA8DOwXcvMW24McBfauquBvqRTp9Bzn0fD+pL7tQ+Br110kySijTd+wVSPWcO43oHQRNDLQt36pCm9vT4CtgywwN696qlaT6vHyGepGp6g8GR4KqvE8zrjPom85jlp8b1qTsBupqp6fYn509CB8DwFLAxGGgKi8Up3+WNAbqgYG1AoPQjbBNhRuqfpxmINBz4JBMQDkcxL1cp+e5T+OnmJ2GNaibVZahgra7W+3dH9ranTYVPOYGIfkV5XAwJVSITsFuIH5S7WY4PnRvzrRt79SteJLxXEMj54GCj0JiOqP3PB7Dz6wx5UlqXTvFtpvkt+bLWu2DUfNM2QCMgSIY4IY1M9UWCFR9N6BrNRX96kJjDwJ/laQgfim4BQxjF0HbvbpPdAVaLR4D3PvGvOQfxmYdbH8hXYzcQyVNcMTJz0A37J3BQcAXTUK08lX/fHkUhV7ei0QTjv2iTO6oNp7wdEO8fFOWMa1Joi/wFz4/oTsfqN8jGDemTYFJwftkcCBwA87i5PVIYxPxSTqrTKPCzeBp8B74EWhsH4E0oknHxY7hrqT9SZj49F+we86p05xkHCeSObpZMSOha0f97Ae0+zQZLA12BLp25geubEHmBnCwq8yY/hD764DOq9pUG/rMmRTDACfXnqEXQ5zVaQgG+HwsAkKS6vlnpUiiQ6HnwXoxatmkNilfBXwOfDkzrh6G2nYOiV7y2jOhnp6Tfheo+EFcHVdPvU0DdW90beLS1GsP9HKdL3oBa9G4etJTvgEIvTR3aFw97P1n6KiaRFxvFVevGD3+7pvuOu/vaml8UWNtMDmv5vTMGxy6xPmgTC/mxb2n8fuEeqFn6NNbnDbtahKzxdU1fREMQKgF9CJ4syqNwQCfDwXAkLTZdiqd2S7QIQVXf9UWPEnYrQ9+83wogM0VqoA+LqDvH7J3ddQ9EISk4HNpKpUS0A8JNKqgm2rrHLsdgXY3XFH94OoRfVxA7+nyUY40bZUS0EMTwVfwOUehvmHTHtwBfBmFIhiY0ccF9CcKtdeI5f5WUiNyYGM2BirJQNz3b5u+tlXJhhN8Hx8oO5XtYG1ZFhTstEV9pWc4D/ltPF1SVj7uSDJQGW314fBiwE5b75WU4wLOj6I/YwL6Firs9AjhTq9gSfLre7qk7EP4eTrJoDXLCK4b0t6WXps/kN+TfhZ83ILNVGwPA9omd2UJMnpMkVYmY3hSWuNGsrOA3khn28baFgx8TaO6kfkS9yKab1fWPDdlraT8m/L36O7K2FDIftsMPm7nBq9nw2lkYMBIwbEiAkfyrefnrgwn85irSJEulaO+KdpoTZPdAo1dznn8PKAPqrAdT8FfAoV6+TCtvIGf/6U1biQ79+WWRhq3jdUYaBUGuPFo21UvG3X2GtSLX/d5utbIrkcjHb2GFNAvpZ+eumBWb793cKwKboM7tkOddKGkXgTzZQFfUcb8JgFferHsmowcudxELivFUeS/ksfQhK2YScdDdFIvP87pdLYH3M7K52Wio4tLZrl24nzUpd4Cel2eVhtUlTHwFv3xb4Y7ojuzDfoZeqFrGfqht5BLFX/SkuTvk6RCryz02KKTZ1PObIijVWhAKFUqxVGp/UpTf2nPSD91+5mnK5iljn405zUM9ZZ7JLOSEO8jIkXC0QJ6DDm25R5DjKmNgTIy0D/ga01uaisG9JVWVXJlG3zhK2ZAevZarbJgBTuWlqPJBD7tClSFcK1qV2derzOp3ifw6kTZUN20j6F+jJzYMZ+Bmlmhc0E9Stf9VU7+aBo3twEffs14mwW+9F3QuZsV0xOPYHeZq8PucPL7ujqlsdvK1WGn2fPtrq7ItF708V+oKtJVzVTTD5NcHejteej2CugrqapkIP28kh1vRd96BFEpqVWO9HhlCnAnJAryxUqo7oRinVm96QzUTECnu9pNsB2F8JXbLqDeDJ1+ZMGV0PZYVwz8l6TcOlF69pR2kX3c0X/DNc6ubvRMYPSb088zIJ0TV3qh35TyF1xlqWl8zo7PuNXd2IB/9e2VgD6r6p2sFarUPsSRXogLPcvPOoQXs1aoBnuuJ/2Dla/pi/u1vcXRzUyZAn1WWSpQIbRqD5iZKo6BWgrocWMwvTFQCwycRicHBzqq7wSvx01xVKAsswpfmqA9yfFQfIbeDn+Xcr395k4CJ2Cr/plMZyA0MfnaOJrpA+hxA7p2ALuD0HU9ncnAX65NLTTW9oq+Ia8Jg0kJDNiKtwTyrKoxkJYBgoFWwA8E7BdG15+bnI4lCT664eA50AUMIL+F75B+aBX0hqffElv3Ru0VN1YWjt5mxP7W+G5wNFtjMdFitA+30Mw00wkBXSHVMRj4sWcAvE8tVNHKkxnwSU22tlJjwBgohYFTqBxahegrbPpJUR2LEuoeRkX9WMtiOQd6RBIM6ujvytlEBz0X1b8edZ+PRmXBI7b6AfPbwE5Bg+pQhr6Hl/ae53Ok1Wiqn42Nhg43+j/h/wEbRboaP2pCqmfpruh/1af+QSFsV6Tyqa6DXNrnO2BiqkIMNMKWu54l3leIiBov1/ecfdEqYx5POcLLKzsa+Cu2gNlMemEljV2orqsb6WYaKc0KRM/Sd2PMz4KO3tj1THEw5VdwvBDbVC9mYb+y7MHOwBcF9UOAv/V+LbqTwBIgEt2U/4m/42k7FAgjO/1WeScyN4H9wT7k9UthevGv2uTLQIeWD+hCKnH6B+C+WLo/Y/2QsZ4fquDqsNNnT0Fqe6AdkO2o97JrU2tp+q+faNW1c4LT93ak9R/wtqH8dUffIomNrreHwFxe4SDqPuHpLNsWDHCSWuW33GnncVCMlOXZZFtwa23WJwNcxAoMeskoTvS76vqf0bsAPW9sFvJaGa8M/gD0u9pJfvQsfbbmyk4CvQJxSF5GuZZj2pxErxXnnuAz4MoEMrErdcpCv+We+qth1N/KbSyXLrhaxq4D8P+RiPjq2TyohAR2J4KQ6F4UnBig7wgOAl8CV34kE7tSp2yka0zaXwkn9DRbEb5L+S33Bagf+uc8v6A/GgR3edDvGlNP/xNgzbgRUBb6LfcT4+wbXd8IK/RGP8c2/ipjgNXI7dyoxtOtPmCOQPfmQ6fVoaAVsXZHtFXfASjA61hIbsXgCNqaGDJEfw9+16Xsj175huRfo0yryfeBXoSaE3QB2gUIPWufFb0C+gBQNcIY9QMmmtAv43RKK8pH0d/C8V0gPrtj22KHA512LNahXDsRrmxL5n3KXtIRfAR0zrqCXXJpDnmiVakmEuK1ZgVOvmHcuzKA54E7WdRu0DXgz5Q/zHEo0KRkGbADWAWERC9vvh0qMF0bMMDJsxV6G/BuTdY+A3x2ugF/tYuqJNFK8IA07GDXHujZeamin0SNfTZNWZus0MUBbZ+RYnCaXAWFup3AIyl8JJloV+CvwQZySsprYoUejYH+7gy0Ki9WxIneKUkUbGyFnshQfmHshzDfzHLGgDFQbgZYmeh7zWuDfwC961GqDMLB2vjtm8YRdlPBgdjqn2UkPjeP8fcd+n3wcYx8xdi0tfoyOjC62E4wrt+oq1X3lUX6+IJ62+Lnb0XWr8pqjKc/HdsUfF5EB3+hjt67uLSIulYlgQEL6AnkWJExUGkGuKl9D/5MO8uCK8BXGdtUwLkfbIEf4ZOM9fWrgOdRZz0wKGVdPQLQM+zVqHtvyjptYkb/tPrWFrEmT0UJPqYAvQi2BXgjpZOfsdNkoht1n0pZp6bMGNebdHhVoAmprsNCoknjbWBl6vYrZGzl2RmYJXsVq2EMGAPlZoAb3Fh86iWskziuA7YD3cHCoDNYCOiZ5LdAq6LXwGDwJHV/5FiS4EOBagvaV9sKgApeiwO1r5u1+vc60Bv6/bD/gWNa+TeGT3rGWXYkhlFXkw5X1JdUQl/fzI3rGCpsAlYEmkBpXF8CPQsvKPgZhB9NfPRym1btm4HFgM7NL0C+XgVPg4exzzJGTebmBZFMiRIVOGoC+KHnN+tEsql67jrQc3NNXsTJTmAFsAjoAMaA4eAxoJ99/oxjFhGf/rkfksWB2WZggBNpz9Az8GWmxoAxYAwYA8ZAJRiwLfdKsGo+jQFjwBgwBoyBVmbAAnorE27NGQPGgDFgDBgDlWDAAnolWDWfxoAxYAwYA8ZAKzNgAb2VCbfmjAFjwBgwBoyBSjBgAb0SrJpPY8AYMAaMAWOglRmwgN7KhFtzxoAxYAwYA8ZAJRiwgF4JVs2nMWAMGAPGgDHQygxYQG9lwq05Y8AYMAaMAWOgEgxYQK8Eq+bTGDAGjAFjwBhoZQYsoLcy4dacMWAMGAPGgDFQCQYsoFeCVfNpDBgDxoAxYAy0MgPt+C12/debUv5Ji/6Bw1pF9vsV6o1LWXd97PRPELKK/gGD/lmCiTEgBr7iH0QcalQYA8aAMVBvDCig618hzlpvA7PxGAMxDAwnoHeJKTO1MWAMGAM1y4BtudfsqbOOGwPGgDFgDBgDMxiwgD6DC0sZA8aAMWAMGAM1y4AF9Jo9ddZxY8AYMAaMAWNgBgN6Ge5rUMoz9E7Un2uGy0ypH7CemLLGvNh1SGnrmk0l842rsHTVMzAHPZy96ntpHTQGjAFjoJ4Y4KW63qBY2SItFzTweJGNjErbhtlVBwOc53OLPNdpqg2rjlFaL4wBY8AYKC8DpXxdrbw9MW/GQB0zwExDOw6tsevwPW/xT65jKqt2aJzj+emc+xhzKufi26rtsHWsJhjgutI1pWvLlYlcWz+6CqUtoPuMWN4YqAwDf8Ht6ZVxned1E3Iv5Wks01oMfEpDejQYyVgSi0SZejkSYHoxlnWBdrv6EFj0Wx8mlWNgMVz7O836bZWt/SYtoPuMWN4YMAaMAWMgyADB/FYKDnQK/4BuQ4J62nehnKqWLDcDFtDLzaj5MwaMAWOgChkg8OqXNhdwujaBQJz6nRLqr0RdN5jL1dpgb3C7MiZty4D7vKdte2KtGwPGgDFgDFSSgT/i/AMH/TM2tkSM/ZIxelO3MgO2Qm9lwq25hmVgNCN/LcXo58Oma8DudXTTAnpf9ZOvsLwxUCYG3sHPeDCb52+wl7dsGzFgAb2NiLdmG4sBtjavYcRCorCtuTsGDwaMNsLHpIDeVMZAqzDA9fc116f+sdFNQL8Vod/4uAD9II4mVcCABfQqOAnWBWPAGDAGaoEBgvddBPVH6auep48gP6YW+t0ofbSA3ihn2sZpDBgDjc5AWd6ZIojrFz6HNDqZ1Tj+spzgahyY9ckYMAaMAWMgj4GF83KWqTsGLKDX3Sm1ARkDxoAxkM8A2+T6lcJt8rWWqzcGbMu93s6ojafhGeDmvRskzO0R0Zet0qa35CnvQtm+YA2wLFgQHET58xxjhXqLUrgTWBksB1RXPzOrf/Ckt/CfwsdAjqkFn/q1K/l1pR9+mt7Wp1y/tKa+6pfJ1J5+iU3tfQQeB49gO4FjUYL/1ai4F9gQqK12YCQYAfSLew9HfSFdUaEvM9PAlmAD0DUH/eSn/rnUMCBu1Z9xHBMFXytgID8Sff/898Bfoc+LnfSujMb/s67CTWO/K3n31/BUrGtLL8ilFvwsjfEuYDOg869r8DugZ/Lazn8In/qKXWrBZw+Ml/Iq9MeP/M5EeWcO+4DuQNeSvlEiLoeC6Fr6lXTRQhvyuTNYFegc6nOia0rtvAP0C29P0KcpHKtPGID9c5bqOy013SOuqYb95yyMfXcQkg5pTyqVPww46IBuefBIoEyq7eL8U7YcuA9MAYXkHQy2jfPl67F9MuBQ7S0KbgeTA+WuaiSZ3/l+C+WpsxB4wHUUk/4e/UmgfQqf33k+Ur0wJt/gePCFVz+U/QXlpSDx/wJQfkSocgrdw0njpP4HAR+p/1sndVcCDwd8hFQvoowmJUndairDtl/AyeroOoObwaRAuav6nMzBBRsKGFBvEfAvMBEUkk8w2C/gJqjCdomAw6dCxgUv0lAl0xkDxkDNMaAb1btghyw950ayP/ZvgV4gzf1ideweo965HIuVHan4P9AbaNWaJPpRkzto7zKglVBBwU6r17fBHgWNZ5ppHmwuAwOpp1VkWQWfi+PwGXAF0Eq1kCiQnwxepa5WmTUj9Pd0Ovse0Ao2jWyM0WDqXQUKXQdx/npS8F+g67/QjrR+M12B/7os7WGrybBW34eCNBPv5bC7k3qaABQ7Lly0lDQf0Ja1TGMMGAO1xsANdLhTlk5zs7kY+75griz1sFVgPZP6Z2WsF5kruPnbulFZ3PEkCs6MK4z09EmPIp4EunlnEW0Na1JTNqEva+DsddCjCKfa0n0KH9pGrmqhj7OAW+nkBaBQUA2N5ViUj+Kj0HU4LVD5UnRZJ2JHUuf8gK8WKvp0AspHQTHnQRMAfS7LJsWQW7bGzZExYAy0CQMTaVUrYK3YvwdLgG9As3Cj2prMH5sV+YmRZPXrYF8C3cgUmBRgfPkbfp7heeFLfkGG/GRsPwJa2X0HtAOwFghtOZ9Ne3o++SrlcaKgr+e3vmgV9zL4FawGegB39XQ2fq9HV065FWeLBBxOQvcc+AzoXK0ENgRzAFeWJaOAsLurdNJ+gAvtYPg2ft5xV3RSE7QDY2p/il7Xx9dgPrAe6AZ82QaFvgO/C+dhql+Yy4fG55rqufXHQNeS2ouupTlJ+/In2nqMtnQegkK5zsvlINSu/D8PRgNNTnVNrQN8ORQ/T9PO3X5Bm+TpjD1DbxPm67dRril7hg4JnnRIe8apF3qGHrnTqk4BvKBgd15UKXfU8/GtQYsbGLpeYCzw5cWkhjAOPUOPfLxBQi/g5Qm6hUH/yMg7Ppxn7GWwHePZK6ubd96YyOu55S1A8lfPTTCLXaZn6NjrefKXIJLfSGgStIDfALrFwIMgJAr2iUKlmQMV30+sFCjER6Zn6NjHPct/n7KtAk3o5bW1gZ6fh+TCUB3pMI7jR37eBQrgeYJuAXA/CIkehSQKlTS+qU7lz0jvCVosltFtCT4FvgxH0T6uIcp0LfryVJx9SXpasYBeEoNW2WeAa8oCuv/xnTatHAH9RtzmBS6fez/vnAutjlrcpFx7yjcDk4EvS7p2bhrDuIA+gLLEMVOu552+6Obqv83d1CR63bx9Ger2x09jXDBYRnWwzRTQVY86UVD/hvTaka/QkXIF5ReAL1oBJwoVWj2g06b41kuFvmj73N9tyOs/5dqm/7dfkbyurxaTPFVGH3opTi6eAR3zGvAylF8lw4B08UxbZKkTBfVnSRcal863Xmz0ZaMWjnMKDFMH9NhZQZxz0xsDxkBNMqAt9dPY2su0pYr9X6inZ32/J63t71ihXFuMDwUMYm9WAVupJoDj8aet5yTRy2Ha9ndFE5bNXYWTDvlLnDTQBz1aqJjg/0Oc9wB7kn4zqSHKtWV8asBm44CuGlRn0Il5vI7oZcRejOUXT5+XpVzX2uHgybyC6Y9BLvB0UTZ0bcvPcfjTNZUkemFvRMCgR0CXp8L3DSh6g91TjEvn+6Y8B9MzZTmHiTPuQKOmchhg5rQu2UccVZTUze+JKKMjtnqut7SrS0gvQ/3xUTl1teLQ885al38yrrgPY62Prdr7fync6xl0ZqHezRkqaRt3D89+US9fKHsrber5caJgo9VfH4x0M3ZlDTL3ugqlsf8R+29Jzu+ULY1O27hnUV7opu9UK1+Sdj/Cm5BGPsBIz5DdxVhWftO0U5INnKp/+wecHM549Z5CQcFOuy2HYTgUdHQq7IR+fsp1Ll3RZM6Xu7HT+yKJgo1Wzf/C6FzPcE0vH8xS/65gQVipZ/i+LOIrislbQC+GtRl19P1LvRTki3vxRWULkgjZRuXu0b8w9eFIW9f1U23pxO2oautsnfXnrXKOh5vf3PjTiz4u9DKTrnNfZvMVBfJZ+hq6OYb6EDWpl4+OjjK54584HsCYLud4OzfnL73yVs/SF33muwKXX6WXB24wJ9vi35lK19ayIR1YyOvEILh93dMlZrEfBReanB3gGOplxR1BX0enZGiFXuq11OJ9Bq/N2Cz9np3CVYF/DhcLVJJtyWIBvWQKzYExUBMMaJVTtHBzUnDZDWwBdINaClRKsvT100An5gnoItVFJPYF7ipdZVrlquxCxvoqx36gDwFlDMeKS+7mrze5dwJrgVVAJ1Crskag448FdGlUj2PkBnTV0crZD+jS+1LJaymvLc7hEih2BT2BJrfLAn9xhqpyYgG9ctyaZ2OgmhgYXUxnuEmtT70bwerF1C+yzqgM9fRc2ZfYmygBegRj0sTkfhDa9VJdjVk4F9sHOOpR0RCOZRf8K2hfAg4BWXcyyt6fMjoMPQYITb7SNBmql3aLumLXUtRxzuGKpP8NNo50bXX0t27aqh/WrjFgDFSWAT13zSTcqE6hwgsgbTDXi05fZGokbJy5r2E3YS3BWWPSLoNW5OPCVk1avTCn1fxguEj9S3QJ/vKK8LkCCu0GHAPSBHNtKQ8HoUkM6qqSOQO9+TmgS6MK1Qv5D/mq6LXEOexNo2+AtMF8ArZZJhmhMcXqbIUeS02qgq+w6hOwHBnQaabvP1MKmDWp9GamKxPJ6CbUGqKVid4NMGlgBrhR7cLwL4mh4Bv0HwSg6/44UPBrVNi0qRDUv6YDpzHOszjuDX4HtgSha1+r9pPAj+BsULLQriYLDwI9Y/VlEopPgM/xh+jGg1/BzKCaRfdGX9Le/9LUC/n361U0zzlchwZ0/w+dC10r/vlT/jOgz5Ye6ZRdLKCXQCk3BX3oDkrjAttT09iFbKirG+hmobJy67hIR+FTz4JMGpQBrgGtfq4ODP8edOdzPYZeRGsyp+5ygXpVq2IsWjH1Fej73Bx3AieA7sCXP2NzPXXK8Vz9jzj3g7n8ngz0n8YUtFsI7euzWQvP1kOPeNaj77e3GFRhher58rmvaM0852Fm2tOjKB1deYbMGeBVzmHoJT1946mrW6GcadtyLyeb5ssYqA8GtmcYS3pDuY8b1L4gNpjn7PUWdk0KY/sR3AnWZwA9gb8K1OpdW/DlkCM8J5pY9KDtu0AwmOfsa4Xfgd74lNV/EtTORFbZO1BBgbMtZV0aX9vrwMvkt+H8DQHBYJ6zr9g5tIDunRHLGgPGQNPzZZ+G63yFn+dmvQC6DX19Lea5IStgnBboe8mrK3iaC79Le76fpc2PPF0ou3NIWW06xjKaPr3h9UuTxKM8XWIWrvQCo4KnK+PIKHi2pawWaPwGxp34zJ7xdKTe1oG6ZVFZQC8LjebEGKgrBlYKjEbPdQvJ+RjMW8iorcu5qe4K9My8kIS21ucoVClFud6K9kXvySQKfdZ5OTbRKKaQQDOFovFe8fxevtzZiwMO9bXA0OOMFqbYLYvy3y0K+He2ufEEilpNVexn5BR62KVSvbSAXilmza8xULsM6IUeX47jBquXw1qI9OBICg5vUVhlCvqp7wnfB/qQ1j9l6ZDQxT0DZSMCuqyqEL896Yv/TL3ZL2XaGbgDJPW32T4mMdzTL4pffee9UnIvjod4zjuR1z8I2tbT52UpXwfFC8CfdIxC988847bJhM7hUfQ79FJlUw8p60Xi/yrZXQvolWTXfBsDtcnAoEC39RyzPzelDUAnoCC+DNBLZE8DbclX9f2EvkbBXEFxFnAh+C/6E8CSQGPSPzHpCq6m7DDgywBfUUReL9NqS9oVrfz1T0SOBp1VwHEuIL4VBN4Ba0tfggwL1L0P/5uC9kD/EKU78INooFphFatoPUc+AHznWevlw8dp516giYyCvMbbAWwIbiCricBi0juiXYzf4dffaXBMWi05KNDSpuj0D1q2AjqfGtPiYBugyY0mkk16lVVCdFGbGAPGgDHgMvAAmb+B5Vwl6R1z0I1aL3E13Yg5Vr1wQ9Vk4x/AX+Euj04rPkFj0lvLcfdF/X/s1ykvSfCh3yi/FCeXe44WJn+NQPlvHMvN72B87gBc0Tl+Hmjs4kY8aeLwLShZGOtQxqKV6WPAX73uhU5Q4PuZg4JdcBdIJkC/A/8ix2oQ9UN8buh1Rt9H1wRXY9LEYzalW0uqekbdWiRYO8aAMTCDAW6aCiZaWf06Q5uX0k03LtiMzrOskoyCKF3ZBnyU0KWOlMUFc221H55QN2vRlVR4NKFSHL9fU0fBtxjRpGVMTEWNvSLxAO6fxXdPMC6mbannBHHBXIFxb/zcJsNqEPqiCcaBQOcjTuKCecU+IxU5gXGjM70xYAzUBgPcsLT62B7EBQB/ILrpngrO8QuqJc+Y9PxVz2ZvyNinN7HfgvqfZ6wXa44vTTD0jP6eWKOWBc+jWg+I68xCm1oF6wdy0rzgmNl/UgXafoHy7uDJJLtA2WvoNqH+/YGyNlXRp6F0YCugRyhpZDJGF4Ej0hgXY2MBvRjWrI4xUDkGdKPXDdtHlha1gtMq20WW+k223LAUQFYH2hr+qUnZ8o9WXdeCVbC/hKOChduu0rqRxUmor1r9pBXx5benZ61BoY+/gCMpVGDvC34MGk5X6kZ9PNiAOsMS7KIi/5wlBl58/gb2pbK2nTVpCInGNwjsh+3mQDsF8uuPGVVhof7dWGlL/dUE6yT+1a4/zgRXM4poexjYFo3QH8Txo+vlWbAfWJ86cdxQnCc67z4vSWPJq0wm07WkyvTtPQ56sfBvQJ+FkOiz0weshf1pHEPXfNIkS2PwOZePFhK3xdHCME7Bc4LelN0eV15AvyUDHFjApqmYdh4noQshq4ymjSWzVmpUe3jWKmaJCo1fvzJ2ZiHf9OFcbAraFfITUz6cPnSJKTN1DAOcEz1fXRcsB+YBX4Jh4H34TLoZYVK9wrhmpne6PywLdF1okfMNeItxaXytJvRFnzutwDsDBSe1/wH9GMux7EJ7i+B0RbBCzrkC0pu0NyKXr+iB9menAU2sFgMLgO+Britxr3RNCePRtbMmWAnMB7QdH31G4iYvmJRP4p4Xla8F82QMGAM1zwA3WAXtwTnU/HiiATCuKaSH5xCp2+RIX/RstWLPV/1B0Z4epwjP+WWtkaf9X2nnhdZoqzXaYDxa4Ws3Ie2OQtm7pRmFiTFgDBgDxoAxYAzUOAMW0Gv8BFr3jQFjwBgwBowBMWAB3a4DY8AYMAaMAWOgDhiwgF4HJ9GGYAwYA8aAMWAMWEC3a8AYMAaMAWPAGKgDBiyg18FJtCEYA8aAMWAMGAMW0O0aMAaMAWPAGDAG6oAB/Xcd/YhBuxRjmcr37JJ+tzaFi2QT+jInFkJI/B/2D9mEdPrvSfoBhUaXcZy/yY1Ogo3fGDAGjIF6ZUA/LDMSdEwxwB+wmTeFXSkm+nUw/TReOWVRnOnXhxpdVocA/UyhiTFgDBgDxkAdMmBb7nV4Um1IxoAxYAwYA43HgAX0xjvnNmJjwBgwBoyBOmTAAnodnlQbkjFgDBgDxkDjMWABvfHOuY3YGDAGjAFjoA4Z0EtxpwIdC0nw/68WqpSx/CXsr4ipsxv6pWPKktQ/U/jvJIMqKVuWfuxcJX2xbhgDxoAxYAzUGAOz8FWmq6qlz/SlP30RWghfPdP/mC0moH+P3xNbOKwyBePbhS5ZQK+y82LdMQaMAWOgVhiwLfdaOVPWT2PAGDAGjAFjIIEBC+gJ5FiRMWAMGAPGgDFQKwykeXZeK2OxfhoDxoAx0KYM5H7tcj468QuP+r5t085Y4w3HgAX0hjvlNuC2YIAbfW/a3T1D25Ow/QqMBWPASwSIjziaVBEDnNfl6M5BYFOwHpgdNAllP5EYDoaCx8ED1Rrk6atiwZJgNH3UtWdSgwxYQK/Bk2ZdrkkGVqPXe5bSc266CgwDwPXcdD8uxZfVLY0BzoX+P8TVQJO0uEeXc1HWLYc9OP6Tejdy/Cvn70eOVSH0aR86cgOYB/xM/lj616cqOmedyMRA3IWYyYkZGwPGQKswsDytnAT+y033eqD/U2DSygzAew+afA9ogpblHqrVu75x8wE+9K2WNhf6oW8OKXgrmEv0z7FuQr9CU87+1BQDWS7GmhqYddYYqGMGtLN2BBjKjXe/ah4n/dsVDHLwWDX3t1DfGEd3bPqDBQvZJpQvRtl/8HV6gk1rFW1IQ/4/5+qAbpPW6oC1Uz4GbMu9fFyaJ2OgtRmYgwbvJDBoO/9MtkmntXYHUrS3ODabO3b6oaeaFHiejY7fDeL+xbPGpX8x/SmYGWj1q39PHRL9y+q98HkZ521iyKCVdHH/Elvvb5jUGAMW0GvshFl364oBPQePW2Hrs6lgoG11vXC1A1gAhOT/UC5LcPhdlQb1UJ9rUXcCne4S6PhkdLeCc+B/lFvOOdFKXuf4KLCyU/YW6a3bOJirO4PAy2AjEMmbJJ6IMnasHQYsoNfOubKe1h8D47mh6+ZZSPRMUyu+34HzwFKBCvuiGw7+HCgzVXkY6B1wMxVdL87jQ4GymdCPQ38V5+96jn8BZ4J3QE/K2vxrbfRhCn3rSX+OBtrp+RBcjd7edIeIWhML6LV2xqy/DcmAbrwMvC833/s46u3qQwNEnE75h9jaG8oBckpRwatW2gp4vmjLPBjMXcNcgDwLP1qZP0++zYN51D/6Mp70pVHejrXLgL0UV7vnznregAxw8/0NHMbQT4sZ/rUEDT23rhZJet5cLX1M0484TvWCXGrh3PUD36SuYIbGQAYGLKBnIMtMjYFqYYCgcBF9Ca2qZkd/brX0k36sWUV9KaUrcbuZVbPSLmVwVrc+GIi7SOtjdDYKY6C+GdAqfR3QwxvmgazSLyfov+fpE7PU0Qt4XcFyYC6gleRQ8Ca+tOWfWvDVCeNeYK/UlTIY4n9ezNVP9XcR8D0YDV6hr79wLLd8HuNwA/Tvx5RVVA0HegSg8eu86RsOGv9wxt8m/aHtmeiTzsmqYGGgbwXoLXpxN4R+TeBYdqFNfZtA/40zemHxK9Kv0d6ocjZGOzPjT583vcOia06PKsT5UNr6jGPJkmtjXRxFbehdBo3n41Y5r3SgNyhWtkjLAA08XmQjZT2pafub1Y6x7VLk+NJW65amTzgbldZhEXbnpezDuUX4TltlWJo+lNuGzl0Q6ODbpbaDzzXB1IDvK9L4pt4S4BLwecBHpPqexDVAN85YoXxr8GsOetkqTiKb6PhqrFOnAGezgxPBW3GO0U8EA8DGTtWSk/hrD34Evoi3BUtuIKUD2uoMdL6Ggzj5lIJzQNxjgrzWsBsNonOho35qOLVgPyf4C9AP5sTJLxQ8BDZM6xjb2YDbL6VfV32OM4M/gFdBnLxNwT6gXdo2Q3bUXwHcDL4GcfImBSeDuUM+CumotxLoA74BcaLzdDnQZKkygnML6GWgFh4toOd4hAsL6BmuKfi6H/hScOJChWPBeL9iQl431MPiukbZdgl1k4rejfMZ6am8GRiZ5CRQdh06/0dTIpeZj/i6JdCGVJ8AraoqJvhvB/4EQpMK1EHRz7ieARI5oNwPIj+kHQh19wdjQRZ5AGPtsCQKNgrovryPYjWgYJ1WtBjMHGip0wlcCSaBtDIGw4MTB+YUYqs2NFmeDNKKzuspjpvmpD1Db6bCEsZAzTJwb6Dny/ChXz2g1+pGK90HKbsKaGs8rWgL9Sbq6utXrSa0p0cLz4IlMzZ6JPZareuXz8ohN+BkWsCRtplfo52HgVaEmYNHwGezCn/iXef4QqBHIWllDgy1K/YMPhZIWymNHf60Y3Extn1B3I/nxLnS79oPof7ycQYJeu06vALWSLDxi7ZFMYj2ZvcL4vLYakv9OXAcmCXOLqDX6lmr+dvBrIHyZpXTxtEotZ2fVnReLwm1YQE9LYVmZwxULwNP0rXQM24978sTbgK6OT0Kds8ryM/oh1L07C5OtJ3bK66wnHraORt//wBxNzwF2N9AnPSk4Mq4wix6nmEqkPwzoc7OlN0NtDX7GDgSLJZgX7CI+goKj4EkvnWuks7XxpQroM3HsVyiyeAfY5z9iv5NMBB8GWOzAvon6FPWiYZW9gpovuiaDU22Iru1SFwbZZKO9GkhyvVjO90T7HTNTU0o702ZdiKCkwH0GocmDHFtjM2Vv8bxJxAStXGdWzALjjdDUUpgX9l1mDG9Ju0nnQTX3fxuJkO6I030yGBfCdMXuBmEbriVaMt8NhgDXFvfc43rxSO9SOOKVhl5gq229t5GuXleAf8whLxuDg+B0UD3BK08fw904/ZXG3qW9wj+xlMWyX9JHBFlcketKnXzikQvRh0fZXLHb728m32HjIKVu8qW/fVAwVMvC02gL0uQ3hr8HfjjPoLyf2H3BmWlyp9xIP/7JTgSV9vlcC1t67mveL2NPozimEU0Tv9cqb7O0UVAfuVT50vnX6vfY8EywJWlyXQB37nKYtKMRytKwRd9x/4koO/ZN9/XsVeAPAscCdwAp/7cA3qCYmQclfRDPYPBh0C8a+Wua/AA4MuB9OVK+qbJRlAol49+QH3z5V0Ul4DH8aFJm2y7gv3BH4A/OVkN3YJgDGgW6mlyeh9YoVk5PSHObgP6GWed32ahznpkLgObNCunJw6h7FXstXvU9HKBnouZVJaBgttkNG/P0KdfoNoStmfoOS7SHuAs9HLQFXH1sVdAjuQsEu6NNq8aZd2BgqYv++QZBjJUGOVVilttBGpPV1F/d6CX3STa1tZNMiiUzQs+Br7cFKxQpBLnR4OkFwn99pUXh3pksWyaZrHrBUKi3++fPc4HZVrE6DMUPZfVc/cNE+xTP0PHz+IgFDOuQK9JRaxQvh7Q819fdglVwij0DD2q+zSJhUP1pKNsLxB69n17XJ1cvbOjBrzj38jHjo+y+YFeaotkGAlNoloIer3M54uuDU1IEwUbfVZ90eRieowhETo5fgXLl8aABfTp/J2XeLXmCjG1gJ6GKMcGzp4NXKK3OCYtktgrqJ/RoiCgwO7MgP8+AdM8FXVKDuhyiB8F9UdA7MQjahibDYAv2sEoq9CAAqcC+wi/sQJ5BdrrQce4DlHWAQwFvvRF0S6unqvHbk+g3ZvYYC57yrME9H9h70t/FLHBzuuTAq0vr7s2URqjuID+A2WdI7u4IzYX+g2RV/AL8od+URCacJwe14avp74C7jAQF8z1/soXwJeDfV9xeSre7Vcmf1qTPQkL6AF2yqyygD6dUAvo+ReWtr7LIrh9J991U05bsomCVfDm5lfCbq2A/+d8Oz9PnbIEdPlN29ec7bhAf7VFWnahHQXfbcA1QF8tSisvYhhcaaPfO+BEAX62LAPAXo8HEgWbVAEdOwUj/1sR2jkp2IbbAez1kp4vemSSJxjEBfRT8wxjMrn6oetgpVAV7PVuiC+PhWzjdFTWtxGSdg5C5/XZOH8hPf47A00KXdFjh5KenYfaMp0xYAy0DQOhm+qXhbrCszc9twsKd4vFgALVKRgIvvjPDP3ysuYL9HUe+rkx0PPyq2hYzyl9KfY9HN9PXp5+TQJPgmMoWBLoRafzgd4pSJKNKbw0xmDXgP5c2hgf0MeqsM97fhtrmK6gJ2adPNOHimjjbs+HsgW3m506em+goOS4Ck2aF42pHOI81Q5W5I82p4GxUT5w3Dmguz6gi1Xh/ysKB3oG3bnu5ym4feVVsqwxYAxUGQN8kPVMNrQF+UWarlJfwW99sCbQizwR5iOdJKlW90kOiimjvytTTy8JRf3UUYG0kFS8v7qh0wm9mSzoMUVXjgeD40Bop05vwt9Ivbcod2VLN0N6EtDLWm0pukZ80XfC9S2LLDJnwLhLQBdS6eXiYaGCGJ0mVVt5ZS0mooxB72Ws7tl9wnmJfYHOs02bDXF4FO0fltZBzm45z16PPJZSQNdbrKHZrGcfm9Vbd8fGliYXnEtxoVls5OHPJNaIMhmO32J7dAp7bWXdksLOTIyBamNgh0CHFFieC+ibVdxENiVzKNgJtLjJNRtWQYK+LkM3tALWKmp5UBNCQPiEjp5B/7Vr8CAIPc8+CH1zQMdW9+WFgSv6L3o/uYo2SId2gbR9LZQqoQlpyOdIeNDkJq2EOOsQqLx4QKdJWbnFP6/y36NMjXSeBXLuL8VZ7uIrNqAPpP2BadqnHc1yiwnov9LGPYXawL9mzxbQCxFl5VXFANdtOzqkoOyLvsoyxlcqn/vMnkPydKD6VS30txcd/BeYp6o7mtA5nQvGsS0m/wP+8+Id0Z3gVF+ItH9exjnlbZV0v35Y7j5MTunw15R2kZkmtmkkNKH4Ok3FtDacfy2c505rX4TdZM0ETYwBY6B2Gdibrmur3Jek54x6hrmnX8HL6zngBw6Gke7v2VQ8y03wSBq5rkBDWoV9CBQso+N5pLuBqhGC+k+MR18lvNjrlB/gQ6vKObw6bZHVbmel5PNKOU7p9+eAXVk55/xP4fz/QDuVmph+bgE9cBZNZQzUAgPcHLR9d0mgr9+juyGgb/r6F/pQMH8MvSYB74EPuPl8x7FZaGvF5kwrJWhT26AXBZpTH+8EbwD1dbRvQ91rfF2V5DVJ8kVff5sv4pyjvjr1C0ZuQFkWHUXxLzH6TiuQD+34XEo7H5ehrefL4KMUF6GxdS3FYUxdteMGdE3e/hhjm1U90gJ6VsrM3hioAga4uesrWA8Af3Wn3p3PjT9uNaUbsC+/x76vr/TybfHc+q/0YS6vHzeTP4L+xm7Rwo3ehwk9E/VctUk2FCS0Lawbuytvk9nYUeilrXXBa46utZOvBhqcwLm4MaCvNdVIOqzPjPtNiA25luZhfFpVl0vEoTs51vWtf/X6VjkaaF8OJ+bDGDAGWo8BbjJ6lvkocG/4UQc+JXFVlHGP1FNQ6OLqSD/BzaRQMFeVNbx6rZHt7jWioHcc/Y0N5jn71Tn6z6A9V8Vl4bAH0LsHxYpeRPRlXGBMD/tG5E8N6BJV9DX0AlhinYTCZyj7zSvfr5g2iqnjtVvWLPxPwaE+U65o0ny8q0iTLjC2AQEfejE9s4TasYCemUarYAy0HQN8iBXkXgFbBXqhgLcbN6cJgTKpVg3o3w/o8lS0qVXLyXnK9BndKF3R/48oGGyx0QtEK7kVSX/G2H71dKHseSFlqTr61AMfj4ALSOtX9nTDTy3Y74bxHoEKzwV0d6Ob6On1U7C7e7rYLLadKdQPDm0da5ShIMf9fV4VTRCv9nSJWfqzDAb/5bh+omHrF/YJNHk6/dQEMZXkbDU2/9qN6mvS8HWUyR2Pw347T5eYxf5ADAZxzNvBsoCeSJsVGgNtzwAf2vZAP5qib2sMAe6WXdRBBc59uOkmBeg5I2PnuBF+YwNsruxi7BXUi5HRXqVO5EPbzp5Z0z9j6egpV6I/if2gvDd1enr1Ss7itwdOFMxnzzk7kePr6ENfQ8uZzDhgp2B+Gwhx/eAMy+kpzuNIUn6gVN3b8FVwfNgsh+1zYGWg378vS1DH11nAn2jot8kV+EJjw3yGYLMeuReBHuE8Sb5qgjqcP60+AVd0vvXTtuIxUbDZCINngMY2kHyLoE4bP1N2PnBFk1f9nOs6rjKUxka/RKdrT4+e1N4T5JuDugX0EGumMwZah4GF+TDqt59DOAe9fu9bL6qNBboJ7h3TrUnoD+VmoRfbkuR/gUIFpCtpZw6/DJ2CwiBwiF+WIa+3433R6laBPVYYi7Z2P/MMFOB1c13K0+tlvznBtej7+mWl5vGrG+Z/QBTMI5fdSLxMuQK7vmuuf7DUDcwF9M861gO9wcvY9QPNN97IAUdNwO518m7yPDI+f5qUPYrP80ALf+jmA3ok8C6IAoq4fgh9wYkAdonCeRmOgR+QVOcC8DxtBFez6DUZux6bV8DiQDI3qKqgTn8ULLXT5YquN53nY0AHt0BpdEuCK0i+ABaUDlkEDEQfnYMmZe7PdRzfcBWk9aLcEPkBSucJuvZgO5SDweUgit36/DYH9VnImBgDxkDbMKAP/d9KbFrfT96TG+3zhfxgM4ybguw282yPJb8PZVrRjQK64a4KVgSl3iOG4sOXHVG8T3tPcdQLYWuCC+hff46u3ErmHFdBWquSodTVakoBXxORVYD6G9qBQF2a0C993ewovPQBLW7o6LSyKri6wsaXiSiOwr92V1oI+u9od2cKNCFQ8ItEfTgDnEz5QI6fAt3glwU9wGzAF02G5vOVRebPpd4awH98sAm6t+iTzosmKpqMdAZa3a4NQqJ+VeS8hRorpIPzD+j/77DTRFqcRjIviauB/nHUsxxHAvV7ebApmBn4onJdn3lCGxPxsRvK14DuAZHIx/HgcMo1+RaHP4AlgXYyFgMh0QSg6XNa6oc15Nx0xoAx0DoMPE8zB3KDGJ6hueOwHQK0anNlITK9XEWZ0jfh50/Av7Eth06IJHQvuoTC3kATC1dmJbODq6h0Go7v4ib7Oe30BUuVob2p+DgAvy8m+aJcz2MV1PsB/3GDAncaHn7FTtfJ/RxLFvzQpWkH4EjBeEfPoYJg1xy8ohbZcWj2wt+gFiVtqKA/AxjfwXRB166uNVc0KdrTVcSkx6DfFV/+SrzJHL3+iY9W3HqMs3iTcsYfnde0k0RdP7vj7ztVd2cgypsYA8ZA9TOgmfvOfIg3B8OzdBd7bcXqhvRbhnrjsT0ig32zKe19SebiZkWGBHXV7jbA33Yu5OUaDDRpKavQH02gVgPXA+0sFCuaGGyDv7it9jy/uXa1QtOqLavofK+Lj7IE86hx/GmSsAu4DBTDxePUWx0/gzhWndCv2+hUTzC2iM5pbGvg49WkupS/Q3l3kGgX40Pf9DgL9MCPJkZNYgE9YsKOxkB1MzCU7l0OtgK6WQwotrvUfZS6ejlJW35Johu12ukGdIMrVs6n4hkg7u37WL/0dSSF2q69A2hVmyT/pXBr6hzLUTe8sgu+fwJH4Xh1cCv4EaQVrdr0iKUbPp5JW0l22H/CQY8mtMPyNSgkozE4BXSn7geFjIspx+9UoDZ0LQ1K6eN17PTy5vZAk72qFfr3Ap1bHvwdaAJTSHT97Q92oO5XhYxVjt0XHPQc/BDwOSgkekxzN1iLuvrve1PcCrO4GUsbA8ZAxRi4D88fZvCugKSbgoLAGD64aW7iqd3jT6t8/cvFzTnuCBQ0FwS6cQ0DWj3cg90IjnrxZ2YOByntyPdOOjaJD43l7/h4gGMvsGIOs3L8Fqitt0FQqK929qe+gqHqbwQWBe3AcPAx6Iedu9I5D93CwJVU/XUrxKVpS/wdTJ+O4CgOtdJaE6jNeYFutNoG/Qa8CQaDl6mnG3JRQt1JVLyaNm/gGJ23rqQXAbqX63p5AzwNBmFfaAKEWZMcw9+OubQOmfpIO2pzC/qlvuwCtJugPs0PNOEZBV4Bj2H7Ece0on4c5Bk3bS17uqTsgxR+4hmoL6mE/v6EoV54VFDfBmwHlgIan65rfT7l7/EcDySzCfV0nm6hDU2aNwY7gRWB2ugE9BnRpOwl8Aj2ZbuO8ZcvdEJvcRYrW+R7i8/RwONFNqKLqaDgW2+mVkrmKtQBGtYbspWUboX6oHI6MKqCndBNtqDQvl48qZQoWJkYA8aAMVB3DLSvuxHZgIwBY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TFgAb3+zqmNyBgwBowBY6ABGbCA3oAn3YZsDBgDxoAxUH8MWECvv3NqIzIGjAFjwBhoQAYsoDfgSbchGwPGgDFgDNQfAxbQ6++c2oiMAWPAGDAGGpABC+gNeNJtyMaAMWAMGAP1x4AF9Po7pzYiY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TFgAb3+zqmNyBgwBowBY6ABGbCA3oAn3YZsDBgDxoAxUH8MWECvv3NqIzIGjAFjwBhoQAYsoDfgSbchGwPGgDFgDNQfAxbQ6++c2oiMAWPAGDAGGpABC+gNeNJtyMaAMWAMGAP1x4AF9Po7pzYiY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TEwS/0NyUZkDNQGA9OmTVuDns7Srl27N9weo5+d/KquLpD+jHrfuHrqrUx+TlfnpX+mzgfYLYF+UfAh+Z9cG8pmI78a+IqyESpD15nD0krHSLNtTLl8bETZcuAh/P7o21G+B7qZKbsvKkO3H+mO6G6NdO6R8mPIj6P8nkiPbkvS6v8d6L8hvzzp7cFr5AdzbCHY7IRyWdAXm+/IL0B6xzyMRcwAAA2jSURBVBaGMxRq89Eoi/1upOeO8rnj5xxfx+4HT9+Upc7WJHT+24HXwHPYTuPYLNj0JvMt+sekJK8+za90jOg8PIFd1P8vyD/t21K+DLrNwEeUD/HLQ3nqrIh+TaBz+Bl4m7ofcswT7OZBsUueMj8zlnpP5qta5vDTFe1aYAXwFXiZev/lmCfY6RrRdd0/r4AMZRtw0PnvT/n3UTn6XqSnoOsX6dwj5YuRXwesCn4Cb2H7MsdYoc4OFIr3ftj+HGtIAbbzcVgXrA6mgv+Bp6k3hWNQqDMXBeqT6uiaeQe8QR31r3xCQ71BsbJF2p7QwONFNjIqTRv4nqtI/2mq6WQkCk52SeOoBJtuiR3IFeJ/VAltFKp6Xso+nFvIUQnlw9L0odI29L8rmJobh4Jds6BbI6dPOhzYXCGXwHhwUgWVy5TjBTm7zQM+VsuV3RCVkT86p4s7XBvZxh2peGuusgJDC6FsOBjrFpB/O1fnBFcfpSmbCBQMm4X8Dbk6Tdc76YXABJBnF1VA3wkoiH8GdKMUP+uBJMnzheFHMcaaUGii0izk1d4zAXudO02ymoX8ZPBqpCD9OkiS52WLQXsg7jRuBeA8QfcU0LW3Xl5BIIPNguBuEJJ7UWqy1yzkVwkZOrpBzcaBBHbzgT6OvZtUDNBktFnI/wzebVY4CfTRtaAJXrOgHwNa3AfQzQp075kEfBGfazc7cRLoFwW6FiVHO0V5ScragT+AH4AvI1HsmFchl0HfC4z1K5D/Cuzt1rEVusuGpY2B1mPgKJpSANGsXDcBdwUwmrzKIzmdhG5kx0YKjqHV5kXoF87ZaJJwALgdvJTT5QXMnC7L4VaMhwQqtFg5BWxKUV3MjUsrbJejVP6o8zV1H8J4L47dyL/nVVTAnRdcQtk0r+xB8ld6OmVDq6If0bsr067k/w5uz7X7KWnJKUC7CA+AK8A3YC+gFbN8JMkRFM6ZM1BbJwO1Ea14f1AZ41CwVtkz4GKwB2gS9DuT6Aluxy5vYjLdYsZfbBcg9zZYHNwDbgVDgcamCaVWx5tgtwa+vibtyqNkdD360tRHX6k8fubm8AboAv4DbgIfAU109gFHghewW5H2JpIum+BTn0XthOjciJcLga4V9Wl78Eegtleh7RGkXTmcTAeglbk+y3ET3KsoOwZoMnE8eB3MDHTuTwP98b8V/geSbhLy+uxfAL4CR4FXgK7TDcHZ4B5slqfO+aRLF5zZCh0SCshchZimvq3QcyTBRV2v0BnfbOBb8DJ4APwGFoq7Rih7DWS6gWF/CJAc6vtFV+wKfX/fV9o8bd6qziBZV+hTqKPVz2jgrwYLrtDVP+ptDST/9PuL7mmglfBiURnpaIWugFtQsNcKfZxviO4gIGneYSCt8y1Zyrf389jkrdDdcsqOkBNEk7agUPZQk8W0aZvLgHQH8CH4BeStdEMOsLkPSJr779qhP66plDFFevLRCv1fkS7tkbrRNXJGqA7lPcGebhn5sqzQ8XMykNwBFGTzBN2K4KQ8JRl0swBdm/8DfwESBeg8QbddU8n0XbRoUtZsQ9kC4BzQ3DbptYCu8Y/Bgs3GuQQ61dG1J5u1pW7vG1neGDAGKs7AvrQwH7gRaBXSEbQIvOhMZpppAiScChYHd3Hjar7hZSDnaWyHAy0+Zo3qkV6GtFZkj7LC+SLSl/H4Xc7XHI7PD3LpU92+OOXlTIq3SeBy2tK9XqtHTai0GzGaY6xgr1VyLzAQ2+DEBr1WnNoF2AP75TgWLdTXhPZAMARcEHJEe3rO3Dx5CNmUoBNXWgUfTRtTfD/o9L7B5b6evHZKdG3qs3wzUF3x7Iv8TwUH4kcr+TxB9w04C7htn4iRVv6Ho28xYVQdyg7L2TRNNmYhY2IMGAOty4A+8D+Ae8FvQFt4WnFdxIdUH/rWEj2bW9NrrHml6umVPRT7zTz9lfT5fU9X1iz+r6DdjXCq54Xngv/L0gD1qT7t37m6ugHfn6t/MMd2IG41KX7WytlGh5vxd2uUcY5qpFMuL58KcH/L5Z/LHXW4GuwHjtWROgM49gMD8OvezFGVJvjTyu4avCgwaAtevGnichEoJNG4HylgqPKtgOw/dWw3pu3rnbySD9CnpzxdlI3aewybrJ+BxQNtye+mkfOkI3UXoVx4kLZ/SLINlOmzrEnnbdTVrpu27TXBWYT8GMden7OP0X3s6LTC10RLE/pI9LLexFxGnPwEno8K/SO2egygPjfxZwHdZ8jyxkAFGeDDtx7u1wXX8mH8VU2hU7A5B+wAdINvLVFQySI9MBZcUTB631VUKH0oflcHp8PXYLjrn7GdW7A/GxwC7sdHe44HAQW4uKCllfWiwJU53YyTnp/0eCcfJa+gry9FGdJ6IUs39yOAArtWpYK2TrelfATpcoquq9+Di3NOT6KNX1I0oAAnGTv9EPs3ClqRfWS4EgnBlU/IxAX0iOfIn1uvUFrci89ipai2OV8r0uCW4E44/TbX+E0cdwKHgfOkw64Th/nAe8p7ovvBK45uIGn5lIhTfXNhWlMu/o/OURP/uqhNjAFjoPUY0IxeMi8f9L8JpJdo0oS36nJFFTkcjNduHvZIaEmBR4uAZnCz0YokjfyWM9LNLSTSRzYtymnnZ5R7Ak2CboO3ZVsYJSio/znFj4Jtqast0p5gKXALZXEr4z6U6YUjF1dTJyTq+zU5PJMzuIy6Wh3nCbqfgLa916FgaaB6Cg43grIKbWjbX9eY5A1wW1Oq8J8o+KxWwDQqj+wj81tINF8nufSlUWHgGNWP/AVMYlWaUGpr2oeCaxrRY5DJIGvbekmtHViBa0ovp91DWp8RiXbcZlaCc6BrYyhYVXlPFIyvBaFzL0664Md9ZJNXnbLZUCwHmvizgJ5Hj2WMgcoxwIdPK4l9cy38juNZOfwhp1OwyRSocvWKPQzjZvO+CxzpxhMnU7HVlmAz4gwD+uiG3d0vY8wKrAuDyMY3acrT7v9IHA7mBfcD3UyziG7wuucdBA4F08DNoBzyC/07VsDZnmAc0NZrxyTn2I/M1dHqdaMk2xLKXszV1c6GxpxG3sboR6CXKxcKVUC/IPrDwE/gLc+mxbVSoG0FZa1yD8Dv4p6vpix6PdbQufdlGr4n+8Ao1Vipp4D7KtAb+5v4zqM8ZVplNwnp2UkoeGsyqEC9Uw7bcZwANEnfGUTyPIkFqSe+moW2h4NjUJzQrJyRUB1dr6fOULVIqUwTB9naS3EiwcQYaCUGtCLWSlQfXgUxF73J68N7JKhHGcCgfgZ6k1criibJ3RgVaCV3TT/E/+XmJ5urgJ4ZagWYRbRC1xa7JlC7gmfw9xnHsgo+f8ChVsXLgJNBszDerfybOvnOGCg4ftVs2MYJxqAgrd0FBfN+9HFpt0vkde32A+r7ydgr+Bct1J9IZa145wH/wX/zNSKn5OfmcCcYlEtLXU5R21ql34n/vIkVeX074FzK9CZ711yjmpBrcnEqfZ/DBbrlgQJ9tBtHcqY/A63GL8NHLyk82crLK3sR+AicQZ0jpHAFna7jM8HH4EKVZf1AqI6JMWAMZGSAD59WkwrW34N/cQP41XVB+d3k/w60ItLbrlo1VJvoJbEVA536jP7eEtA3qygfQd3jUPwb6Ec6FAwUNLYHXcBd2NzBMY2cgtF6YIM0xpEN/vUVOPXzjJzuX1FZzHEH7EOrxfH4OiCmTqS+noTG+39qE3s9O9c1cD5Yn7SuhZeAJnF7AgWHs0HVCH2+hX52o0MK7O+TfpbjJ0BBbQswJ9A7AoV4xKyw4Ode2tB7Egp+7+baU0BbFOwIFOyvAZoYllVoW+0pQF4L9KLZQI7vAU0ktgW6Dp4AY4BEwVqf0T7KuIKvUdR/FN1OHFcgr5fhxpHeF50+5/o64BCOr4PJYH2ga1kTQQXxJqHOeOz2IfMguJ602nylqXC6vbgaDvbBtul+Uo6ArllH03KfY1bRzS2tiNzZ0ho7dl876aSkZlTFjiPJr8rku5B8g0Gl2lfbvxTqQK5cF9qwlLZZzYanrCC7SnERfSBTdqVsZtvgSTdCvRWeF8zVAjr9GMiNJHXD3xvcBqpNdqVDgi+6+SlQJgpjvJUxDsXoYrAXmBUoQOhGpQDoy5coWnzm8TMJP6rv3mCjuvocjQATI4V31IRCwfgnoElFSPR5VbkeA+i8+eIHFOU1lmahj/oO+Z9Q9AV/BiegQ9X007Snkz8QHA+mgeHgKMp9DrTq9dtC1SST+KtyHQvJVAw0nvGFDP1y+nQyff4P+r+AjcAuQBy/Cs6jfBBHV8Sd+vWbq0ybxt+ZtDcA+7NBd6CtbPV7MLiccpW5onHF3dtUT+UavytBLvDdh7ZfwPACsAHQqln8vgvOBv/GBpOm73wvR14/Ffwtx5Bch3Jz8Htwpgyw1e7CKiT/DjQhOkpqMApcAVp8nZA671BHgfuvYAdwCJBoVX4p+Cs2zeOXMxNjwBioMAN8KOeiCUG/zR282WGjoLAg0PNYzdabBL10HdB9mVMVPFBndoy06vueenkTCMrmRq/V1TjK8gIfZZrkdwbNfUA3B3mtjuJkAn50k08tuXZm9fuW2kEdGOZ41bPmzIG2rYZPn+emvwrYrSK0p+vuR9rUxKdVhbb1ef2VtjVJKbvgX59Rnf/g/SDUIHU6Sk+dCaHy/weN5Lia9jbZjQAAAABJRU5ErkJggg==","css":"/* gitops default css */\n","favicon":"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","json":"{\n \"graphql\": {\n \"boardCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\",\n \"plural\": \"Cases\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\",\n \"plural\": \"Experiments\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\",\n \"plural\": \"Aliquots\"\n }\n ],\n \"chartCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\"\n }\n ],\n \"projectDetails\": \"boardCounts\"\n },\n \"components\": {\n \"appName\": \"Generic Data Commons Portal\",\n \"index\": {\n \"introduction\": {\n \"heading\": \"Data Commons\",\n \"text\": \"The Generic Data Commons supports the management, analysis and sharing of data for the research community.\",\n \"link\": \"/submission\"\n },\n \"buttons\": [\n {\n \"name\": \"Define Data Field\",\n \"icon\": \"data-field-define\",\n \"body\": \"The Generic Data Commons define the data in a general way. Please study the dictionary before you start browsing.\",\n \"link\": \"/DD\",\n \"label\": \"Learn more\"\n },\n {\n \"name\": \"Explore Data\",\n \"icon\": \"data-explore\",\n \"body\": \"The Exploration Page gives you insights and a clear overview under selected factors.\",\n \"link\": \"/explorer\",\n \"label\": \"Explore data\"\n },\n {\n \"name\": \"Access Data\",\n \"icon\": \"data-access\",\n \"body\": \"Use our selected tool to filter out the data you need.\",\n \"link\": \"/query\",\n \"label\": \"Query data\"\n },\n {\n \"name\": \"Submit Data\",\n \"icon\": \"data-submit\",\n \"body\": \"Submit Data based on the dictionary.\",\n \"link\": \"/submission\",\n \"label\": \"Submit data\"\n }\n ]\n },\n \"navigation\": {\n \"title\": \"Generic Data Commons\",\n \"items\": [\n {\n \"icon\": \"dictionary\",\n \"link\": \"/DD\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Dictionary\"\n },\n {\n \"icon\": \"exploration\",\n \"link\": \"/explorer\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Exploration\"\n },\n {\n \"icon\": \"query\",\n \"link\": \"/query\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Query\"\n },\n {\n \"icon\": \"workspace\",\n \"link\": \"/workspace\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Workspace\"\n },\n {\n \"icon\": \"profile\",\n \"link\": \"/identity\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Profile\"\n }\n ]\n },\n \"topBar\": {\n \"items\": [\n {\n \"icon\": \"upload\",\n \"link\": \"/submission\",\n \"name\": \"Submit Data\"\n },\n {\n \"link\": \"https://gen3.org/resources/user\",\n \"name\": \"Documentation\"\n }\n ]\n },\n \"login\": {\n \"title\": \"Generic Data Commons\",\n \"subTitle\": \"Explore, Analyze, and Share Data\",\n \"text\": \"This website supports the management, analysis and sharing of human disease data for the research community and aims to advance basic understanding of the genetic basis of complex traits and accelerate discovery and development of therapies, diagnostic tests, and other technologies for diseases like cancer.\",\n \"contact\": \"If you have any questions about access or the registration process, please contact \",\n \"email\": \"support@datacommons.io\"\n },\n \"certs\": {},\n \"footerLogos\": [\n {\n \"src\": \"/src/img/gen3.png\",\n \"href\": \"https://ctds.uchicago.edu/gen3\",\n \"alt\": \"Gen3 Data Commons\"\n },\n {\n \"src\": \"/src/img/createdby.png\",\n \"href\": \"https://ctds.uchicago.edu/\",\n \"alt\": \"Center for Translational Data Science at the University of Chicago\"\n }\n ]\n },\n \"requiredCerts\": [],\n \"featureFlags\": {\n \"explorer\": true,\n \"noIndex\": true,\n \"analysis\": false,\n \"discovery\": false,\n \"discoveryUseAggMDS\": false,\n \"studyRegistration\": false\n },\n \"dataExplorerConfig\": {\n \"charts\": {\n \"project_id\": {\n \"chartType\": \"count\",\n \"title\": \"Projects\"\n },\n \"case_id\": {\n \"chartType\": \"count\",\n \"title\": \"Cases\"\n },\n \"gender\": {\n \"chartType\": \"pie\",\n \"title\": \"Gender\"\n },\n \"race\": {\n \"chartType\": \"bar\",\n \"title\": \"Race\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"Case\",\n \"fields\":[\n \"project_id\",\n \"gender\",\n \"race\",\n \"ethnicity\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": false\n },\n \"dropdowns\": {},\n \"buttons\": [],\n \"guppyConfig\": {\n \"dataType\": \"case\",\n \"nodeCountTitle\": \"Cases\",\n \"fieldMapping\": [\n { \"field\": \"disease_type\", \"name\": \"Disease type\" },\n { \"field\": \"primary_site\", \"name\": \"Site where samples were collected\"}\n ],\n \"manifestMapping\": {\n \"resourceIndexType\": \"file\",\n \"resourceIdField\": \"object_id\",\n \"referenceIdFieldInResourceIndex\": \"case_id\",\n \"referenceIdFieldInDataIndex\": \"node_id\"\n },\n \"accessibleFieldCheckList\": [\"case_id\"],\n \"accessibleValidationField\": \"case_id\"\n }\n },\n \"fileExplorerConfig\": {\n \"charts\": {\n \"data_type\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Type\"\n },\n \"data_format\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Format\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"File\",\n \"fields\": [\n \"project_id\",\n \"data_type\",\n \"data_format\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": true,\n \"fields\": [\n \"project_id\",\n \"file_name\",\n \"file_size\",\n \"object_id\"\n ]\n },\n \"dropdowns\": {},\n \"guppyConfig\": {\n \"dataType\": \"file\",\n \"fieldMapping\": [\n { \"field\": \"object_id\", \"name\": \"GUID\" }\n ],\n \"nodeCountTitle\": \"Files\",\n \"manifestMapping\": {\n \"resourceIndexType\": \"case\",\n \"resourceIdField\": \"case_id\",\n \"referenceIdFieldInResourceIndex\": \"object_id\",\n \"referenceIdFieldInDataIndex\": \"object_id\"\n },\n \"accessibleFieldCheckList\": [\"case_id\"],\n \"accessibleValidationField\": \"case_id\",\n \"downloadAccessor\": \"object_id\"\n }\n }\n}\n","logo":"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","sponsors":null}` | GitOps configuration for portal | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["portal"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["portal"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["portal"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["portal"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["portal"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| gitops | map | `{"createdby":"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","css":"/* gitops default css */\n","favicon":"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","json":"{\n \"graphql\": {\n \"boardCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\",\n \"plural\": \"Cases\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\",\n \"plural\": \"Experiments\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\",\n \"plural\": \"Aliquots\"\n }\n ],\n \"chartCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\"\n }\n ],\n \"projectDetails\": \"boardCounts\"\n },\n \"components\": {\n \"appName\": \"Generic Data Commons Portal\",\n \"index\": {\n \"introduction\": {\n \"heading\": \"Data Commons\",\n \"text\": \"The Generic Data Commons supports the management, analysis and sharing of data for the research community.\",\n \"link\": \"/submission\"\n },\n \"buttons\": [\n {\n \"name\": \"Define Data Field\",\n \"icon\": \"data-field-define\",\n \"body\": \"The Generic Data Commons define the data in a general way. Please study the dictionary before you start browsing.\",\n \"link\": \"/DD\",\n \"label\": \"Learn more\"\n },\n {\n \"name\": \"Explore Data\",\n \"icon\": \"data-explore\",\n \"body\": \"The Exploration Page gives you insights and a clear overview under selected factors.\",\n \"link\": \"/explorer\",\n \"label\": \"Explore data\"\n },\n {\n \"name\": \"Access Data\",\n \"icon\": \"data-access\",\n \"body\": \"Use our selected tool to filter out the data you need.\",\n \"link\": \"/query\",\n \"label\": \"Query data\"\n },\n {\n \"name\": \"Submit Data\",\n \"icon\": \"data-submit\",\n \"body\": \"Submit Data based on the dictionary.\",\n \"link\": \"/submission\",\n \"label\": \"Submit data\"\n }\n ]\n },\n \"navigation\": {\n \"title\": \"Generic Data Commons\",\n \"items\": [\n {\n \"icon\": \"dictionary\",\n \"link\": \"/DD\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Dictionary\"\n },\n {\n \"icon\": \"exploration\",\n \"link\": \"/explorer\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Exploration\"\n },\n {\n \"icon\": \"query\",\n \"link\": \"/query\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Query\"\n },\n {\n \"icon\": \"workspace\",\n \"link\": \"/workspace\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Workspace\"\n },\n {\n \"icon\": \"profile\",\n \"link\": \"/identity\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Profile\"\n }\n ]\n },\n \"topBar\": {\n \"items\": [\n {\n \"icon\": \"upload\",\n \"link\": \"/submission\",\n \"name\": \"Submit Data\"\n },\n {\n \"link\": \"https://gen3.org/resources/user\",\n \"name\": \"Documentation\"\n }\n ]\n },\n \"login\": {\n \"title\": \"Generic Data Commons\",\n \"subTitle\": \"Explore, Analyze, and Share Data\",\n \"text\": \"This website supports the management, analysis and sharing of human disease data for the research community and aims to advance basic understanding of the genetic basis of complex traits and accelerate discovery and development of therapies, diagnostic tests, and other technologies for diseases like cancer.\",\n \"contact\": \"If you have any questions about access or the registration process, please contact \",\n \"email\": \"support@datacommons.io\"\n },\n \"certs\": {},\n \"footerLogos\": [\n {\n \"src\": \"/src/img/gen3.png\",\n \"href\": \"https://ctds.uchicago.edu/gen3\",\n \"alt\": \"Gen3 Data Commons\"\n },\n {\n \"src\": \"/src/img/createdby.png\",\n \"href\": \"https://ctds.uchicago.edu/\",\n \"alt\": \"Center for Translational Data Science at the University of Chicago\"\n }\n ]\n },\n \"requiredCerts\": [],\n \"featureFlags\": {\n \"explorer\": true,\n \"noIndex\": true,\n \"analysis\": false,\n \"discovery\": false,\n \"discoveryUseAggMDS\": false,\n \"studyRegistration\": false\n },\n \"dataExplorerConfig\": {\n \"charts\": {\n \"project_id\": {\n \"chartType\": \"count\",\n \"title\": \"Projects\"\n },\n \"_case_id\": {\n \"chartType\": \"count\",\n \"title\": \"Cases\"\n },\n \"gender\": {\n \"chartType\": \"pie\",\n \"title\": \"Gender\"\n },\n \"race\": {\n \"chartType\": \"bar\",\n \"title\": \"Race\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"Case\",\n \"fields\":[\n \"project_id\",\n \"gender\",\n \"race\",\n \"ethnicity\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": false\n },\n \"dropdowns\": {},\n \"buttons\": [],\n \"guppyConfig\": {\n \"dataType\": \"case\",\n \"nodeCountTitle\": \"Cases\",\n \"fieldMapping\": [\n { \"field\": \"disease_type\", \"name\": \"Disease type\" },\n { \"field\": \"primary_site\", \"name\": \"Site where samples were collected\"}\n ],\n \"manifestMapping\": {\n \"resourceIndexType\": \"file\",\n \"resourceIdField\": \"object_id\",\n \"referenceIdFieldInResourceIndex\": \"_case_id\",\n \"referenceIdFieldInDataIndex\": \"node_id\"\n },\n \"accessibleFieldCheckList\": [\"_case_id\"],\n \"accessibleValidationField\": \"_case_id\"\n }\n },\n \"fileExplorerConfig\": {\n \"charts\": {\n \"data_type\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Type\"\n },\n \"data_format\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Format\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"File\",\n \"fields\": [\n \"project_id\",\n \"data_type\",\n \"data_format\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": true,\n \"fields\": [\n \"project_id\",\n \"file_name\",\n \"file_size\",\n \"object_id\"\n ]\n },\n \"dropdowns\": {},\n \"guppyConfig\": {\n \"dataType\": \"file\",\n \"fieldMapping\": [\n { \"field\": \"object_id\", \"name\": \"GUID\" }\n ],\n \"nodeCountTitle\": \"Files\",\n \"manifestMapping\": {\n \"resourceIndexType\": \"case\",\n \"resourceIdField\": \"_case_id\",\n \"referenceIdFieldInResourceIndex\": \"object_id\",\n \"referenceIdFieldInDataIndex\": \"object_id\"\n },\n \"accessibleFieldCheckList\": [\"_case_id\"],\n \"accessibleValidationField\": \"_case_id\",\n \"downloadAccessor\": \"object_id\"\n }\n }\n}\n","logo":"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","sponsors":null}` | GitOps configuration for portal | | gitops.createdby | string | `"iVBORw0KGgoAAAANSUhEUgAAAfQAAACxCAYAAAAyNE/hAAAAAXNSR0IArs4c6QAAQABJREFUeAHtnQe8FcX1xwVFsHfsBcUudrErKvau2ILGHnuP0fw1xlhi7LG3REXsjSjYC1gRe0ssqFQVFHtB+v/7e9x9zJ03u3f3lvduOefz+b2dOXPmzMxv9+6Zmd17X7uZSpRp06YthYsNSnRj1RuTgQHt2rX7NW7oXFu6rpaMKy+gfwHfYwrYWLExYAwYA3XDwCxlGMmm+Li9DH7MReMx0IUhD08Y9smU7ZVQnlS0A4WPJRlUYxmTmJnpV3ewfe64MMfOYCEwGXwHvgSvg1fBI0xcxnI0MQYyM8D1tjKVTgQ6rgjmA7rGPgBXcG3142hSIwyUI6DXyFCtm8ZA9TLAjXVxencG2BfophqSDihnA4uBdcARYAp1B3L8JzffRziaGAMFGeCaaYeRrrczQUevgiaQwhBgAd0jp5qz7au5c9Y3Y6DeGeDGugC4lHF+Ao4CccE8jgqt6HuCAfh5GWiVZWIMFGJgPwzOBX4wL1TPyquYAVuhV/HJsa7VNwME37UY4X+A3kMph2yIkzfxewqr9evL4bDWfcDF6YxheWccz8HNbU6+4ZJw0olBX9BwA2+AAVtAb4CTbEOsPga4qWpr/WagLfRCMhGDr4C23OfPHTkEZXa01+F/K44HELx+C1o1jnInhrqxM9wppBs6oDN+7eiEJpH/y3Gj621d8DkwqSEG2jqgaxWhD5c+ZCb1zcDUwPC+COhclVZXF7qKDOmhGWxb1ZRgexAN3pLQqF5Kuh/ombje1v82sqWunn2uAvQyql4Y7AHaA196oZgf+x2oP8EvtHxDM7BsYPR6wXIzu1YCzDSSihtGb1CsbN1IXNlYjQE+KJuBCTEfmPHo/wHmTcsUtsuDu0CcHJfWVz3aQcqLHjE31uM4s4wJPv7pcaLsHll8mG11MhCa2bdmT/U1HBNjoCEY4Kapr+k9CGYNDHgUug1ZIZ0Ovg+UB1XYDgV6welA8ItnpDeUr/N0ljUGFg1QoJcyTWqcgbYO6DVOn3XfGMjEwGVYLxCo8Q669QjMbwfKUqmoexuGeiku+jEdvWy3D/pGnzTrBTCTfAZC9/3QI7H8WparegZCJ7bqO20dNAZqjQFW53oxa7dAv8ei25nAq2NJgo/3cKDHWHeAvclPKslhjVeG8w4MYdkaH4Z13xhIzUBbvxSXuqNmaAzUOAOhl/umMaZeBF5tt5dF8PU+jvYvxRmBUBN9BcJFgL4Xr5fyNOH4FP/qc9mFNvWyn34wR+3OBfRWvx4n/MAxs+T8nUNF9b/sgn/dO5cD+iW/ecA4MIb+DuNYEcmdlyVwLo70bYfnaU/tVo3QxznpzDJA14440o7RSPrZ/GIn+bIL7eociJclwTjae7nsjTSCQ4gs5aW4LRqBIxtjYzPAZ6QLCMm91cQMHdwY3Aq+DnUW3VegL9ggS7+xvxO856Ep0KJbEVwOxgBfJqF4HuwNFPATBZvzgdp5H/wAQvItSr8vhyY6dgqpuy24G3wPQjIa5U2gm1OtYBL7p4Dbr7eiSujXAtcD9d2VzSKbpCMV9G0H13eIm489G9mflOQ3KsNuFnAIeAKEXvicin4wOBVospZasP8dcPuu9AFywHF2cDx4FbjSP3UDZpjPACz2dpnMmLaAnk+n5eqQAT4Tx8V8LlaohuHSt6VAv5g+xqkfokCrsIKC3ZCAk8XR6Y3+iYGykEpvq+vnSGOF8j6hiil0p8U6zRXgY2XwTApfkYl+kvc2oJVjQcFuZFQxd5zEcQ6g3xSQr5CkDeidQ5VT6EK7Snljwcf24MMUviKTsST+kOckIYNt6LNzIvodwAgQkoYN6O0TuLQiY8AYKA8DOwXcvMW24McBfauquBvqRTp9Bzn0fD+pL7tQ+Br110kySijTd+wVSPWcO43oHQRNDLQt36pCm9vT4CtgywwN696qlaT6vHyGepGp6g8GR4KqvE8zrjPom85jlp8b1qTsBupqp6fYn509CB8DwFLAxGGgKi8Up3+WNAbqgYG1AoPQjbBNhRuqfpxmINBz4JBMQDkcxL1cp+e5T+OnmJ2GNaibVZahgra7W+3dH9ranTYVPOYGIfkV5XAwJVSITsFuIH5S7WY4PnRvzrRt79SteJLxXEMj54GCj0JiOqP3PB7Dz6wx5UlqXTvFtpvkt+bLWu2DUfNM2QCMgSIY4IY1M9UWCFR9N6BrNRX96kJjDwJ/laQgfim4BQxjF0HbvbpPdAVaLR4D3PvGvOQfxmYdbH8hXYzcQyVNcMTJz0A37J3BQcAXTUK08lX/fHkUhV7ei0QTjv2iTO6oNp7wdEO8fFOWMa1Joi/wFz4/oTsfqN8jGDemTYFJwftkcCBwA87i5PVIYxPxSTqrTKPCzeBp8B74EWhsH4E0oknHxY7hrqT9SZj49F+we86p05xkHCeSObpZMSOha0f97Ae0+zQZLA12BLp25geubEHmBnCwq8yY/hD764DOq9pUG/rMmRTDACfXnqEXQ5zVaQgG+HwsAkKS6vlnpUiiQ6HnwXoxatmkNilfBXwOfDkzrh6G2nYOiV7y2jOhnp6Tfheo+EFcHVdPvU0DdW90beLS1GsP9HKdL3oBa9G4etJTvgEIvTR3aFw97P1n6KiaRFxvFVevGD3+7pvuOu/vaml8UWNtMDmv5vTMGxy6xPmgTC/mxb2n8fuEeqFn6NNbnDbtahKzxdU1fREMQKgF9CJ4syqNwQCfDwXAkLTZdiqd2S7QIQVXf9UWPEnYrQ9+83wogM0VqoA+LqDvH7J3ddQ9EISk4HNpKpUS0A8JNKqgm2rrHLsdgXY3XFH94OoRfVxA7+nyUY40bZUS0EMTwVfwOUehvmHTHtwBfBmFIhiY0ccF9CcKtdeI5f5WUiNyYGM2BirJQNz3b5u+tlXJhhN8Hx8oO5XtYG1ZFhTstEV9pWc4D/ltPF1SVj7uSDJQGW314fBiwE5b75WU4wLOj6I/YwL6Firs9AjhTq9gSfLre7qk7EP4eTrJoDXLCK4b0t6WXps/kN+TfhZ83ILNVGwPA9omd2UJMnpMkVYmY3hSWuNGsrOA3khn28baFgx8TaO6kfkS9yKab1fWPDdlraT8m/L36O7K2FDIftsMPm7nBq9nw2lkYMBIwbEiAkfyrefnrgwn85irSJEulaO+KdpoTZPdAo1dznn8PKAPqrAdT8FfAoV6+TCtvIGf/6U1biQ79+WWRhq3jdUYaBUGuPFo21UvG3X2GtSLX/d5utbIrkcjHb2GFNAvpZ+eumBWb793cKwKboM7tkOddKGkXgTzZQFfUcb8JgFferHsmowcudxELivFUeS/ksfQhK2YScdDdFIvP87pdLYH3M7K52Wio4tLZrl24nzUpd4Cel2eVhtUlTHwFv3xb4Y7ojuzDfoZeqFrGfqht5BLFX/SkuTvk6RCryz02KKTZ1PObIijVWhAKFUqxVGp/UpTf2nPSD91+5mnK5iljn405zUM9ZZ7JLOSEO8jIkXC0QJ6DDm25R5DjKmNgTIy0D/ga01uaisG9JVWVXJlG3zhK2ZAevZarbJgBTuWlqPJBD7tClSFcK1qV2derzOp3ifw6kTZUN20j6F+jJzYMZ+Bmlmhc0E9Stf9VU7+aBo3twEffs14mwW+9F3QuZsV0xOPYHeZq8PucPL7ujqlsdvK1WGn2fPtrq7ItF708V+oKtJVzVTTD5NcHejteej2CugrqapkIP28kh1vRd96BFEpqVWO9HhlCnAnJAryxUqo7oRinVm96QzUTECnu9pNsB2F8JXbLqDeDJ1+ZMGV0PZYVwz8l6TcOlF69pR2kX3c0X/DNc6ubvRMYPSb088zIJ0TV3qh35TyF1xlqWl8zo7PuNXd2IB/9e2VgD6r6p2sFarUPsSRXogLPcvPOoQXs1aoBnuuJ/2Dla/pi/u1vcXRzUyZAn1WWSpQIbRqD5iZKo6BWgrocWMwvTFQCwycRicHBzqq7wSvx01xVKAsswpfmqA9yfFQfIbeDn+Xcr395k4CJ2Cr/plMZyA0MfnaOJrpA+hxA7p2ALuD0HU9ncnAX65NLTTW9oq+Ia8Jg0kJDNiKtwTyrKoxkJYBgoFWwA8E7BdG15+bnI4lCT664eA50AUMIL+F75B+aBX0hqffElv3Ru0VN1YWjt5mxP7W+G5wNFtjMdFitA+30Mw00wkBXSHVMRj4sWcAvE8tVNHKkxnwSU22tlJjwBgohYFTqBxahegrbPpJUR2LEuoeRkX9WMtiOQd6RBIM6ujvytlEBz0X1b8edZ+PRmXBI7b6AfPbwE5Bg+pQhr6Hl/ae53Ok1Wiqn42Nhg43+j/h/wEbRboaP2pCqmfpruh/1af+QSFsV6Tyqa6DXNrnO2BiqkIMNMKWu54l3leIiBov1/ecfdEqYx5POcLLKzsa+Cu2gNlMemEljV2orqsb6WYaKc0KRM/Sd2PMz4KO3tj1THEw5VdwvBDbVC9mYb+y7MHOwBcF9UOAv/V+LbqTwBIgEt2U/4m/42k7FAgjO/1WeScyN4H9wT7k9UthevGv2uTLQIeWD+hCKnH6B+C+WLo/Y/2QsZ4fquDqsNNnT0Fqe6AdkO2o97JrU2tp+q+faNW1c4LT93ak9R/wtqH8dUffIomNrreHwFxe4SDqPuHpLNsWDHCSWuW33GnncVCMlOXZZFtwa23WJwNcxAoMeskoTvS76vqf0bsAPW9sFvJaGa8M/gD0u9pJfvQsfbbmyk4CvQJxSF5GuZZj2pxErxXnnuAz4MoEMrErdcpCv+We+qth1N/KbSyXLrhaxq4D8P+RiPjq2TyohAR2J4KQ6F4UnBig7wgOAl8CV34kE7tSp2yka0zaXwkn9DRbEb5L+S33Bagf+uc8v6A/GgR3edDvGlNP/xNgzbgRUBb6LfcT4+wbXd8IK/RGP8c2/ipjgNXI7dyoxtOtPmCOQPfmQ6fVoaAVsXZHtFXfASjA61hIbsXgCNqaGDJEfw9+16Xsj175huRfo0yryfeBXoSaE3QB2gUIPWufFb0C+gBQNcIY9QMmmtAv43RKK8pH0d/C8V0gPrtj22KHA512LNahXDsRrmxL5n3KXtIRfAR0zrqCXXJpDnmiVakmEuK1ZgVOvmHcuzKA54E7WdRu0DXgz5Q/zHEo0KRkGbADWAWERC9vvh0qMF0bMMDJsxV6G/BuTdY+A3x2ugF/tYuqJNFK8IA07GDXHujZeamin0SNfTZNWZus0MUBbZ+RYnCaXAWFup3AIyl8JJloV+CvwQZySsprYoUejYH+7gy0Ki9WxIneKUkUbGyFnshQfmHshzDfzHLGgDFQbgZYmeh7zWuDfwC961GqDMLB2vjtm8YRdlPBgdjqn2UkPjeP8fcd+n3wcYx8xdi0tfoyOjC62E4wrt+oq1X3lUX6+IJ62+Lnb0XWr8pqjKc/HdsUfF5EB3+hjt67uLSIulYlgQEL6AnkWJExUGkGuKl9D/5MO8uCK8BXGdtUwLkfbIEf4ZOM9fWrgOdRZz0wKGVdPQLQM+zVqHtvyjptYkb/tPrWFrEmT0UJPqYAvQi2BXgjpZOfsdNkoht1n0pZp6bMGNebdHhVoAmprsNCoknjbWBl6vYrZGzl2RmYJXsVq2EMGAPlZoAb3Fh86iWskziuA7YD3cHCoDNYCOiZ5LdAq6LXwGDwJHV/5FiS4EOBagvaV9sKgApeiwO1r5u1+vc60Bv6/bD/gWNa+TeGT3rGWXYkhlFXkw5X1JdUQl/fzI3rGCpsAlYEmkBpXF8CPQsvKPgZhB9NfPRym1btm4HFgM7NL0C+XgVPg4exzzJGTebmBZFMiRIVOGoC+KHnN+tEsql67jrQc3NNXsTJTmAFsAjoAMaA4eAxoJ99/oxjFhGf/rkfksWB2WZggBNpz9Az8GWmxoAxYAwYA8ZAJRiwLfdKsGo+jQFjwBgwBoyBVmbAAnorE27NGQPGgDFgDBgDlWDAAnolWDWfxoAxYAwYA8ZAKzNgAb2VCbfmjAFjwBgwBoyBSjBgAb0SrJpPY8AYMAaMAWOglRmwgN7KhFtzxoAxYAwYA8ZAJRiwgF4JVs2nMWAMGAPGgDHQygxYQG9lwq05Y8AYMAaMAWOgEgxYQK8Eq+bTGDAGjAFjwBhoZQYsoLcy4dacMWAMGAPGgDFQCQYsoFeCVfNpDBgDxoAxYAy0MgPt+C12/debUv5Ji/6Bw1pF9vsV6o1LWXd97PRPELKK/gGD/lmCiTEgBr7iH0QcalQYA8aAMVBvDCig618hzlpvA7PxGAMxDAwnoHeJKTO1MWAMGAM1y4BtudfsqbOOGwPGgDFgDBgDMxiwgD6DC0sZA8aAMWAMGAM1y4AF9Jo9ddZxY8AYMAaMAWNgBgN6Ge5rUMoz9E7Un2uGy0ypH7CemLLGvNh1SGnrmk0l842rsHTVMzAHPZy96ntpHTQGjAFjoJ4Y4KW63qBY2SItFzTweJGNjErbhtlVBwOc53OLPNdpqg2rjlFaL4wBY8AYKC8DpXxdrbw9MW/GQB0zwExDOw6tsevwPW/xT65jKqt2aJzj+emc+xhzKufi26rtsHWsJhjgutI1pWvLlYlcWz+6CqUtoPuMWN4YqAwDf8Ht6ZVxned1E3Iv5Wks01oMfEpDejQYyVgSi0SZejkSYHoxlnWBdrv6EFj0Wx8mlWNgMVz7O836bZWt/SYtoPuMWN4YMAaMAWMgyADB/FYKDnQK/4BuQ4J62nehnKqWLDcDFtDLzaj5MwaMAWOgChkg8OqXNhdwujaBQJz6nRLqr0RdN5jL1dpgb3C7MiZty4D7vKdte2KtGwPGgDFgDFSSgT/i/AMH/TM2tkSM/ZIxelO3MgO2Qm9lwq25hmVgNCN/LcXo58Oma8DudXTTAnpf9ZOvsLwxUCYG3sHPeDCb52+wl7dsGzFgAb2NiLdmG4sBtjavYcRCorCtuTsGDwaMNsLHpIDeVMZAqzDA9fc116f+sdFNQL8Vod/4uAD9II4mVcCABfQqOAnWBWPAGDAGaoEBgvddBPVH6auep48gP6YW+t0ofbSA3ihn2sZpDBgDjc5AWd6ZIojrFz6HNDqZ1Tj+spzgahyY9ckYMAaMAWMgj4GF83KWqTsGLKDX3Sm1ARkDxoAxkM8A2+T6lcJt8rWWqzcGbMu93s6ojafhGeDmvRskzO0R0Zet0qa35CnvQtm+YA2wLFgQHET58xxjhXqLUrgTWBksB1RXPzOrf/Ckt/CfwsdAjqkFn/q1K/l1pR9+mt7Wp1y/tKa+6pfJ1J5+iU3tfQQeB49gO4FjUYL/1ai4F9gQqK12YCQYAfSLew9HfSFdUaEvM9PAlmAD0DUH/eSn/rnUMCBu1Z9xHBMFXytgID8Sff/898Bfoc+LnfSujMb/s67CTWO/K3n31/BUrGtLL8ilFvwsjfEuYDOg869r8DugZ/Lazn8In/qKXWrBZw+Ml/Iq9MeP/M5EeWcO+4DuQNeSvlEiLoeC6Fr6lXTRQhvyuTNYFegc6nOia0rtvAP0C29P0KcpHKtPGID9c5bqOy013SOuqYb95yyMfXcQkg5pTyqVPww46IBuefBIoEyq7eL8U7YcuA9MAYXkHQy2jfPl67F9MuBQ7S0KbgeTA+WuaiSZ3/l+C+WpsxB4wHUUk/4e/UmgfQqf33k+Ur0wJt/gePCFVz+U/QXlpSDx/wJQfkSocgrdw0njpP4HAR+p/1sndVcCDwd8hFQvoowmJUndairDtl/AyeroOoObwaRAuav6nMzBBRsKGFBvEfAvMBEUkk8w2C/gJqjCdomAw6dCxgUv0lAl0xkDxkDNMaAb1btghyw950ayP/ZvgV4gzf1ideweo965HIuVHan4P9AbaNWaJPpRkzto7zKglVBBwU6r17fBHgWNZ5ppHmwuAwOpp1VkWQWfi+PwGXAF0Eq1kCiQnwxepa5WmTUj9Pd0Ovse0Ao2jWyM0WDqXQUKXQdx/npS8F+g67/QjrR+M12B/7os7WGrybBW34eCNBPv5bC7k3qaABQ7Lly0lDQf0Ja1TGMMGAO1xsANdLhTlk5zs7kY+75griz1sFVgPZP6Z2WsF5kruPnbulFZ3PEkCs6MK4z09EmPIp4EunlnEW0Na1JTNqEva+DsddCjCKfa0n0KH9pGrmqhj7OAW+nkBaBQUA2N5ViUj+Kj0HU4LVD5UnRZJ2JHUuf8gK8WKvp0AspHQTHnQRMAfS7LJsWQW7bGzZExYAy0CQMTaVUrYK3YvwdLgG9As3Cj2prMH5sV+YmRZPXrYF8C3cgUmBRgfPkbfp7heeFLfkGG/GRsPwJa2X0HtAOwFghtOZ9Ne3o++SrlcaKgr+e3vmgV9zL4FawGegB39XQ2fq9HV065FWeLBBxOQvcc+AzoXK0ENgRzAFeWJaOAsLurdNJ+gAvtYPg2ft5xV3RSE7QDY2p/il7Xx9dgPrAe6AZ82QaFvgO/C+dhql+Yy4fG55rqufXHQNeS2ouupTlJ+/In2nqMtnQegkK5zsvlINSu/D8PRgNNTnVNrQN8ORQ/T9PO3X5Bm+TpjD1DbxPm67dRril7hg4JnnRIe8apF3qGHrnTqk4BvKBgd15UKXfU8/GtQYsbGLpeYCzw5cWkhjAOPUOPfLxBQi/g5Qm6hUH/yMg7Ppxn7GWwHePZK6ubd96YyOu55S1A8lfPTTCLXaZn6NjrefKXIJLfSGgStIDfALrFwIMgJAr2iUKlmQMV30+sFCjER6Zn6NjHPct/n7KtAk3o5bW1gZ6fh+TCUB3pMI7jR37eBQrgeYJuAXA/CIkehSQKlTS+qU7lz0jvCVosltFtCT4FvgxH0T6uIcp0LfryVJx9SXpasYBeEoNW2WeAa8oCuv/xnTatHAH9RtzmBS6fez/vnAutjlrcpFx7yjcDk4EvS7p2bhrDuIA+gLLEMVOu552+6Obqv83d1CR63bx9Ger2x09jXDBYRnWwzRTQVY86UVD/hvTaka/QkXIF5ReAL1oBJwoVWj2g06b41kuFvmj73N9tyOs/5dqm/7dfkbyurxaTPFVGH3opTi6eAR3zGvAylF8lw4B08UxbZKkTBfVnSRcal863Xmz0ZaMWjnMKDFMH9NhZQZxz0xsDxkBNMqAt9dPY2su0pYr9X6inZ32/J63t71ihXFuMDwUMYm9WAVupJoDj8aet5yTRy2Ha9ndFE5bNXYWTDvlLnDTQBz1aqJjg/0Oc9wB7kn4zqSHKtWV8asBm44CuGlRn0Il5vI7oZcRejOUXT5+XpVzX2uHgybyC6Y9BLvB0UTZ0bcvPcfjTNZUkemFvRMCgR0CXp8L3DSh6g91TjEvn+6Y8B9MzZTmHiTPuQKOmchhg5rQu2UccVZTUze+JKKMjtnqut7SrS0gvQ/3xUTl1teLQ885al38yrrgPY62Prdr7fync6xl0ZqHezRkqaRt3D89+US9fKHsrber5caJgo9VfH4x0M3ZlDTL3ugqlsf8R+29Jzu+ULY1O27hnUV7opu9UK1+Sdj/Cm5BGPsBIz5DdxVhWftO0U5INnKp/+wecHM549Z5CQcFOuy2HYTgUdHQq7IR+fsp1Ll3RZM6Xu7HT+yKJgo1Wzf/C6FzPcE0vH8xS/65gQVipZ/i+LOIrislbQC+GtRl19P1LvRTki3vxRWULkgjZRuXu0b8w9eFIW9f1U23pxO2oautsnfXnrXKOh5vf3PjTiz4u9DKTrnNfZvMVBfJZ+hq6OYb6EDWpl4+OjjK54584HsCYLud4OzfnL73yVs/SF33muwKXX6WXB24wJ9vi35lK19ayIR1YyOvEILh93dMlZrEfBReanB3gGOplxR1BX0enZGiFXuq11OJ9Bq/N2Cz9np3CVYF/DhcLVJJtyWIBvWQKzYExUBMMaJVTtHBzUnDZDWwBdINaClRKsvT100An5gnoItVFJPYF7ipdZVrlquxCxvoqx36gDwFlDMeKS+7mrze5dwJrgVVAJ1Crskag448FdGlUj2PkBnTV0crZD+jS+1LJaymvLc7hEih2BT2BJrfLAn9xhqpyYgG9ctyaZ2OgmhgYXUxnuEmtT70bwerF1C+yzqgM9fRc2ZfYmygBegRj0sTkfhDa9VJdjVk4F9sHOOpR0RCOZRf8K2hfAg4BWXcyyt6fMjoMPQYITb7SNBmql3aLumLXUtRxzuGKpP8NNo50bXX0t27aqh/WrjFgDFSWAT13zSTcqE6hwgsgbTDXi05fZGokbJy5r2E3YS3BWWPSLoNW5OPCVk1avTCn1fxguEj9S3QJ/vKK8LkCCu0GHAPSBHNtKQ8HoUkM6qqSOQO9+TmgS6MK1Qv5D/mq6LXEOexNo2+AtMF8ArZZJhmhMcXqbIUeS02qgq+w6hOwHBnQaabvP1MKmDWp9GamKxPJ6CbUGqKVid4NMGlgBrhR7cLwL4mh4Bv0HwSg6/44UPBrVNi0qRDUv6YDpzHOszjuDX4HtgSha1+r9pPAj+BsULLQriYLDwI9Y/VlEopPgM/xh+jGg1/BzKCaRfdGX9Le/9LUC/n361U0zzlchwZ0/w+dC10r/vlT/jOgz5Ye6ZRdLKCXQCk3BX3oDkrjAttT09iFbKirG+hmobJy67hIR+FTz4JMGpQBrgGtfq4ODP8edOdzPYZeRGsyp+5ygXpVq2IsWjH1Fej73Bx3AieA7sCXP2NzPXXK8Vz9jzj3g7n8ngz0n8YUtFsI7euzWQvP1kOPeNaj77e3GFRhher58rmvaM0852Fm2tOjKB1deYbMGeBVzmHoJT1946mrW6GcadtyLyeb5ssYqA8GtmcYS3pDuY8b1L4gNpjn7PUWdk0KY/sR3AnWZwA9gb8K1OpdW/DlkCM8J5pY9KDtu0AwmOfsa4Xfgd74lNV/EtTORFbZO1BBgbMtZV0aX9vrwMvkt+H8DQHBYJ6zr9g5tIDunRHLGgPGQNPzZZ+G63yFn+dmvQC6DX19Lea5IStgnBboe8mrK3iaC79Le76fpc2PPF0ou3NIWW06xjKaPr3h9UuTxKM8XWIWrvQCo4KnK+PIKHi2pawWaPwGxp34zJ7xdKTe1oG6ZVFZQC8LjebEGKgrBlYKjEbPdQvJ+RjMW8iorcu5qe4K9My8kIS21ucoVClFud6K9kXvySQKfdZ5OTbRKKaQQDOFovFe8fxevtzZiwMO9bXA0OOMFqbYLYvy3y0K+He2ufEEilpNVexn5BR62KVSvbSAXilmza8xULsM6IUeX47jBquXw1qI9OBICg5vUVhlCvqp7wnfB/qQ1j9l6ZDQxT0DZSMCuqyqEL896Yv/TL3ZL2XaGbgDJPW32T4mMdzTL4pffee9UnIvjod4zjuR1z8I2tbT52UpXwfFC8CfdIxC988847bJhM7hUfQ79FJlUw8p60Xi/yrZXQvolWTXfBsDtcnAoEC39RyzPzelDUAnoCC+DNBLZE8DbclX9f2EvkbBXEFxFnAh+C/6E8CSQGPSPzHpCq6m7DDgywBfUUReL9NqS9oVrfz1T0SOBp1VwHEuIL4VBN4Ba0tfggwL1L0P/5uC9kD/EKU78INooFphFatoPUc+AHznWevlw8dp516giYyCvMbbAWwIbiCricBi0juiXYzf4dffaXBMWi05KNDSpuj0D1q2AjqfGtPiYBugyY0mkk16lVVCdFGbGAPGgDHgMvAAmb+B5Vwl6R1z0I1aL3E13Yg5Vr1wQ9Vk4x/AX+Euj04rPkFj0lvLcfdF/X/s1ykvSfCh3yi/FCeXe44WJn+NQPlvHMvN72B87gBc0Tl+Hmjs4kY8aeLwLShZGOtQxqKV6WPAX73uhU5Q4PuZg4JdcBdIJkC/A/8ix2oQ9UN8buh1Rt9H1wRXY9LEYzalW0uqekbdWiRYO8aAMTCDAW6aCiZaWf06Q5uX0k03LtiMzrOskoyCKF3ZBnyU0KWOlMUFc221H55QN2vRlVR4NKFSHL9fU0fBtxjRpGVMTEWNvSLxAO6fxXdPMC6mbannBHHBXIFxb/zcJsNqEPqiCcaBQOcjTuKCecU+IxU5gXGjM70xYAzUBgPcsLT62B7EBQB/ILrpngrO8QuqJc+Y9PxVz2ZvyNinN7HfgvqfZ6wXa44vTTD0jP6eWKOWBc+jWg+I68xCm1oF6wdy0rzgmNl/UgXafoHy7uDJJLtA2WvoNqH+/YGyNlXRp6F0YCugRyhpZDJGF4Ej0hgXY2MBvRjWrI4xUDkGdKPXDdtHlha1gtMq20WW+k223LAUQFYH2hr+qUnZ8o9WXdeCVbC/hKOChduu0rqRxUmor1r9pBXx5benZ61BoY+/gCMpVGDvC34MGk5X6kZ9PNiAOsMS7KIi/5wlBl58/gb2pbK2nTVpCInGNwjsh+3mQDsF8uuPGVVhof7dWGlL/dUE6yT+1a4/zgRXM4poexjYFo3QH8Txo+vlWbAfWJ86cdxQnCc67z4vSWPJq0wm07WkyvTtPQ56sfBvQJ+FkOiz0weshf1pHEPXfNIkS2PwOZePFhK3xdHCME7Bc4LelN0eV15AvyUDHFjApqmYdh4noQshq4ymjSWzVmpUe3jWKmaJCo1fvzJ2ZiHf9OFcbAraFfITUz6cPnSJKTN1DAOcEz1fXRcsB+YBX4Jh4H34TLoZYVK9wrhmpne6PywLdF1okfMNeItxaXytJvRFnzutwDsDBSe1/wH9GMux7EJ7i+B0RbBCzrkC0pu0NyKXr+iB9menAU2sFgMLgO+Britxr3RNCePRtbMmWAnMB7QdH31G4iYvmJRP4p4Xla8F82QMGAM1zwA3WAXtwTnU/HiiATCuKaSH5xCp2+RIX/RstWLPV/1B0Z4epwjP+WWtkaf9X2nnhdZoqzXaYDxa4Ws3Ie2OQtm7pRmFiTFgDBgDxoAxYAzUOAMW0Gv8BFr3jQFjwBgwBowBMWAB3a4DY8AYMAaMAWOgDhiwgF4HJ9GGYAwYA8aAMWAMWEC3a8AYMAaMAWPAGKgDBiyg18FJtCEYA8aAMWAMGAMW0O0aMAaMAWPAGDAG6oAB/Xcd/YhBuxRjmcr37JJ+tzaFi2QT+jInFkJI/B/2D9mEdPrvSfoBhUaXcZy/yY1Ogo3fGDAGjIF6ZUA/LDMSdEwxwB+wmTeFXSkm+nUw/TReOWVRnOnXhxpdVocA/UyhiTFgDBgDxkAdMmBb7nV4Um1IxoAxYAwYA43HgAX0xjvnNmJjwBgwBoyBOmTAAnodnlQbkjFgDBgDxkDjMWABvfHOuY3YGDAGjAFjoA4Z0EtxpwIdC0nw/68WqpSx/CXsr4ipsxv6pWPKktQ/U/jvJIMqKVuWfuxcJX2xbhgDxoAxYAzUGAOz8FWmq6qlz/SlP30RWghfPdP/mC0moH+P3xNbOKwyBePbhS5ZQK+y82LdMQaMAWOgVhiwLfdaOVPWT2PAGDAGjAFjIIEBC+gJ5FiRMWAMGAPGgDFQKwykeXZeK2OxfhoDxoAx0KYM5H7tcj468QuP+r5t085Y4w3HgAX0hjvlNuC2YIAbfW/a3T1D25Ow/QqMBWPASwSIjziaVBEDnNfl6M5BYFOwHpgdNAllP5EYDoaCx8ED1Rrk6atiwZJgNH3UtWdSgwxYQK/Bk2ZdrkkGVqPXe5bSc266CgwDwPXcdD8uxZfVLY0BzoX+P8TVQJO0uEeXc1HWLYc9OP6Tejdy/Cvn70eOVSH0aR86cgOYB/xM/lj616cqOmedyMRA3IWYyYkZGwPGQKswsDytnAT+y033eqD/U2DSygzAew+afA9ogpblHqrVu75x8wE+9K2WNhf6oW8OKXgrmEv0z7FuQr9CU87+1BQDWS7GmhqYddYYqGMGtLN2BBjKjXe/ah4n/dsVDHLwWDX3t1DfGEd3bPqDBQvZJpQvRtl/8HV6gk1rFW1IQ/4/5+qAbpPW6oC1Uz4GbMu9fFyaJ2OgtRmYgwbvJDBoO/9MtkmntXYHUrS3ODabO3b6oaeaFHiejY7fDeL+xbPGpX8x/SmYGWj1q39PHRL9y+q98HkZ521iyKCVdHH/Elvvb5jUGAMW0GvshFl364oBPQePW2Hrs6lgoG11vXC1A1gAhOT/UC5LcPhdlQb1UJ9rUXcCne4S6PhkdLeCc+B/lFvOOdFKXuf4KLCyU/YW6a3bOJirO4PAy2AjEMmbJJ6IMnasHQYsoNfOubKe1h8D47mh6+ZZSPRMUyu+34HzwFKBCvuiGw7+HCgzVXkY6B1wMxVdL87jQ4GymdCPQ38V5+96jn8BZ4J3QE/K2vxrbfRhCn3rSX+OBtrp+RBcjd7edIeIWhML6LV2xqy/DcmAbrwMvC833/s46u3qQwNEnE75h9jaG8oBckpRwatW2gp4vmjLPBjMXcNcgDwLP1qZP0++zYN51D/6Mp70pVHejrXLgL0UV7vnznregAxw8/0NHMbQT4sZ/rUEDT23rhZJet5cLX1M0484TvWCXGrh3PUD36SuYIbGQAYGLKBnIMtMjYFqYYCgcBF9Ca2qZkd/brX0k36sWUV9KaUrcbuZVbPSLmVwVrc+GIi7SOtjdDYKY6C+GdAqfR3QwxvmgazSLyfov+fpE7PU0Qt4XcFyYC6gleRQ8Ca+tOWfWvDVCeNeYK/UlTIY4n9ezNVP9XcR8D0YDV6hr79wLLd8HuNwA/Tvx5RVVA0HegSg8eu86RsOGv9wxt8m/aHtmeiTzsmqYGGgbwXoLXpxN4R+TeBYdqFNfZtA/40zemHxK9Kv0d6ocjZGOzPjT583vcOia06PKsT5UNr6jGPJkmtjXRxFbehdBo3n41Y5r3SgNyhWtkjLAA08XmQjZT2pafub1Y6x7VLk+NJW65amTzgbldZhEXbnpezDuUX4TltlWJo+lNuGzl0Q6ODbpbaDzzXB1IDvK9L4pt4S4BLwecBHpPqexDVAN85YoXxr8GsOetkqTiKb6PhqrFOnAGezgxPBW3GO0U8EA8DGTtWSk/hrD34Evoi3BUtuIKUD2uoMdL6Ggzj5lIJzQNxjgrzWsBsNonOho35qOLVgPyf4C9AP5sTJLxQ8BDZM6xjb2YDbL6VfV32OM4M/gFdBnLxNwT6gXdo2Q3bUXwHcDL4GcfImBSeDuUM+CumotxLoA74BcaLzdDnQZKkygnML6GWgFh4toOd4hAsL6BmuKfi6H/hScOJChWPBeL9iQl431MPiukbZdgl1k4rejfMZ6am8GRiZ5CRQdh06/0dTIpeZj/i6JdCGVJ8AraoqJvhvB/4EQpMK1EHRz7ieARI5oNwPIj+kHQh19wdjQRZ5AGPtsCQKNgrovryPYjWgYJ1WtBjMHGip0wlcCSaBtDIGw4MTB+YUYqs2NFmeDNKKzuspjpvmpD1Db6bCEsZAzTJwb6Dny/ChXz2g1+pGK90HKbsKaGs8rWgL9Sbq6utXrSa0p0cLz4IlMzZ6JPZareuXz8ohN+BkWsCRtplfo52HgVaEmYNHwGezCn/iXef4QqBHIWllDgy1K/YMPhZIWymNHf60Y3Extn1B3I/nxLnS79oPof7ycQYJeu06vALWSLDxi7ZFMYj2ZvcL4vLYakv9OXAcmCXOLqDX6lmr+dvBrIHyZpXTxtEotZ2fVnReLwm1YQE9LYVmZwxULwNP0rXQM24978sTbgK6OT0Kds8ryM/oh1L07C5OtJ3bK66wnHraORt//wBxNzwF2N9AnPSk4Mq4wix6nmEqkPwzoc7OlN0NtDX7GDgSLJZgX7CI+goKj4EkvnWuks7XxpQroM3HsVyiyeAfY5z9iv5NMBB8GWOzAvon6FPWiYZW9gpovuiaDU22Iru1SFwbZZKO9GkhyvVjO90T7HTNTU0o702ZdiKCkwH0GocmDHFtjM2Vv8bxJxAStXGdWzALjjdDUUpgX9l1mDG9Ju0nnQTX3fxuJkO6I030yGBfCdMXuBmEbriVaMt8NhgDXFvfc43rxSO9SOOKVhl5gq229t5GuXleAf8whLxuDg+B0UD3BK08fw904/ZXG3qW9wj+xlMWyX9JHBFlcketKnXzikQvRh0fZXLHb728m32HjIKVu8qW/fVAwVMvC02gL0uQ3hr8HfjjPoLyf2H3BmWlyp9xIP/7JTgSV9vlcC1t67mveL2NPozimEU0Tv9cqb7O0UVAfuVT50vnX6vfY8EywJWlyXQB37nKYtKMRytKwRd9x/4koO/ZN9/XsVeAPAscCdwAp/7cA3qCYmQclfRDPYPBh0C8a+Wua/AA4MuB9OVK+qbJRlAol49+QH3z5V0Ul4DH8aFJm2y7gv3BH4A/OVkN3YJgDGgW6mlyeh9YoVk5PSHObgP6GWed32ahznpkLgObNCunJw6h7FXstXvU9HKBnouZVJaBgttkNG/P0KdfoNoStmfoOS7SHuAs9HLQFXH1sVdAjuQsEu6NNq8aZd2BgqYv++QZBjJUGOVVilttBGpPV1F/d6CX3STa1tZNMiiUzQs+Br7cFKxQpBLnR4OkFwn99pUXh3pksWyaZrHrBUKi3++fPc4HZVrE6DMUPZfVc/cNE+xTP0PHz+IgFDOuQK9JRaxQvh7Q819fdglVwij0DD2q+zSJhUP1pKNsLxB69n17XJ1cvbOjBrzj38jHjo+y+YFeaotkGAlNoloIer3M54uuDU1IEwUbfVZ90eRieowhETo5fgXLl8aABfTp/J2XeLXmCjG1gJ6GKMcGzp4NXKK3OCYtktgrqJ/RoiCgwO7MgP8+AdM8FXVKDuhyiB8F9UdA7MQjahibDYAv2sEoq9CAAqcC+wi/sQJ5BdrrQce4DlHWAQwFvvRF0S6unqvHbk+g3ZvYYC57yrME9H9h70t/FLHBzuuTAq0vr7s2URqjuID+A2WdI7u4IzYX+g2RV/AL8od+URCacJwe14avp74C7jAQF8z1/soXwJeDfV9xeSre7Vcmf1qTPQkL6AF2yqyygD6dUAvo+ReWtr7LIrh9J991U05bsomCVfDm5lfCbq2A/+d8Oz9PnbIEdPlN29ec7bhAf7VFWnahHQXfbcA1QF8tSisvYhhcaaPfO+BEAX62LAPAXo8HEgWbVAEdOwUj/1sR2jkp2IbbAez1kp4vemSSJxjEBfRT8wxjMrn6oetgpVAV7PVuiC+PhWzjdFTWtxGSdg5C5/XZOH8hPf47A00KXdFjh5KenYfaMp0xYAy0DQOhm+qXhbrCszc9twsKd4vFgALVKRgIvvjPDP3ysuYL9HUe+rkx0PPyq2hYzyl9KfY9HN9PXp5+TQJPgmMoWBLoRafzgd4pSJKNKbw0xmDXgP5c2hgf0MeqsM97fhtrmK6gJ2adPNOHimjjbs+HsgW3m506em+goOS4Ck2aF42pHOI81Q5W5I82p4GxUT5w3Dmguz6gi1Xh/ysKB3oG3bnu5ym4feVVsqwxYAxUGQN8kPVMNrQF+UWarlJfwW99sCbQizwR5iOdJKlW90kOiimjvytTTy8JRf3UUYG0kFS8v7qh0wm9mSzoMUVXjgeD40Bop05vwt9Ivbcod2VLN0N6EtDLWm0pukZ80XfC9S2LLDJnwLhLQBdS6eXiYaGCGJ0mVVt5ZS0mooxB72Ws7tl9wnmJfYHOs02bDXF4FO0fltZBzm45z16PPJZSQNdbrKHZrGcfm9Vbd8fGliYXnEtxoVls5OHPJNaIMhmO32J7dAp7bWXdksLOTIyBamNgh0CHFFieC+ibVdxENiVzKNgJtLjJNRtWQYK+LkM3tALWKmp5UBNCQPiEjp5B/7Vr8CAIPc8+CH1zQMdW9+WFgSv6L3o/uYo2SId2gbR9LZQqoQlpyOdIeNDkJq2EOOsQqLx4QKdJWbnFP6/y36NMjXSeBXLuL8VZ7uIrNqAPpP2BadqnHc1yiwnov9LGPYXawL9mzxbQCxFl5VXFANdtOzqkoOyLvsoyxlcqn/vMnkPydKD6VS30txcd/BeYp6o7mtA5nQvGsS0m/wP+8+Id0Z3gVF+ItH9exjnlbZV0v35Y7j5MTunw15R2kZkmtmkkNKH4Ok3FtDacfy2c505rX4TdZM0ETYwBY6B2Gdibrmur3Jek54x6hrmnX8HL6zngBw6Gke7v2VQ8y03wSBq5rkBDWoV9CBQso+N5pLuBqhGC+k+MR18lvNjrlB/gQ6vKObw6bZHVbmel5PNKOU7p9+eAXVk55/xP4fz/QDuVmph+bgE9cBZNZQzUAgPcHLR9d0mgr9+juyGgb/r6F/pQMH8MvSYB74EPuPl8x7FZaGvF5kwrJWhT26AXBZpTH+8EbwD1dbRvQ91rfF2V5DVJ8kVff5sv4pyjvjr1C0ZuQFkWHUXxLzH6TiuQD+34XEo7H5ehrefL4KMUF6GxdS3FYUxdteMGdE3e/hhjm1U90gJ6VsrM3hioAga4uesrWA8Af3Wn3p3PjT9uNaUbsC+/x76vr/TybfHc+q/0YS6vHzeTP4L+xm7Rwo3ehwk9E/VctUk2FCS0Lawbuytvk9nYUeilrXXBa46utZOvBhqcwLm4MaCvNdVIOqzPjPtNiA25luZhfFpVl0vEoTs51vWtf/X6VjkaaF8OJ+bDGDAGWo8BbjJ6lvkocG/4UQc+JXFVlHGP1FNQ6OLqSD/BzaRQMFeVNbx6rZHt7jWioHcc/Y0N5jn71Tn6z6A9V8Vl4bAH0LsHxYpeRPRlXGBMD/tG5E8N6BJV9DX0AlhinYTCZyj7zSvfr5g2iqnjtVvWLPxPwaE+U65o0ny8q0iTLjC2AQEfejE9s4TasYCemUarYAy0HQN8iBXkXgFbBXqhgLcbN6cJgTKpVg3o3w/o8lS0qVXLyXnK9BndKF3R/48oGGyx0QtEK7kVSX/G2H71dKHseSFlqTr61AMfj4ALSOtX9nTDTy3Y74bxHoEKzwV0d6Ob6On1U7C7e7rYLLadKdQPDm0da5ShIMf9fV4VTRCv9nSJWfqzDAb/5bh+omHrF/YJNHk6/dQEMZXkbDU2/9qN6mvS8HWUyR2Pw347T5eYxf5ADAZxzNvBsoCeSJsVGgNtzwAf2vZAP5qib2sMAe6WXdRBBc59uOkmBeg5I2PnuBF+YwNsruxi7BXUi5HRXqVO5EPbzp5Z0z9j6egpV6I/if2gvDd1enr1Ss7itwdOFMxnzzk7kePr6ENfQ8uZzDhgp2B+Gwhx/eAMy+kpzuNIUn6gVN3b8FVwfNgsh+1zYGWg378vS1DH11nAn2jot8kV+EJjw3yGYLMeuReBHuE8Sb5qgjqcP60+AVd0vvXTtuIxUbDZCINngMY2kHyLoE4bP1N2PnBFk1f9nOs6rjKUxka/RKdrT4+e1N4T5JuDugX0EGumMwZah4GF+TDqt59DOAe9fu9bL6qNBboJ7h3TrUnoD+VmoRfbkuR/gUIFpCtpZw6/DJ2CwiBwiF+WIa+3433R6laBPVYYi7Z2P/MMFOB1c13K0+tlvznBtej7+mWl5vGrG+Z/QBTMI5fdSLxMuQK7vmuuf7DUDcwF9M861gO9wcvY9QPNN97IAUdNwO518m7yPDI+f5qUPYrP80ALf+jmA3ok8C6IAoq4fgh9wYkAdonCeRmOgR+QVOcC8DxtBFez6DUZux6bV8DiQDI3qKqgTn8ULLXT5YquN53nY0AHt0BpdEuCK0i+ABaUDlkEDEQfnYMmZe7PdRzfcBWk9aLcEPkBSucJuvZgO5SDweUgit36/DYH9VnImBgDxkDbMKAP/d9KbFrfT96TG+3zhfxgM4ybguw282yPJb8PZVrRjQK64a4KVgSl3iOG4sOXHVG8T3tPcdQLYWuCC+hff46u3ErmHFdBWquSodTVakoBXxORVYD6G9qBQF2a0C993ewovPQBLW7o6LSyKri6wsaXiSiOwr92V1oI+u9od2cKNCFQ8ItEfTgDnEz5QI6fAt3glwU9wGzAF02G5vOVRebPpd4awH98sAm6t+iTzosmKpqMdAZa3a4NQqJ+VeS8hRorpIPzD+j/77DTRFqcRjIviauB/nHUsxxHAvV7ebApmBn4onJdn3lCGxPxsRvK14DuAZHIx/HgcMo1+RaHP4AlgXYyFgMh0QSg6XNa6oc15Nx0xoAx0DoMPE8zB3KDGJ6hueOwHQK0anNlITK9XEWZ0jfh50/Av7Eth06IJHQvuoTC3kATC1dmJbODq6h0Go7v4ib7Oe30BUuVob2p+DgAvy8m+aJcz2MV1PsB/3GDAncaHn7FTtfJ/RxLFvzQpWkH4EjBeEfPoYJg1xy8ohbZcWj2wt+gFiVtqKA/AxjfwXRB166uNVc0KdrTVcSkx6DfFV/+SrzJHL3+iY9W3HqMs3iTcsYfnde0k0RdP7vj7ztVd2cgypsYA8ZA9TOgmfvOfIg3B8OzdBd7bcXqhvRbhnrjsT0ig32zKe19SebiZkWGBHXV7jbA33Yu5OUaDDRpKavQH02gVgPXA+0sFCuaGGyDv7it9jy/uXa1QtOqLavofK+Lj7IE86hx/GmSsAu4DBTDxePUWx0/gzhWndCv2+hUTzC2iM5pbGvg49WkupS/Q3l3kGgX40Pf9DgL9MCPJkZNYgE9YsKOxkB1MzCU7l0OtgK6WQwotrvUfZS6ejlJW35Johu12ukGdIMrVs6n4hkg7u37WL/0dSSF2q69A2hVmyT/pXBr6hzLUTe8sgu+fwJH4Xh1cCv4EaQVrdr0iKUbPp5JW0l22H/CQY8mtMPyNSgkozE4BXSn7geFjIspx+9UoDZ0LQ1K6eN17PTy5vZAk72qFfr3Ap1bHvwdaAJTSHT97Q92oO5XhYxVjt0XHPQc/BDwOSgkekxzN1iLuvrve1PcCrO4GUsbA8ZAxRi4D88fZvCugKSbgoLAGD64aW7iqd3jT6t8/cvFzTnuCBQ0FwS6cQ0DWj3cg90IjnrxZ2YOByntyPdOOjaJD43l7/h4gGMvsGIOs3L8Fqitt0FQqK929qe+gqHqbwQWBe3AcPAx6Iedu9I5D93CwJVU/XUrxKVpS/wdTJ+O4CgOtdJaE6jNeYFutNoG/Qa8CQaDl6mnG3JRQt1JVLyaNm/gGJ23rqQXAbqX63p5AzwNBmFfaAKEWZMcw9+OubQOmfpIO2pzC/qlvuwCtJugPs0PNOEZBV4Bj2H7Ece0on4c5Bk3bS17uqTsgxR+4hmoL6mE/v6EoV54VFDfBmwHlgIan65rfT7l7/EcDySzCfV0nm6hDU2aNwY7gRWB2ugE9BnRpOwl8Aj2ZbuO8ZcvdEJvcRYrW+R7i8/RwONFNqKLqaDgW2+mVkrmKtQBGtYbspWUboX6oHI6MKqCndBNtqDQvl48qZQoWJkYA8aAMVB3DLSvuxHZgIwBY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TFgAb3+zqmNyBgwBowBY6ABGbCA3oAn3YZsDBgDxoAxUH8MWECvv3NqIzIGjAFjwBhoQAYsoDfgSbchGwPGgDFgDNQfAxbQ6++c2oiMAWPAGDAGGpABC+gNeNJtyMaAMWAMGAP1x4AF9Po7pzYiY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TFgAb3+zqmNyBgwBowBY6ABGbCA3oAn3YZsDBgDxoAxUH8MWECvv3NqIzIGjAFjwBhoQAYsoDfgSbchGwPGgDFgDNQfAxbQ6++c2oiMAWPAGDAGGpABC+gNeNJtyMaAMWAMGAP1x4AF9Po7pzYiY8AYMAaMgQZkwAJ6A550G7IxYAwYA8ZA/TEwS/0NyUZkDNQGA9OmTVuDns7Srl27N9weo5+d/KquLpD+jHrfuHrqrUx+TlfnpX+mzgfYLYF+UfAh+Z9cG8pmI78a+IqyESpD15nD0krHSLNtTLl8bETZcuAh/P7o21G+B7qZKbsvKkO3H+mO6G6NdO6R8mPIj6P8nkiPbkvS6v8d6L8hvzzp7cFr5AdzbCHY7IRyWdAXm+/IL0B6xzyMRcwAAA2jSURBVBaGMxRq89Eoi/1upOeO8rnj5xxfx+4HT9+Upc7WJHT+24HXwHPYTuPYLNj0JvMt+sekJK8+za90jOg8PIFd1P8vyD/t21K+DLrNwEeUD/HLQ3nqrIh+TaBz+Bl4m7ofcswT7OZBsUueMj8zlnpP5qta5vDTFe1aYAXwFXiZev/lmCfY6RrRdd0/r4AMZRtw0PnvT/n3UTn6XqSnoOsX6dwj5YuRXwesCn4Cb2H7MsdYoc4OFIr3ftj+HGtIAbbzcVgXrA6mgv+Bp6k3hWNQqDMXBeqT6uiaeQe8QR31r3xCQ71BsbJF2p7QwONFNjIqTRv4nqtI/2mq6WQkCk52SeOoBJtuiR3IFeJ/VAltFKp6Xso+nFvIUQnlw9L0odI29L8rmJobh4Jds6BbI6dPOhzYXCGXwHhwUgWVy5TjBTm7zQM+VsuV3RCVkT86p4s7XBvZxh2peGuusgJDC6FsOBjrFpB/O1fnBFcfpSmbCBQMm4X8Dbk6Tdc76YXABJBnF1VA3wkoiH8GdKMUP+uBJMnzheFHMcaaUGii0izk1d4zAXudO02ymoX8ZPBqpCD9OkiS52WLQXsg7jRuBeA8QfcU0LW3Xl5BIIPNguBuEJJ7UWqy1yzkVwkZOrpBzcaBBHbzgT6OvZtUDNBktFnI/wzebVY4CfTRtaAJXrOgHwNa3AfQzQp075kEfBGfazc7cRLoFwW6FiVHO0V5ScragT+AH4AvI1HsmFchl0HfC4z1K5D/Cuzt1rEVusuGpY2B1mPgKJpSANGsXDcBdwUwmrzKIzmdhG5kx0YKjqHV5kXoF87ZaJJwALgdvJTT5QXMnC7L4VaMhwQqtFg5BWxKUV3MjUsrbJejVP6o8zV1H8J4L47dyL/nVVTAnRdcQtk0r+xB8ld6OmVDq6If0bsr067k/w5uz7X7KWnJKUC7CA+AK8A3YC+gFbN8JMkRFM6ZM1BbJwO1Ea14f1AZ41CwVtkz4GKwB2gS9DuT6Aluxy5vYjLdYsZfbBcg9zZYHNwDbgVDgcamCaVWx5tgtwa+vibtyqNkdD360tRHX6k8fubm8AboAv4DbgIfAU109gFHghewW5H2JpIum+BTn0XthOjciJcLga4V9Wl78Eegtleh7RGkXTmcTAeglbk+y3ET3KsoOwZoMnE8eB3MDHTuTwP98b8V/geSbhLy+uxfAL4CR4FXgK7TDcHZ4B5slqfO+aRLF5zZCh0SCshchZimvq3QcyTBRV2v0BnfbOBb8DJ4APwGFoq7Rih7DWS6gWF/CJAc6vtFV+wKfX/fV9o8bd6qziBZV+hTqKPVz2jgrwYLrtDVP+ptDST/9PuL7mmglfBiURnpaIWugFtQsNcKfZxviO4gIGneYSCt8y1Zyrf389jkrdDdcsqOkBNEk7agUPZQk8W0aZvLgHQH8CH4BeStdEMOsLkPSJr779qhP66plDFFevLRCv1fkS7tkbrRNXJGqA7lPcGebhn5sqzQ8XMykNwBFGTzBN2K4KQ8JRl0swBdm/8DfwESBeg8QbddU8n0XbRoUtZsQ9kC4BzQ3DbptYCu8Y/Bgs3GuQQ61dG1J5u1pW7vG1neGDAGKs7AvrQwH7gRaBXSEbQIvOhMZpppAiScChYHd3Hjar7hZSDnaWyHAy0+Zo3qkV6GtFZkj7LC+SLSl/H4Xc7XHI7PD3LpU92+OOXlTIq3SeBy2tK9XqtHTai0GzGaY6xgr1VyLzAQ2+DEBr1WnNoF2AP75TgWLdTXhPZAMARcEHJEe3rO3Dx5CNmUoBNXWgUfTRtTfD/o9L7B5b6evHZKdG3qs3wzUF3x7Iv8TwUH4kcr+TxB9w04C7htn4iRVv6Ho28xYVQdyg7L2TRNNmYhY2IMGAOty4A+8D+Ae8FvQFt4WnFdxIdUH/rWEj2bW9NrrHml6umVPRT7zTz9lfT5fU9X1iz+r6DdjXCq54Xngv/L0gD1qT7t37m6ugHfn6t/MMd2IG41KX7WytlGh5vxd2uUcY5qpFMuL58KcH/L5Z/LHXW4GuwHjtWROgM49gMD8OvezFGVJvjTyu4avCgwaAtevGnichEoJNG4HylgqPKtgOw/dWw3pu3rnbySD9CnpzxdlI3aewybrJ+BxQNtye+mkfOkI3UXoVx4kLZ/SLINlOmzrEnnbdTVrpu27TXBWYT8GMden7OP0X3s6LTC10RLE/pI9LLexFxGnPwEno8K/SO2egygPjfxZwHdZ8jyxkAFGeDDtx7u1wXX8mH8VU2hU7A5B+wAdINvLVFQySI9MBZcUTB631VUKH0oflcHp8PXYLjrn7GdW7A/GxwC7sdHe44HAQW4uKCllfWiwJU53YyTnp/0eCcfJa+gry9FGdJ6IUs39yOAArtWpYK2TrelfATpcoquq9+Di3NOT6KNX1I0oAAnGTv9EPs3ClqRfWS4EgnBlU/IxAX0iOfIn1uvUFrci89ipai2OV8r0uCW4E44/TbX+E0cdwKHgfOkw64Th/nAe8p7ovvBK45uIGn5lIhTfXNhWlMu/o/OURP/uqhNjAFjoPUY0IxeMi8f9L8JpJdo0oS36nJFFTkcjNduHvZIaEmBR4uAZnCz0YokjfyWM9LNLSTSRzYtymnnZ5R7Ak2CboO3ZVsYJSio/znFj4Jtqast0p5gKXALZXEr4z6U6YUjF1dTJyTq+zU5PJMzuIy6Wh3nCbqfgLa916FgaaB6Cg43grIKbWjbX9eY5A1wW1Oq8J8o+KxWwDQqj+wj81tINF8nufSlUWHgGNWP/AVMYlWaUGpr2oeCaxrRY5DJIGvbekmtHViBa0ovp91DWp8RiXbcZlaCc6BrYyhYVXlPFIyvBaFzL0664Md9ZJNXnbLZUCwHmvizgJ5Hj2WMgcoxwIdPK4l9cy38juNZOfwhp1OwyRSocvWKPQzjZvO+CxzpxhMnU7HVlmAz4gwD+uiG3d0vY8wKrAuDyMY3acrT7v9IHA7mBfcD3UyziG7wuucdBA4F08DNoBzyC/07VsDZnmAc0NZrxyTn2I/M1dHqdaMk2xLKXszV1c6GxpxG3sboR6CXKxcKVUC/IPrDwE/gLc+mxbVSoG0FZa1yD8Dv4p6vpix6PdbQufdlGr4n+8Ao1Vipp4D7KtAb+5v4zqM8ZVplNwnp2UkoeGsyqEC9Uw7bcZwANEnfGUTyPIkFqSe+moW2h4NjUJzQrJyRUB1dr6fOULVIqUwTB9naS3EiwcQYaCUGtCLWSlQfXgUxF73J68N7JKhHGcCgfgZ6k1criibJ3RgVaCV3TT/E/+XmJ5urgJ4ZagWYRbRC1xa7JlC7gmfw9xnHsgo+f8ChVsXLgJNBszDerfybOvnOGCg4ftVs2MYJxqAgrd0FBfN+9HFpt0vkde32A+r7ydgr+Bct1J9IZa145wH/wX/zNSKn5OfmcCcYlEtLXU5R21ql34n/vIkVeX074FzK9CZ711yjmpBrcnEqfZ/DBbrlgQJ9tBtHcqY/A63GL8NHLyk82crLK3sR+AicQZ0jpHAFna7jM8HH4EKVZf1AqI6JMWAMZGSAD59WkwrW34N/cQP41XVB+d3k/w60ItLbrlo1VJvoJbEVA536jP7eEtA3qygfQd3jUPwb6Ec6FAwUNLYHXcBd2NzBMY2cgtF6YIM0xpEN/vUVOPXzjJzuX1FZzHEH7EOrxfH4OiCmTqS+noTG+39qE3s9O9c1cD5Yn7SuhZeAJnF7AgWHs0HVCH2+hX52o0MK7O+TfpbjJ0BBbQswJ9A7AoV4xKyw4Ode2tB7Egp+7+baU0BbFOwIFOyvAZoYllVoW+0pQF4L9KLZQI7vAU0ktgW6Dp4AY4BEwVqf0T7KuIKvUdR/FN1OHFcgr5fhxpHeF50+5/o64BCOr4PJYH2ga1kTQQXxJqHOeOz2IfMguJ602nylqXC6vbgaDvbBtul+Uo6ArllH03KfY1bRzS2tiNzZ0ho7dl876aSkZlTFjiPJr8rku5B8g0Gl2lfbvxTqQK5cF9qwlLZZzYanrCC7SnERfSBTdqVsZtvgSTdCvRWeF8zVAjr9GMiNJHXD3xvcBqpNdqVDgi+6+SlQJgpjvJUxDsXoYrAXmBUoQOhGpQDoy5coWnzm8TMJP6rv3mCjuvocjQATI4V31IRCwfgnoElFSPR5VbkeA+i8+eIHFOU1lmahj/oO+Z9Q9AV/BiegQ9X007Snkz8QHA+mgeHgKMp9DrTq9dtC1SST+KtyHQvJVAw0nvGFDP1y+nQyff4P+r+AjcAuQBy/Cs6jfBBHV8Sd+vWbq0ybxt+ZtDcA+7NBd6CtbPV7MLiccpW5onHF3dtUT+UavytBLvDdh7ZfwPACsAHQqln8vgvOBv/GBpOm73wvR14/Ffwtx5Bch3Jz8Htwpgyw1e7CKiT/DjQhOkpqMApcAVp8nZA671BHgfuvYAdwCJBoVX4p+Cs2zeOXMxNjwBioMAN8KOeiCUG/zR282WGjoLAg0PNYzdabBL10HdB9mVMVPFBndoy06vueenkTCMrmRq/V1TjK8gIfZZrkdwbNfUA3B3mtjuJkAn50k08tuXZm9fuW2kEdGOZ41bPmzIG2rYZPn+emvwrYrSK0p+vuR9rUxKdVhbb1ef2VtjVJKbvgX59Rnf/g/SDUIHU6Sk+dCaHy/weN5Lia9jbZjQAAAABJRU5ErkJggg=="` | - createdby.png - base64 | | gitops.css | string | `"/* gitops default css */\n"` | - multiline string - gitops.css | | gitops.favicon | string | `"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"` | - favicon in base64 | -| gitops.json | string | `"{\n \"graphql\": {\n \"boardCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\",\n \"plural\": \"Cases\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\",\n \"plural\": \"Experiments\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\",\n \"plural\": \"Aliquots\"\n }\n ],\n \"chartCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\"\n }\n ],\n \"projectDetails\": \"boardCounts\"\n },\n \"components\": {\n \"appName\": \"Generic Data Commons Portal\",\n \"index\": {\n \"introduction\": {\n \"heading\": \"Data Commons\",\n \"text\": \"The Generic Data Commons supports the management, analysis and sharing of data for the research community.\",\n \"link\": \"/submission\"\n },\n \"buttons\": [\n {\n \"name\": \"Define Data Field\",\n \"icon\": \"data-field-define\",\n \"body\": \"The Generic Data Commons define the data in a general way. Please study the dictionary before you start browsing.\",\n \"link\": \"/DD\",\n \"label\": \"Learn more\"\n },\n {\n \"name\": \"Explore Data\",\n \"icon\": \"data-explore\",\n \"body\": \"The Exploration Page gives you insights and a clear overview under selected factors.\",\n \"link\": \"/explorer\",\n \"label\": \"Explore data\"\n },\n {\n \"name\": \"Access Data\",\n \"icon\": \"data-access\",\n \"body\": \"Use our selected tool to filter out the data you need.\",\n \"link\": \"/query\",\n \"label\": \"Query data\"\n },\n {\n \"name\": \"Submit Data\",\n \"icon\": \"data-submit\",\n \"body\": \"Submit Data based on the dictionary.\",\n \"link\": \"/submission\",\n \"label\": \"Submit data\"\n }\n ]\n },\n \"navigation\": {\n \"title\": \"Generic Data Commons\",\n \"items\": [\n {\n \"icon\": \"dictionary\",\n \"link\": \"/DD\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Dictionary\"\n },\n {\n \"icon\": \"exploration\",\n \"link\": \"/explorer\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Exploration\"\n },\n {\n \"icon\": \"query\",\n \"link\": \"/query\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Query\"\n },\n {\n \"icon\": \"workspace\",\n \"link\": \"/workspace\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Workspace\"\n },\n {\n \"icon\": \"profile\",\n \"link\": \"/identity\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Profile\"\n }\n ]\n },\n \"topBar\": {\n \"items\": [\n {\n \"icon\": \"upload\",\n \"link\": \"/submission\",\n \"name\": \"Submit Data\"\n },\n {\n \"link\": \"https://gen3.org/resources/user\",\n \"name\": \"Documentation\"\n }\n ]\n },\n \"login\": {\n \"title\": \"Generic Data Commons\",\n \"subTitle\": \"Explore, Analyze, and Share Data\",\n \"text\": \"This website supports the management, analysis and sharing of human disease data for the research community and aims to advance basic understanding of the genetic basis of complex traits and accelerate discovery and development of therapies, diagnostic tests, and other technologies for diseases like cancer.\",\n \"contact\": \"If you have any questions about access or the registration process, please contact \",\n \"email\": \"support@datacommons.io\"\n },\n \"certs\": {},\n \"footerLogos\": [\n {\n \"src\": \"/src/img/gen3.png\",\n \"href\": \"https://ctds.uchicago.edu/gen3\",\n \"alt\": \"Gen3 Data Commons\"\n },\n {\n \"src\": \"/src/img/createdby.png\",\n \"href\": \"https://ctds.uchicago.edu/\",\n \"alt\": \"Center for Translational Data Science at the University of Chicago\"\n }\n ]\n },\n \"requiredCerts\": [],\n \"featureFlags\": {\n \"explorer\": true,\n \"noIndex\": true,\n \"analysis\": false,\n \"discovery\": false,\n \"discoveryUseAggMDS\": false,\n \"studyRegistration\": false\n },\n \"dataExplorerConfig\": {\n \"charts\": {\n \"project_id\": {\n \"chartType\": \"count\",\n \"title\": \"Projects\"\n },\n \"case_id\": {\n \"chartType\": \"count\",\n \"title\": \"Cases\"\n },\n \"gender\": {\n \"chartType\": \"pie\",\n \"title\": \"Gender\"\n },\n \"race\": {\n \"chartType\": \"bar\",\n \"title\": \"Race\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"Case\",\n \"fields\":[\n \"project_id\",\n \"gender\",\n \"race\",\n \"ethnicity\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": false\n },\n \"dropdowns\": {},\n \"buttons\": [],\n \"guppyConfig\": {\n \"dataType\": \"case\",\n \"nodeCountTitle\": \"Cases\",\n \"fieldMapping\": [\n { \"field\": \"disease_type\", \"name\": \"Disease type\" },\n { \"field\": \"primary_site\", \"name\": \"Site where samples were collected\"}\n ],\n \"manifestMapping\": {\n \"resourceIndexType\": \"file\",\n \"resourceIdField\": \"object_id\",\n \"referenceIdFieldInResourceIndex\": \"case_id\",\n \"referenceIdFieldInDataIndex\": \"node_id\"\n },\n \"accessibleFieldCheckList\": [\"case_id\"],\n \"accessibleValidationField\": \"case_id\"\n }\n },\n \"fileExplorerConfig\": {\n \"charts\": {\n \"data_type\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Type\"\n },\n \"data_format\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Format\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"File\",\n \"fields\": [\n \"project_id\",\n \"data_type\",\n \"data_format\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": true,\n \"fields\": [\n \"project_id\",\n \"file_name\",\n \"file_size\",\n \"object_id\"\n ]\n },\n \"dropdowns\": {},\n \"guppyConfig\": {\n \"dataType\": \"file\",\n \"fieldMapping\": [\n { \"field\": \"object_id\", \"name\": \"GUID\" }\n ],\n \"nodeCountTitle\": \"Files\",\n \"manifestMapping\": {\n \"resourceIndexType\": \"case\",\n \"resourceIdField\": \"case_id\",\n \"referenceIdFieldInResourceIndex\": \"object_id\",\n \"referenceIdFieldInDataIndex\": \"object_id\"\n },\n \"accessibleFieldCheckList\": [\"case_id\"],\n \"accessibleValidationField\": \"case_id\",\n \"downloadAccessor\": \"object_id\"\n }\n }\n}\n"` | multiline string - gitops.json | +| gitops.json | string | `"{\n \"graphql\": {\n \"boardCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\",\n \"plural\": \"Cases\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\",\n \"plural\": \"Experiments\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\",\n \"plural\": \"Aliquots\"\n }\n ],\n \"chartCounts\": [\n {\n \"graphql\": \"_case_count\",\n \"name\": \"Case\"\n },\n {\n \"graphql\": \"_experiment_count\",\n \"name\": \"Experiment\"\n },\n {\n \"graphql\": \"_aliquot_count\",\n \"name\": \"Aliquot\"\n }\n ],\n \"projectDetails\": \"boardCounts\"\n },\n \"components\": {\n \"appName\": \"Generic Data Commons Portal\",\n \"index\": {\n \"introduction\": {\n \"heading\": \"Data Commons\",\n \"text\": \"The Generic Data Commons supports the management, analysis and sharing of data for the research community.\",\n \"link\": \"/submission\"\n },\n \"buttons\": [\n {\n \"name\": \"Define Data Field\",\n \"icon\": \"data-field-define\",\n \"body\": \"The Generic Data Commons define the data in a general way. Please study the dictionary before you start browsing.\",\n \"link\": \"/DD\",\n \"label\": \"Learn more\"\n },\n {\n \"name\": \"Explore Data\",\n \"icon\": \"data-explore\",\n \"body\": \"The Exploration Page gives you insights and a clear overview under selected factors.\",\n \"link\": \"/explorer\",\n \"label\": \"Explore data\"\n },\n {\n \"name\": \"Access Data\",\n \"icon\": \"data-access\",\n \"body\": \"Use our selected tool to filter out the data you need.\",\n \"link\": \"/query\",\n \"label\": \"Query data\"\n },\n {\n \"name\": \"Submit Data\",\n \"icon\": \"data-submit\",\n \"body\": \"Submit Data based on the dictionary.\",\n \"link\": \"/submission\",\n \"label\": \"Submit data\"\n }\n ]\n },\n \"navigation\": {\n \"title\": \"Generic Data Commons\",\n \"items\": [\n {\n \"icon\": \"dictionary\",\n \"link\": \"/DD\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Dictionary\"\n },\n {\n \"icon\": \"exploration\",\n \"link\": \"/explorer\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Exploration\"\n },\n {\n \"icon\": \"query\",\n \"link\": \"/query\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Query\"\n },\n {\n \"icon\": \"workspace\",\n \"link\": \"/workspace\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Workspace\"\n },\n {\n \"icon\": \"profile\",\n \"link\": \"/identity\",\n \"color\": \"#a2a2a2\",\n \"name\": \"Profile\"\n }\n ]\n },\n \"topBar\": {\n \"items\": [\n {\n \"icon\": \"upload\",\n \"link\": \"/submission\",\n \"name\": \"Submit Data\"\n },\n {\n \"link\": \"https://gen3.org/resources/user\",\n \"name\": \"Documentation\"\n }\n ]\n },\n \"login\": {\n \"title\": \"Generic Data Commons\",\n \"subTitle\": \"Explore, Analyze, and Share Data\",\n \"text\": \"This website supports the management, analysis and sharing of human disease data for the research community and aims to advance basic understanding of the genetic basis of complex traits and accelerate discovery and development of therapies, diagnostic tests, and other technologies for diseases like cancer.\",\n \"contact\": \"If you have any questions about access or the registration process, please contact \",\n \"email\": \"support@datacommons.io\"\n },\n \"certs\": {},\n \"footerLogos\": [\n {\n \"src\": \"/src/img/gen3.png\",\n \"href\": \"https://ctds.uchicago.edu/gen3\",\n \"alt\": \"Gen3 Data Commons\"\n },\n {\n \"src\": \"/src/img/createdby.png\",\n \"href\": \"https://ctds.uchicago.edu/\",\n \"alt\": \"Center for Translational Data Science at the University of Chicago\"\n }\n ]\n },\n \"requiredCerts\": [],\n \"featureFlags\": {\n \"explorer\": true,\n \"noIndex\": true,\n \"analysis\": false,\n \"discovery\": false,\n \"discoveryUseAggMDS\": false,\n \"studyRegistration\": false\n },\n \"dataExplorerConfig\": {\n \"charts\": {\n \"project_id\": {\n \"chartType\": \"count\",\n \"title\": \"Projects\"\n },\n \"_case_id\": {\n \"chartType\": \"count\",\n \"title\": \"Cases\"\n },\n \"gender\": {\n \"chartType\": \"pie\",\n \"title\": \"Gender\"\n },\n \"race\": {\n \"chartType\": \"bar\",\n \"title\": \"Race\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"Case\",\n \"fields\":[\n \"project_id\",\n \"gender\",\n \"race\",\n \"ethnicity\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": false\n },\n \"dropdowns\": {},\n \"buttons\": [],\n \"guppyConfig\": {\n \"dataType\": \"case\",\n \"nodeCountTitle\": \"Cases\",\n \"fieldMapping\": [\n { \"field\": \"disease_type\", \"name\": \"Disease type\" },\n { \"field\": \"primary_site\", \"name\": \"Site where samples were collected\"}\n ],\n \"manifestMapping\": {\n \"resourceIndexType\": \"file\",\n \"resourceIdField\": \"object_id\",\n \"referenceIdFieldInResourceIndex\": \"_case_id\",\n \"referenceIdFieldInDataIndex\": \"node_id\"\n },\n \"accessibleFieldCheckList\": [\"_case_id\"],\n \"accessibleValidationField\": \"_case_id\"\n }\n },\n \"fileExplorerConfig\": {\n \"charts\": {\n \"data_type\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Type\"\n },\n \"data_format\": {\n \"chartType\": \"stackedBar\",\n \"title\": \"File Format\"\n }\n },\n \"filters\": {\n \"tabs\": [\n {\n \"title\": \"File\",\n \"fields\": [\n \"project_id\",\n \"data_type\",\n \"data_format\"\n ]\n }\n ]\n },\n \"table\": {\n \"enabled\": true,\n \"fields\": [\n \"project_id\",\n \"file_name\",\n \"file_size\",\n \"object_id\"\n ]\n },\n \"dropdowns\": {},\n \"guppyConfig\": {\n \"dataType\": \"file\",\n \"fieldMapping\": [\n { \"field\": \"object_id\", \"name\": \"GUID\" }\n ],\n \"nodeCountTitle\": \"Files\",\n \"manifestMapping\": {\n \"resourceIndexType\": \"case\",\n \"resourceIdField\": \"_case_id\",\n \"referenceIdFieldInResourceIndex\": \"object_id\",\n \"referenceIdFieldInDataIndex\": \"object_id\"\n },\n \"accessibleFieldCheckList\": [\"_case_id\"],\n \"accessibleValidationField\": \"_case_id\",\n \"downloadAccessor\": \"object_id\"\n }\n }\n}\n"` | multiline string - gitops.json | | gitops.logo | string | `"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"` | - logo in base64 | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre","tierAccessLimit":1000}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -38,7 +52,9 @@ A Helm chart for gen3 data-portal | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -49,37 +65,40 @@ A Helm chart for gen3 data-portal | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"quay.io/cdis/data-portal-prebuilt"` | | -| image.tag | string | `"brh.data-commons.org-feat-develop"` | | -| imagePullSecrets | list | `[]` | | -| labels.app | string | `"portal"` | | -| labels.public | string | `"yes"` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | -| portalApp | string | `"gitops"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `2` | | -| resources.limits.memory | string | `"4096Mi"` | | -| resources.requests.cpu | float | `0.6` | | -| resources.requests.memory | string | `"512Mi"` | | -| revisionHistoryLimit | int | `2` | | -| securityContext | object | `{}` | | -| selectorLabels.app | string | `"portal"` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| strategy.rollingUpdate.maxSurge | int | `2` | | -| strategy.rollingUpdate.maxUnavailable | string | `"25%"` | | -| strategy.type | string | `"RollingUpdate"` | | -| tolerations | list | `[]` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private`. | +| global.tierAccessLimit | int | `1000` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | +| image | map | `{"pullPolicy":"IfNotPresent","repository":"quay.io/cdis/data-portal","tag":"master"}` | Docker image information. | +| image.pullPolicy | string | `"IfNotPresent"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/data-portal"` | Docker repository. | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node selector to apply to the pod | +| partOf | string | `"Front-End"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{}` | Annotations to add to the pod | +| podSecurityContext | map | `{}` | Security context to apply to the pod | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"memory":"4096Mi"},"requests":{"cpu":2,"memory":"4096Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"memory":"4096Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.memory | string | `"4096Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":2,"memory":"4096Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `2` | The amount of CPU requested | +| resources.requests.memory | string | `"4096Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| securityContext | map | `{}` | Security context to apply to the container | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| strategy | map | `{"rollingUpdate":{"maxSurge":2,"maxUnavailable":"25%"},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `2` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `"25%"` | Maximum amount of pods that can be unavailable during the update. | +| tolerations | list | `[]` | Tolerations to apply to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/portal/templates/_helpers.tpl b/helm/portal/templates/_helpers.tpl index c9d82c78..391aa496 100644 --- a/helm/portal/templates/_helpers.tpl +++ b/helm/portal/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "portal.labels" -}} -helm.sh/chart: {{ include "portal.chart" . }} -{{ include "portal.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "portal.selectorLabels" -}} -app.kubernetes.io/name: {{ include "portal.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/portal/templates/deployment.yaml b/helm/portal/templates/deployment.yaml index c802b532..a8bc25ec 100644 --- a/helm/portal/templates/deployment.yaml +++ b/helm/portal/templates/deployment.yaml @@ -2,6 +2,11 @@ apiVersion: apps/v1 kind: Deployment metadata: name: portal-deployment + labels: + {{- include "portal.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: selector: matchLabels: @@ -16,7 +21,11 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: - {{- include "portal.labels" . | nindent 8 }} + {{- include "portal.selectorLabels" . | nindent 8 }} + public: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -52,22 +61,17 @@ spec: initialDelaySeconds: 180 periodSeconds: 60 timeoutSeconds: 30 - failureThreshold: 30 resources: - requests: - cpu: 0.8 - memory: 4096Mi - limits: - cpu: 2.0 - memory: 4096Mi + {{- toYaml .Values.resources | nindent 12 }} ports: - containerPort: 80 - containerPort: 443 # command: [ "/bin/bash", "-c", "--" ] # args: [ "while true; do sleep 30; done;" ] env: - - name: DOIT - value: "TOIT" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: HOSTNAME value: revproxy-service # disable npm 7's brand new update notifier to prevent Portal from stuck at starting up @@ -77,7 +81,7 @@ spec: - name: NODE_ENV value: "dev" - name: APP - value: {{ .Values.portalApp | quote }} + value: {{ .Values.global.portalApp | quote }} - name: GEN3_BUNDLE # optional: true value: "" @@ -97,12 +101,8 @@ spec: valueFrom: configMapKeyRef: name: manifest-global - # acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` key: tier_access_level - # for now making it optional so won't break anything optional: true -#needed to be adjusted to use the gen3 umbrella chart or local var ^ -#adding a var in helpers.tpl for later- Elise - name: TIER_ACCESS_LIMIT valueFrom: configMapKeyRef: diff --git a/helm/portal/templates/pdb.yaml b/helm/portal/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/portal/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/portal/templates/tests/test-connection.yaml b/helm/portal/templates/tests/test-connection.yaml index e23afb6e..2a138e30 100644 --- a/helm/portal/templates/tests/test-connection.yaml +++ b/helm/portal/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "portal.fullname" . }}:{{ .Values.service.port }}'] + args: ['portal-service:{{ .Values.service.port }}'] restartPolicy: Never diff --git a/helm/portal/values.yaml b/helm/portal/values.yaml index 696eb07f..70e52223 100644 --- a/helm/portal/values.yaml +++ b/helm/portal/values.yaml @@ -41,48 +41,62 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private`. tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: 1000 # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: - repository: quay.io/cdis/data-portal-prebuilt + # -- (string) Docker repository. + repository: quay.io/cdis/data-portal + # -- (string) Docker pull policy. pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "brh.data-commons.org-feat-develop" - + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "master" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. + # -- (string) The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" +# -- (map) Annotations to add to the pod podAnnotations: {} +# -- (map) Security context to apply to the pod podSecurityContext: {} # fsGroup: 2000 +# -- (map) Security context to apply to the container securityContext: {} # capabilities: # drop: @@ -91,67 +105,360 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node selector to apply to the pod nodeSelector: {} +# -- (list) Tolerations to apply to the pod tolerations: [] -selectorLabels: - app: portal - +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 2 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 25% -labels: - app: portal - public: "yes" - - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - portal + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: - cpu: 0.8 + # -- (string) The amount of CPU requested + cpu: 2.0 + # -- (string) The amount of memory requested memory: 4096Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: - cpu: 2.0 + # -- (string) The maximum amount of memory the container can use memory: 4096Mi -portalApp: "gitops" +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Front-End" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 # -- (map) GitOps configuration for portal gitops: # -- (string) multiline string - gitops.json # json: | - # + # { + # "graphql": { + # "boardCounts": [ + # { + # "graphql": "_case_count", + # "name": "Case", + # "plural": "Cases" + # }, + # { + # "graphql": "_experiment_count", + # "name": "Experiment", + # "plural": "Experiments" + # }, + # { + # "graphql": "_aliquot_count", + # "name": "Aliquot", + # "plural": "Aliquots" + # } + # ], + # "chartCounts": [ + # { + # "graphql": "_case_count", + # "name": "Case" + # }, + # { + # "graphql": "_experiment_count", + # "name": "Experiment" + # }, + # { + # "graphql": "_aliquot_count", + # "name": "Aliquot" + # } + # ], + # "projectDetails": "boardCounts" + # }, + # "components": { + # "appName": "Generic Data Commons Portal", + # "index": { + # "introduction": { + # "heading": "Data Commons", + # "text": "The Generic Data Commons supports the management, analysis and sharing of data for the research community.", + # "link": "/submission" + # }, + # "buttons": [ + # { + # "name": "Define Data Field", + # "icon": "data-field-define", + # "body": "The Generic Data Commons define the data in a general way. Please study the dictionary before you start browsing.", + # "link": "/DD", + # "label": "Learn more" + # }, + # { + # "name": "Explore Data", + # "icon": "data-explore", + # "body": "The Exploration Page gives you insights and a clear overview under selected factors.", + # "link": "/explorer", + # "label": "Explore data" + # }, + # { + # "name": "Access Data", + # "icon": "data-access", + # "body": "Use our selected tool to filter out the data you need.", + # "link": "/query", + # "label": "Query data" + # }, + # { + # "name": "Submit Data", + # "icon": "data-submit", + # "body": "Submit Data based on the dictionary.", + # "link": "/submission", + # "label": "Submit data" + # } + # ] + # }, + # "navigation": { + # "title": "Generic Data Commons", + # "items": [ + # { + # "icon": "dictionary", + # "link": "/DD", + # "color": "#a2a2a2", + # "name": "Dictionary" + # }, + # { + # "icon": "exploration", + # "link": "/explorer", + # "color": "#a2a2a2", + # "name": "Exploration" + # }, + # { + # "icon": "query", + # "link": "/query", + # "color": "#a2a2a2", + # "name": "Query" + # }, + # { + # "icon": "workspace", + # "link": "/workspace", + # "color": "#a2a2a2", + # "name": "Workspace" + # }, + # { + # "icon": "profile", + # "link": "/identity", + # "color": "#a2a2a2", + # "name": "Profile" + # } + # ] + # }, + # "topBar": { + # "items": [ + # { + # "icon": "upload", + # "link": "/submission", + # "name": "Submit Data" + # }, + # { + # "link": "https://gen3.org/resources/user", + # "name": "Documentation" + # } + # ] + # }, + # "login": { + # "title": "Generic Data Commons", + # "subTitle": "Explore, Analyze, and Share Data", + # "text": "This website supports the management, analysis and sharing of human disease data for the research community and aims to advance basic understanding of the genetic basis of complex traits and accelerate discovery and development of therapies, diagnostic tests, and other technologies for diseases like cancer.", + # "contact": "If you have any questions about access or the registration process, please contact ", + # "email": "support@datacommons.io" + # }, + # "certs": {}, + # "footerLogos": [ + # { + # "src": "/src/img/gen3.png", + # "href": "https://ctds.uchicago.edu/gen3", + # "alt": "Gen3 Data Commons" + # }, + # { + # "src": "/src/img/createdby.png", + # "href": "https://ctds.uchicago.edu/", + # "alt": "Center for Translational Data Science at the University of Chicago" + # } + # ] + # }, + # "requiredCerts": [], + # "featureFlags": { + # "explorer": true, + # "noIndex": true, + # "analysis": false, + # "discovery": false, + # "discoveryUseAggMDS": false, + # "studyRegistration": false + # }, + # "dataExplorerConfig": { + # "charts": { + # "project_id": { + # "chartType": "count", + # "title": "Projects" + # }, + # "_case_id": { + # "chartType": "count", + # "title": "Cases" + # }, + # "gender": { + # "chartType": "pie", + # "title": "Gender" + # }, + # "race": { + # "chartType": "bar", + # "title": "Race" + # } + # }, + # "filters": { + # "tabs": [ + # { + # "title": "Case", + # "fields":[ + # "project_id", + # "gender", + # "race", + # "ethnicity" + # ] + # } + # ] + # }, + # "table": { + # "enabled": false + # }, + # "dropdowns": {}, + # "buttons": [], + # "guppyConfig": { + # "dataType": "case", + # "nodeCountTitle": "Cases", + # "fieldMapping": [ + # { "field": "disease_type", "name": "Disease type" }, + # { "field": "primary_site", "name": "Site where samples were collected"} + # ], + # "manifestMapping": { + # "resourceIndexType": "file", + # "resourceIdField": "object_id", + # "referenceIdFieldInResourceIndex": "_case_id", + # "referenceIdFieldInDataIndex": "node_id" + # }, + # "accessibleFieldCheckList": ["_case_id"], + # "accessibleValidationField": "_case_id" + # } + # }, + # "fileExplorerConfig": { + # "charts": { + # "data_type": { + # "chartType": "stackedBar", + # "title": "File Type" + # }, + # "data_format": { + # "chartType": "stackedBar", + # "title": "File Format" + # } + # }, + # "filters": { + # "tabs": [ + # { + # "title": "File", + # "fields": [ + # "project_id", + # "data_type", + # "data_format" + # ] + # } + # ] + # }, + # "table": { + # "enabled": true, + # "fields": [ + # "project_id", + # "file_name", + # "file_size", + # "object_id" + # ] + # }, + # "dropdowns": {}, + # "guppyConfig": { + # "dataType": "file", + # "fieldMapping": [ + # { "field": "object_id", "name": "GUID" } + # ], + # "nodeCountTitle": "Files", + # "manifestMapping": { + # "resourceIndexType": "case", + # "resourceIdField": "_case_id", + # "referenceIdFieldInResourceIndex": "object_id", + # "referenceIdFieldInDataIndex": "object_id" + # }, + # "accessibleFieldCheckList": ["_case_id"], + # "accessibleValidationField": "_case_id", + # "downloadAccessor": "object_id" + # } + # } + # } # -- (string) - favicon in base64 favicon: "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nP84fZz/OH2c/zh9nP84fZz/OH6d/y9Zbf8gISX/Iykw/zVxjP83e5n/KUNR/yAeIv8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyL/ISMo/y5Vaf84fZz/OH2c/zh+nf8xYXj/LE5f/zh8m/84fZz/OH2c4SAfI+8gHyP/IB8k/yEkKf8gHyP/IB8j/yAfI/8pQU//NnaT/zh+nf84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fp3/MWN7/yIoL/8gHiH/Iykw/zVxjP84fp7/NnOQ/yY4Q/8gHiH/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB4i/yEkKv8uV2v/OH2b/zh9nf84fZz/N3eV/zh8m/84fZz/OH2c8CAfI/ggHyP/IB8j/yAfI/8gHiL/ICAk/ypFVP82d5T/OH6d/zh9nP84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fZz/OH2c/zh+nv8zaIH/Iy01/yAeIv8gHiL/Iysz/zRtiP84fp7/OH6d/zRviv8lMjz/IB4h/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAeIv8hJCn/LVNm/zd7mf84fp3/OH2c/zh9nP84fZz/OH2c+SAeIv0gHyP/IB8j/yAeIv8hJCn/LE9h/zd5l/84fp3/OH2c/zh9nP84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fZz/OH6e/zNpgv8kLzj/IB4i/yAeIv8iJSv/LVBj/ypIWf82dZL/OH6d/zh+nf80bIb/JTE7/yAeIf8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHiL/ISIm/ypIWf82d5T/OH6d/zh9nP84fZz/OH2c/iAfI/8gHyP/IB8j/yQuNv8wXnT/OHyb/zh9nf84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fZz/OH2c/zh9nP84fp3/MmeA/yQuN/8gHiL/IB4i/yEiJ/8uVWn/NXGN/yMqMv8pQ1H/N3iW/zh+nf84fp3/NG2H/yU0Pv8gHiL/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8nO0f/NGyG/zh9nP84fZz/OH2c/yAf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" diff --git a/helm/requestor/Chart.yaml b/helm/requestor/Chart.yaml index f17659fb..552ae3bc 100644 --- a/helm/requestor/Chart.yaml +++ b/helm/requestor/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -26,5 +26,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/requestor/README.md b/helm/requestor/README.md index 21eeecfc..612a3faa 100644 --- a/helm/requestor/README.md +++ b/helm/requestor/README.md @@ -1,6 +1,6 @@ # requestor -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.9](https://img.shields.io/badge/Version-0.1.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Requestor Service @@ -8,30 +8,36 @@ A Helm chart for gen3 Requestor Service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"requestor"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| arboristUrl | string | `"http://arborist-service"` | | -| args[0] | string | `"-c"` | | -| args[1] | string | `"/env/bin/alembic upgrade head\n"` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| command[0] | string | `"/bin/sh"` | | -| containerPort[0].containerPort | int | `80` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["requestor"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["requestor"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["requestor"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["requestor"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["requestor"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| arboristUrl | string | `"http://arborist-service"` | Arborist service URL. | +| args | list | `["-c","/env/bin/alembic upgrade head\n"]` | Arguments to pass to the init container. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| command | list | `["/bin/sh"]` | Command to run for the init container. | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| global | map | `{"addDbgap":false,"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"onlyDbgap":false,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","slack_send_dbgap":false,"slack_webhook":"None","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/helm-test/user.yaml","usersync":false}` | Global configuration options. | +| global.addDbgap | bool | `false` | Force attempting a dbgap sync if "true", falls back on user.yaml | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -44,7 +50,10 @@ A Helm chart for gen3 Requestor Service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.onlyDbgap | bool | `false` | Forces ONLY a dbgap sync if "true", IGNORING user.yaml | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -55,45 +64,51 @@ A Helm chart for gen3 Requestor Service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | +| global.slack_send_dbgap | bool | `false` | Will echo what files we are seeing on dbgap ftp to Slack. | +| global.slack_webhook | string | `"None"` | Slack webhook endpoint used with certain jobs. | | global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/requestor"` | | -| image.tag | string | `"master"` | | -| initContainerName | string | `"requestor-db-migrate"` | | -| initResources.limits.cpu | float | `0.8` | | -| initResources.limits.memory | string | `"512Mi"` | | -| livenessProbe.httpGet.path | string | `"/_status"` | | -| livenessProbe.httpGet.port | int | `80` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| global.userYamlS3Path | string | `"s3://cdis-gen3-users/helm-test/user.yaml"` | Path to the user.yaml file in S3. | +| global.usersync | bool | `false` | Whether to run Fence usersync or not. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/requestor","tag":"master"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/requestor"` | Docker repository. | +| image.tag | string | `"master"` | Overrides the image tag whose default is the chart appVersion. | +| initContainerName | string | `"requestor-db-migrate"` | Name of the init container. | +| initResources | map | `{"limits":{"cpu":0.8,"memory":"512Mi"}}` | Resource limits for the init container. | +| initResources.limits | map | `{"cpu":0.8,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| initResources.limits.cpu | string | `0.8` | The maximum amount of CPU the container can use | +| initResources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| partOf | string | `"Authentication"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| readinessProbe.httpGet.path | string | `"/_status"` | | -| readinessProbe.httpGet.port | int | `80` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | releaseLabel | string | `"production"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| revisionHistoryLimit | int | `2` | | -| service.port[0].name | string | `"http"` | | -| service.port[0].port | int | `80` | | -| service.port[0].protocol | string | `"TCP"` | | -| service.port[0].targetPort | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| volumeMounts | string | `nil` | | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":[{"name":"http","port":80,"protocol":"TCP","targetPort":80}],"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `[{"name":"http","port":80,"protocol":"TCP","targetPort":80}]` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| volumeMounts | list | `nil` | Volumes to mount to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/requestor/templates/_helpers.tpl b/helm/requestor/templates/_helpers.tpl index 36ae7f51..899b723c 100644 --- a/helm/requestor/templates/_helpers.tpl +++ b/helm/requestor/templates/_helpers.tpl @@ -34,22 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "requestor.labels" -}} -helm.sh/chart: {{ include "requestor.chart" . }} -{{ include "requestor.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "requestor.selectorLabels" -}} -app.kubernetes.io/name: {{ include "requestor.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "requestor.name" . }} -release: {{ .Values.releaseLabel }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -63,17 +67,6 @@ Create the name of the service account to use {{- end }} {{- end }} -{{/* -Define ddEnabled -*/}} -{{- define "requestor.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} -{{- end }} - {{/* Postgres Password lookup */}} diff --git a/helm/requestor/templates/deployment.yaml b/helm/requestor/templates/deployment.yaml index fcb15119..24876c59 100644 --- a/helm/requestor/templates/deployment.yaml +++ b/helm/requestor/templates/deployment.yaml @@ -2,6 +2,11 @@ apiVersion: apps/v1 kind: Deployment metadata: name: requestor-deployment + labels: + {{- include "requestor.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -17,17 +22,14 @@ spec: template: metadata: labels: + {{- include "requestor.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' dbrequestor: 'yes' - {{- if eq (include "requestor.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "requestor" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "requestor.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -81,18 +83,19 @@ spec: value: {{ . }} {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.livenessProbe }} livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.readinessProbe }} + httpGet: + path: /_status + port: 80 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.containerPort}} + httpGet: + path: /_status + port: 80 ports: - {{- toYaml . | nindent 12}} - {{- end }} + - containerPort: 80 {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} @@ -106,6 +109,9 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: DB_PORT value: "5432" - name: DB_HOST diff --git a/helm/requestor/templates/pdb.yaml b/helm/requestor/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/requestor/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/requestor/templates/tests/test-connection.yaml b/helm/requestor/templates/tests/test-connection.yaml index 8d9970ec..244fd6ed 100644 --- a/helm/requestor/templates/tests/test-connection.yaml +++ b/helm/requestor/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "requestor.fullname" . }}:{{ .Values.service.port }}'] + args: ['requestor-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/requestor/values.yaml b/helm/requestor/values.yaml index 6cdded9c..437ffac6 100644 --- a/helm/requestor/values.yaml +++ b/helm/requestor/values.yaml @@ -44,11 +44,21 @@ global: logsBucket: logs-gen3 # -- (bool) Whether to sync data from dbGaP. syncFromDbgap: false + # -- (bool) Force attempting a dbgap sync if "true", falls back on user.yaml + addDbgap: false + # -- (bool) Forces ONLY a dbgap sync if "true", IGNORING user.yaml + onlyDbgap: false # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml + userYamlS3Path: s3://cdis-gen3-users/helm-test/user.yaml + # -- (bool) Whether to run Fence usersync or not. + usersync: false + # -- (string) Slack webhook endpoint used with certain jobs. + slack_webhook: None + # -- (bool) Will echo what files we are seeing on dbgap ftp to Slack. + slack_send_dbgap: false # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,6 +66,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -73,104 +87,151 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false # Deployment releaseLabel: production +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 -dataDog: - enabled: false - env: dev - +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - requestor + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/requestor + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "master" # Environment Variables +# -- (string) Arborist service URL. arboristUrl: http://arborist-service -livenessProbe: - httpGet: - path: /_status - port: 80 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 -readinessProbe: - httpGet: - path: /_status - port: 80 - -containerPort: - - containerPort: 80 - +# -- (list) Volumes to mount to the container. volumeMounts: # - name: "config-volume" # readOnly: true # mountPath: "/src/requestor-config.yaml" # subPath: "requestor-config.yaml" +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi # Init Container +# -- (string) Name of the init container. initContainerName: requestor-db-migrate - +# -- (map) Resource limits for the init container. initResources: + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 0.8 + # -- (string) The maximum amount of memory the container can use memory: 512Mi - +# -- (list) Command to run for the init container. command: ["/bin/sh"] - +# -- (list) Arguments to pass to the init container. args: - "-c" - | /env/bin/alembic upgrade head # Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: - protocol: TCP port: 80 targetPort: 80 name: http + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Authentication" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/revproxy/Chart.yaml b/helm/revproxy/Chart.yaml index 7d50708d..09320900 100644 --- a/helm/revproxy/Chart.yaml +++ b/helm/revproxy/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.12 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/revproxy/README.md b/helm/revproxy/README.md index 428279e4..ff1b01e7 100644 --- a/helm/revproxy/README.md +++ b/helm/revproxy/README.md @@ -1,20 +1,32 @@ # revproxy -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.12](https://img.shields.io/badge/Version-0.1.12-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 revproxy +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| fullnameOverride | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","tls":{"cert":null,"key":null},"userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{}` | Affinity to use for the deployment. | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre","tierAccessLimit":1000,"tls":{"cert":null,"key":null}}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -27,7 +39,9 @@ A Helm chart for gen3 revproxy | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -38,24 +52,24 @@ A Helm chart for gen3 revproxy | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"nginx"` | | -| image.tag | string | `"stable-perl"` | | -| imagePullSecrets | list | `[]` | | -| ingress.annotations | object | `{}` | | -| ingress.className | string | `""` | | -| ingress.enabled | bool | `false` | | -| ingress.hosts[0].host | string | `"chart-example.local"` | | -| ingress.hosts[0].paths[0].path | string | `"/"` | | -| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | -| ingress.tls | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podSecurityContext | object | `{}` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| global.tierAccessLimit | int | `1000` | Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. | +| image | map | `{"pullPolicy":"Always","repository":"nginx","tag":"stable-perl"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"nginx"` | Docker repository. | +| image.tag | string | `"stable-perl"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| ingress | map | `{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"chart-example.local","paths":[{"path":"/","pathType":"Prefix"}]}],"tls":[]}` | Configuration for revproxy ingress. | +| ingress.annotations | map | `{}` | Annotations to add to the ingress. | +| ingress.className | string | `""` | The ingress class name. | +| ingress.enabled | bool | `false` | Whether to create the ingress | +| ingress.hosts | list | `[{"host":"chart-example.local","paths":[{"path":"/","pathType":"Prefix"}]}]` | Where to route the traffic. | +| ingress.tls | list | `[]` | To secure an Ingress by specifying a secret that contains a TLS private key and certificate. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node selector labels. | +| partOf | string | `"Front-End"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{}` | Annotations to add to the pod. | +| podSecurityContext | map | `{}` | Pod-level security context. | | postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | @@ -63,26 +77,30 @@ A Helm chart for gen3 revproxy | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| revisionHistoryLimit | int | `2` | | -| revproxyElb.gen3SecretsFolder | string | `"Gen3Secrets"` | | -| revproxyElb.sslCert | string | `""` | | -| revproxyElb.targetPortHTTP | int | `80` | | -| revproxyElb.targetPortHTTPS | int | `443` | | -| securityContext | object | `{}` | | -| service.port | int | `80` | | -| service.type | string | `"NodePort"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| tolerations | list | `[]` | | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.1,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of cpu the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| revproxyElb | map | `{"gen3SecretsFolder":"Gen3Secrets","sslCert":"","targetPortHTTP":80,"targetPortHTTPS":443}` | Configuration for depricated revproxy service ELB. | +| securityContext | map | `{}` | Container-level security context. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"NodePort"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"NodePort"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| tolerations | list | `[]` | Tolerations to use for the deployment. | | userhelperEnabled | bool | `false` | | ---------------------------------------------- diff --git a/helm/revproxy/gen3.nginx.conf/fence-service-ga4gh.conf b/helm/revproxy/gen3.nginx.conf/fence-service-ga4gh.conf index b017919f..522fad15 100644 --- a/helm/revproxy/gen3.nginx.conf/fence-service-ga4gh.conf +++ b/helm/revproxy/gen3.nginx.conf/fence-service-ga4gh.conf @@ -3,8 +3,8 @@ location ~ \/ga4gh\/drs\/v1\/objects\/(.*)\/access { return 403 "failed csrf check"; } - set $proxy_service "presigned_url_fence"; - set $upstream http://presigned_url_fence-service$des_domain; + set $proxy_service "presigned-url-fence"; + set $upstream http://presigned-url-fence-service$des_domain; rewrite ^/user/(.*) /$1 break; proxy_pass $upstream; } diff --git a/helm/revproxy/logrotate-nginx.conf b/helm/revproxy/logrotate-nginx.conf deleted file mode 100644 index fc6b7e3c..00000000 --- a/helm/revproxy/logrotate-nginx.conf +++ /dev/null @@ -1,9 +0,0 @@ -# nginx log rotation -/var/log/nginx { - weekly - size 10M - postrotate - [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid` - endscript - rotate 5 -} diff --git a/helm/revproxy/templates/_helpers.tpl b/helm/revproxy/templates/_helpers.tpl index 2019b5ad..24011557 100644 --- a/helm/revproxy/templates/_helpers.tpl +++ b/helm/revproxy/templates/_helpers.tpl @@ -34,10 +34,12 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "revproxy.labels" -}} -helm.sh/chart: {{ include "revproxy.chart" . }} -{{ include "revproxy.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} {{- end }} @@ -45,10 +47,13 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} Selector labels */}} {{- define "revproxy.selectorLabels" -}} -app.kubernetes.io/name: {{ include "revproxy.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: revproxy -#GEN3_DATE_LABEL +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/revproxy/templates/deployment.yaml b/helm/revproxy/templates/deployment.yaml index afa84e1f..878a1466 100644 --- a/helm/revproxy/templates/deployment.yaml +++ b/helm/revproxy/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: revproxy-deployment labels: {{- include "revproxy.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} annotations: gen3.io/network-ingress: "portal,sowerjob" spec: @@ -26,6 +29,9 @@ spec: {{- end }} labels: {{- include "revproxy.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} {{- if .Values.userhelperEnabled }} userhelper: "yes" {{- end}} @@ -80,6 +86,9 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: POD_NAMESPACE valueFrom: fieldRef: diff --git a/helm/revproxy/templates/ingress.yaml b/helm/revproxy/templates/ingress_aws.yaml similarity index 71% rename from helm/revproxy/templates/ingress.yaml rename to helm/revproxy/templates/ingress_aws.yaml index d16cc7fb..7b0760c3 100644 --- a/helm/revproxy/templates/ingress.yaml +++ b/helm/revproxy/templates/ingress_aws.yaml @@ -1,27 +1,17 @@ +{{- if .Values.global.aws.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: revproxy - {{- with .Values.ingress.annotations }} + name: revproxy-alb annotations: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- if .Values.global.aws.enabled }} alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/tags: Environment={{ .Values.global.environment }} alb.ingress.kubernetes.io/certificate-arn: {{ .Values.global.revproxyArn }} alb.ingress.kubernetes.io/group.name: {{ .Values.global.environment }} alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' - {{- end }} spec: - ingressClassName: nginx - {{- if .Values.global.aws.enabled }} - {{- end }} - {{- with .Values.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end }} + ingressClassName: alb rules: - host: {{ default .Values.global.hostname .Values.hostname }} http: @@ -32,4 +22,5 @@ spec: service: name: revproxy-service port: - number: 80 \ No newline at end of file + number: 80 +{{- end }} \ No newline at end of file diff --git a/helm/elasticsearch/templates/ingress.yaml b/helm/revproxy/templates/ingress_default.yaml similarity index 90% rename from helm/elasticsearch/templates/ingress.yaml rename to helm/revproxy/templates/ingress_default.yaml index 3f8cc2aa..18efa92a 100644 --- a/helm/elasticsearch/templates/ingress.yaml +++ b/helm/revproxy/templates/ingress_default.yaml @@ -1,5 +1,5 @@ {{- if .Values.ingress.enabled -}} -{{- $fullName := include "elasticsearch.fullname" . -}} +{{- $fullName := include "revproxy.fullname" . -}} {{- $svcPort := .Values.service.port -}} {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} @@ -17,7 +17,7 @@ kind: Ingress metadata: name: {{ $fullName }} labels: - {{- include "elasticsearch.labels" . | nindent 4 }} + {{- include "revproxy.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} @@ -49,11 +49,11 @@ spec: backend: {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: - name: {{ $fullName }} + name: revproxy-service port: number: {{ $svcPort }} {{- else }} - serviceName: {{ $fullName }} + serviceName: revproxy-service servicePort: {{ $svcPort }} {{- end }} {{- end }} diff --git a/helm/revproxy/templates/ingress_dev.yaml b/helm/revproxy/templates/ingress_dev.yaml new file mode 100644 index 00000000..c7c83c4e --- /dev/null +++ b/helm/revproxy/templates/ingress_dev.yaml @@ -0,0 +1,24 @@ +{{- if not .Values.ingress.enabled -}} +{{- if and (eq .Values.global.dev true) (eq .Values.global.aws.enabled false) }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: revproxy-dev +spec: +{{- if .Values.global.dev }} + tls: + - secretName: gen3-certs + {{- end }} + rules: + - host: {{ default .Values.global.hostname .Values.hostname }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: revproxy-service + port: + number: 80 + {{- end }} +{{- end }} diff --git a/helm/revproxy/templates/pdb.yaml b/helm/revproxy/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/revproxy/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/revproxy/templates/tests/test-connection.yaml b/helm/revproxy/templates/tests/test-connection.yaml index c73c44bd..9266de3f 100644 --- a/helm/revproxy/templates/tests/test-connection.yaml +++ b/helm/revproxy/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "revproxy.fullname" . }}-test-connection" + name: "revproxy-test-connection" labels: {{- include "revproxy.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "revproxy.fullname" . }}:{{ .Values.service.port }}'] + args: ['revproxy-service:{{ .Values.service.port }}/_status'] restartPolicy: Never diff --git a/helm/revproxy/values.yaml b/helm/revproxy/values.yaml index 99318fd1..9c1c9b02 100644 --- a/helm/revproxy/values.yaml +++ b/helm/revproxy/values.yaml @@ -45,20 +45,22 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: 1000 # -- (bool) Whether network policies are enabled. netPolicy: true # -- (int) Number of dispatcher jobs. dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -77,32 +79,45 @@ postgres: # -- (string) Password for Postgres. Will be autogenerated if left empty. password: +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: nginx + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "stable-perl" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. + # -- (string) The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" +# -- (map) Annotations to add to the pod. podAnnotations: {} +# -- (map) Pod-level security context. podSecurityContext: {} # fsGroup: 2000 +# -- (map) Container-level security context. securityContext: {} # capabilities: # drop: @@ -111,60 +126,109 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: NodePort + # -- (int) The port number that the service exposes. port: 80 +# -- (map) Configuration for revproxy ingress. ingress: + # -- (bool) Whether to create the ingress enabled: false + # -- (string) The ingress class name. className: "" + # -- (map) Annotations to add to the ingress. annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" + # -- (list) Where to route the traffic. hosts: - host: chart-example.local paths: - path: / - pathType: ImplementationSpecific - tls: - - secretName: gen3-certs + pathType: Prefix + # -- (list) To secure an Ingress by specifying a secret that contains a TLS private key and certificate. + tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of cpu the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled or not enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node selector labels. nodeSelector: {} +# -- (list) Tolerations to use for the deployment. tolerations: [] +# -- (map) Affinity to use for the deployment. affinity: {} +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 + +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 userhelperEnabled: false +# -- (map) Configuration for depricated revproxy service ELB. revproxyElb: sslCert: "" targetPortHTTPS: 443 targetPortHTTP: 80 gen3SecretsFolder: Gen3Secrets + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Front-End" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/sheepdog/Chart.yaml b/helm/sheepdog/Chart.yaml index d18b8211..5156d475 100644 --- a/helm/sheepdog/Chart.yaml +++ b/helm/sheepdog/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.11 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,5 +25,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/sheepdog/README.md b/helm/sheepdog/README.md index eea22bbc..2b0a6806 100644 --- a/helm/sheepdog/README.md +++ b/helm/sheepdog/README.md @@ -1,6 +1,6 @@ # sheepdog -![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.11](https://img.shields.io/badge/Version-0.1.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 Sheepdog Service @@ -8,37 +8,37 @@ A Helm chart for gen3 Sheepdog Service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"sheepdog"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| arboristUrl | string | `"http://arborist-service.default.svc.cluster.local"` | | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sheepdog"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sheepdog"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sheepdog"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["sheepdog"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["sheepdog"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| arboristUrl | string | `"http://arborist-service"` | URL for the arborist service | | authNamespace | string | `"default"` | | -| automountServiceAccountToken | bool | `false` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| dataDog.enabled | bool | `false` | | -| dataDog.env | string | `"dev"` | | -| ddEnv | string | `nil` | | -| ddLogsInjection | string | `nil` | | -| ddProfilingEnabled | string | `nil` | | -| ddService | string | `nil` | | -| ddTraceAgentHostname | string | `nil` | | -| ddTraceEnabled | string | `nil` | | -| ddTraceSampleRate | string | `nil` | | -| ddVersion | string | `nil` | | -| dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | | -| fenceUrl | string | `"http://fence-service"` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| automountServiceAccountToken | bool | `false` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| dataDog | bool | `{"enabled":false,"env":"dev"}` | Whether Datadog is enabled. | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | +| fenceUrl | string | `"http://fence-service"` | URL for the fence service | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -51,7 +51,9 @@ A Helm chart for gen3 Sheepdog Service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -62,58 +64,58 @@ A Helm chart for gen3 Sheepdog Service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/sheepdog"` | | -| image.tag | string | `"helm-test"` | | -| indexdUrl | string | `"http://indexd-service"` | | -| livenessProbe.httpGet.path | string | `"/_status?timeout=20"` | | -| livenessProbe.httpGet.port | int | `80` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `60` | | -| livenessProbe.timeoutSeconds | int | `30` | | -| podAnnotations."gen3.io/network-ingress" | string | `"sheepdog"` | | -| ports[0].containerPort | int | `80` | | -| ports[1].containerPort | int | `443` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/sheepdog","tag":"helm-test"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/sheepdog"` | Docker repository. | +| image.tag | string | `"helm-test"` | Overrides the image tag whose default is the chart appVersion. | +| indexdUrl | string | `"http://indexd-service"` | URL for the indexd service | +| partOf | string | `"Core-Service"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{"gen3.io/network-ingress":"sheepdog"}` | Annotations to add to the pod | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| readinessProbe.httpGet.path | string | `"/_status?timeout=2"` | | -| readinessProbe.httpGet.port | int | `80` | | -| readinessProbe.initialDelaySeconds | int | `30` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| postgresql.primary.persistence.enabled | bool | `false` | Option to persist the dbs data. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | | releaseLabel | string | `"production"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.3` | | -| resources.requests.memory | string | `"12Mi"` | | -| revisionHistoryLimit | int | `2` | | -| secrets.fence.database | string | `"fence"` | | -| secrets.fence.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | | -| secrets.fence.password | string | `"postgres"` | | -| secrets.fence.user | string | `"postgres"` | | -| secrets.gdcapi.secretKey | string | `nil` | | -| secrets.indexd.password | string | `"postgres"` | | -| secrets.sheepdog.database | string | `"sheepdog"` | | -| secrets.sheepdog.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | | -| secrets.sheepdog.password | string | `"postgres"` | | -| secrets.sheepdog.user | string | `"postgres"` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| terminationGracePeriodSeconds | int | `50` | | -| volumeMounts[0].mountPath | string | `"/var/www/sheepdog/wsgi.py"` | | -| volumeMounts[0].name | string | `"config-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"wsgi.py"` | | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"512Mi"},"requests":{"cpu":0.3,"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.3,"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.3` | The amount of CPU requested | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| revisionHistoryLimit | int | `2` | Number of old revisions to retain | +| secrets | map | `{"fence":{"database":"fence","host":"postgres-postgresql.postgres.svc.cluster.local","password":"postgres","user":"postgres"},"gdcapi":{"secretKey":null},"indexd":{"password":"postgres"},"sheepdog":{"database":"sheepdog","host":"postgres-postgresql.postgres.svc.cluster.local","password":"postgres","user":"postgres"}}` | Values for sheepdog secret. | +| secrets.fence | map | `{"database":"fence","host":"postgres-postgresql.postgres.svc.cluster.local","password":"postgres","user":"postgres"}` | Values for sheepdog's access to the fence database. | +| secrets.fence.database | string | `"fence"` | Database name for fence's db. | +| secrets.fence.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | Host for fence's db. | +| secrets.fence.password | string | `"postgres"` | Password to fence's db. | +| secrets.fence.user | string | `"postgres"` | User for fence's db. | +| secrets.gdcapi.secretKey | string | `nil` | GDCAPI token. | +| secrets.indexd | map | `{"password":"postgres"}` | Values for sheepdog's access to indexd database. | +| secrets.indexd.password | string | `"postgres"` | Password to indexd's db. | +| secrets.sheepdog | map | `{"database":"sheepdog","host":"postgres-postgresql.postgres.svc.cluster.local","password":"postgres","user":"postgres"}` | Values for sheepdog's database. | +| secrets.sheepdog.database | string | `"sheepdog"` | Database name for sheepdog's db. | +| secrets.sheepdog.host | string | `"postgres-postgresql.postgres.svc.cluster.local"` | Host for sheepdog's db. | +| secrets.sheepdog.password | string | `"postgres"` | Password to sheepdog's db. | +| secrets.sheepdog.user | string | `"postgres"` | User for sheepdog's db. | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| terminationGracePeriodSeconds | int | `50` | sheepdog transactions take forever - try to let the complete before termination | +| volumeMounts | list | `[{"mountPath":"/var/www/sheepdog/wsgi.py","name":"config-volume","readOnly":true,"subPath":"wsgi.py"}]` | Volumes to mount to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/sheepdog/sheepdog-secret/wsgi.py b/helm/sheepdog/sheepdog-secret/wsgi.py index 785b684b..0f020376 100644 --- a/helm/sheepdog/sheepdog-secret/wsgi.py +++ b/helm/sheepdog/sheepdog-secret/wsgi.py @@ -18,7 +18,7 @@ config['INDEX_CLIENT'] = { 'host': environ.get('INDEX_CLIENT_HOST') or 'http://indexd-service', 'version': 'v0', - 'auth': (environ.get( "INDEXD_USER", 'gdcapi'), environ.get( "INDEXD_PASS") ), + 'auth': (environ.get( "INDEXD_USER", 'sheepdog'), environ.get( "INDEXD_PASS") ), } config["PSQLGRAPH"] = { diff --git a/helm/sheepdog/templates/_helpers.tpl b/helm/sheepdog/templates/_helpers.tpl index e98750a2..1c935c37 100644 --- a/helm/sheepdog/templates/_helpers.tpl +++ b/helm/sheepdog/templates/_helpers.tpl @@ -34,22 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "sheepdog.labels" -}} -helm.sh/chart: {{ include "sheepdog.chart" . }} -{{ include "sheepdog.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "sheepdog.selectorLabels" -}} -app.kubernetes.io/name: {{ include "sheepdog.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app: {{ include "sheepdog.name" . }} -release: {{ .Values.releaseLabel }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* @@ -87,17 +91,6 @@ Create the name of the service account to use {{- end }} {{- end }} -{{/* -Define ddEnabled -*/}} -{{- define "sheepdog.ddEnabled" -}} -{{- if .Values.global }} -{{- .Values.global.ddEnabled }} -{{- else}} -{{- .Values.dataDog.enabled }} -{{- end }} -{{- end }} - {{/* Define dictionaryUrl */}} diff --git a/helm/sheepdog/templates/deployment.yaml b/helm/sheepdog/templates/deployment.yaml index 1223455d..d9505264 100644 --- a/helm/sheepdog/templates/deployment.yaml +++ b/helm/sheepdog/templates/deployment.yaml @@ -8,6 +8,9 @@ metadata: {{- end }} labels: {{- include "sheepdog.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} @@ -23,17 +26,14 @@ spec: template: metadata: labels: + {{- include "sheepdog.selectorLabels" . | nindent 8 }} # gen3 networkpolicy labels netnolimit: 'yes' public: 'yes' s3: 'yes' - {{- if eq (include "sheepdog.ddEnabled" . ) "true" }} - tags.datadoghq.com/service: "sheepdog" - # TODO: move this to helpers so we can have this populated from a configmap - tags.datadoghq.com/env: {{ .Values.dataDog.env }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Chart.AppVersion }} - {{- end }} - {{- include "sheepdog.selectorLabels" . | nindent 8 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: {{- with .Values.affinity }} affinity: @@ -51,6 +51,9 @@ spec: - name: sheepdog-init image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} - name: DICTIONARY_URL value: {{ .Values.dictionaryUrl }} - name: PGHOST @@ -103,18 +106,21 @@ spec: - name: sheepdog image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.ports}} ports: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.livenessProbe }} + - containerPort: 80 + - containerPort: 443 livenessProbe: - {{- toYaml . | nindent 12}} - {{- end }} - {{- with .Values.readinessProbe }} + httpGet: + path: /_status?timeout=20 + port: 80 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: - {{- toYaml . | nindent 12}} - {{- end }} + initialDelaySeconds: 30 + httpGet: + path: /_status?timeout=2 + port: 80 # command: ["/bin/bash" ] # args: # - "-c" @@ -197,7 +203,7 @@ spec: # valueFrom: # secretKeyRef: # name: indexd-service-creds - # key: gdcapi + # key: sheepdog # optional: false - name: GEN3_UWSGI_TIMEOUT value: "600" @@ -228,38 +234,6 @@ spec: # value: /etc/ssl/certs/ca-certificates.crt - name: GEN3_DEBUG value: "True" - {{- with .Values.ddTraceEnabled }} - - name: DD_TRACE_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddEnv }} - - name: DD_ENV - value: {{ . }} - {{- end }} - {{- with .Values.ddService }} - - name: DD_SERVICE - value: {{ . }} - {{- end }} - {{- with .Values.ddVersion }} - - name: DD_VERSION - value: {{ . }} - {{- end }} - {{- with .Values.ddLogsInjection }} - - name: DD_LOGS_INJECTION - value: {{ . }} - {{- end }} - {{- with .Values.ddProfilingEnabled }} - - name: DD_PROFILING_ENABLED - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceSampleRate }} - - name: DD_TRACE_SAMPLE_RATE - value: {{ . }} - {{- end }} - {{- with .Values.ddTraceAgentHostname }} - - name: DD_TRACE_AGENT_HOSTNAME - value: {{ . }} - {{- end }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/sheepdog/templates/pdb.yaml b/helm/sheepdog/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/sheepdog/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/sheepdog/templates/tests/test-connection.yaml b/helm/sheepdog/templates/tests/test-connection.yaml index e508fe6e..dc94a171 100644 --- a/helm/sheepdog/templates/tests/test-connection.yaml +++ b/helm/sheepdog/templates/tests/test-connection.yaml @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "sheepdog.fullname" . }}:{{ .Values.service.port }}'] + args: ['sheepdog-service:{{ .Values.service.port }}/_status?timeout=2'] restartPolicy: Never diff --git a/helm/sheepdog/values.yaml b/helm/sheepdog/values.yaml index c219c8f4..941c33c6 100644 --- a/helm/sheepdog/values.yaml +++ b/helm/sheepdog/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,6 +52,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -73,125 +73,177 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false + +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + # -- (bool) Option to persist the dbs data. + enabled: false # Deployment releaseLabel: production +# -- (map) Annotations to add to the pod podAnnotations: {"gen3.io/network-ingress": "sheepdog"} +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (int) Number of old revisions to retain revisionHistoryLimit: 2 +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 +# -- (bool) Whether Datadog is enabled. dataDog: enabled: false env: dev +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - sheepdog + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" +# -- (bool) Automount the default service account token automountServiceAccountToken: false - -# sheepdog transactions take forever - try to let the complete before termination +# -- (int) sheepdog transactions take forever - try to let the complete before termination terminationGracePeriodSeconds: 50 +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/sheepdog + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "helm-test" # Environment Variables +# -- (string) URL of the data dictionary. dictionaryUrl: https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json +# -- (string) URL for the indexd service indexdUrl: http://indexd-service +# -- (string) URL for the fence service fenceUrl: http://fence-service -arboristUrl: http://arborist-service.default.svc.cluster.local +# -- (string) URL for the arborist service +arboristUrl: http://arborist-service authNamespace: default -# Placeholders for datadog -ddTraceEnabled: -ddEnv: -ddService: -ddVersion: -ddLogsInjection: -ddProfilingEnabled: -ddTraceSampleRate: -ddTraceAgentHostname: - - -livenessProbe: - httpGet: - path: /_status?timeout=20 - port: 80 - initialDelaySeconds: 30 - periodSeconds: 60 - timeoutSeconds: 30 - -readinessProbe: - initialDelaySeconds: 30 - httpGet: - path: /_status?timeout=2 - port: 80 - -ports: -- containerPort: 80 -- containerPort: 443 - +# -- (list) Volumes to mount to the container. volumeMounts: - name: "config-volume" readOnly: true mountPath: "/var/www/sheepdog/wsgi.py" subPath: "wsgi.py" +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.3 + # -- (string) The amount of memory requested memory: 12Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 512Mi # Service and Pod +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 # Secrets +# -- (map) Values for sheepdog secret. secrets: + # -- (map) Values for sheepdog's access to the fence database. fence: + # -- (string) Host for fence's db. host: postgres-postgresql.postgres.svc.cluster.local + # -- (string) User for fence's db. user: postgres + # -- (string) Password to fence's db. password: postgres + # -- (string) Database name for fence's db. database: fence + # -- (map) Values for sheepdog's database. sheepdog: + # -- (string) Host for sheepdog's db. host: postgres-postgresql.postgres.svc.cluster.local + # -- (string) Password to sheepdog's db. password: postgres + # -- (string) User for sheepdog's db. user: postgres + # -- (string) Database name for sheepdog's db. database: sheepdog gdcapi: + # -- (string) GDCAPI token. secretKey: + # -- (map) Values for sheepdog's access to indexd database. indexd: + # -- (string) Password to indexd's db. password: postgres + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Core-Service" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/elasticsearch/.helmignore b/helm/sower/.helmignore similarity index 100% rename from helm/elasticsearch/.helmignore rename to helm/sower/.helmignore diff --git a/helm/sower/Chart.yaml b/helm/sower/Chart.yaml new file mode 100644 index 00000000..95b847ab --- /dev/null +++ b/helm/sower/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +name: sower +description: A Helm chart for gen3 sower + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.7 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/sower/README.md b/helm/sower/README.md new file mode 100644 index 00000000..8ccb3ee7 --- /dev/null +++ b/helm/sower/README.md @@ -0,0 +1,155 @@ +# sower + +![Version: 0.1.7](https://img.shields.io/badge/Version-0.1.7-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) + +A Helm chart for gen3 sower + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sower"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sower"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["sower"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["sower"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["sower"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| automountServiceAccountToken | bool | `true` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| awsRegion | string | `"us-east-1"` | AWS region to be used. | +| awsStsRegionalEndpoints | string | `"regional"` | AWS STS to issue temporary credentials to users and roles that make an AWS STS request. Values regional or global. | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"false"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| gen3Namespace | string | `"default"` | Namespace to deploy the job. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | +| global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | +| global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | +| global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | +| global.aws.enabled | bool | `false` | Set to true if deploying to AWS. Controls ingress annotations. | +| global.ddEnabled | bool | `false` | Whether Datadog is enabled. | +| global.dev | bool | `true` | Whether the deployment is for development purposes. | +| global.dictionaryUrl | string | `"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json"` | URL of the data dictionary. | +| global.dispatcherJobNum | int | `10` | Number of dispatcher jobs. | +| global.environment | string | `"default"` | Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. | +| global.hostname | string | `"localhost"` | Hostname for the deployment. | +| global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | +| global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.portalApp | string | `"gitops"` | Portal application name. | +| global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | +| global.postgres.dbCreate | bool | `true` | Whether the database should be created. | +| global.postgres.master | map | `{"host":null,"password":null,"port":"5432","username":"postgres"}` | Master credentials to postgres. This is going to be the default postgres server being used for each service, unless each service specifies their own postgres | +| global.postgres.master.host | string | `nil` | hostname of postgres server | +| global.postgres.master.password | string | `nil` | password for superuser in postgres. This is used to create or restore databases | +| global.postgres.master.port | string | `"5432"` | Port for Postgres. | +| global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | +| global.publicDataSets | bool | `true` | Whether public datasets are enabled. | +| global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/sower","tag":""}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/sower"` | Docker repository. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"Core-Service"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podSecurityContext | map | `{"fsGroup":1000,"runAsUser":1000}` | Security context to apply to the pod | +| podSecurityContext.fsGroup | int | `1000` | Group that Kubernetes will change the permissions of all files in volumes to when volumes are mounted by a pod. | +| podSecurityContext.runAsUser | int | `1000` | User that all the processes will run under in the container. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"memory":"400Mi"},"requests":{"cpu":"100m","memory":"20Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"memory":"400Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.memory | string | `"400Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":"100m","memory":"20Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `"100m"` | The amount of CPU requested | +| resources.requests.memory | string | `"20Mi"` | The amount of memory requested | +| securityContext | map | `{}` | Security context for the containers in the pod | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":"sower-service-account"}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `"sower-service-account"` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| sowerConfig[0].action | string | `"export"` | | +| sowerConfig[0].container.cpu-limit | string | `"1"` | | +| sowerConfig[0].container.env[0].name | string | `"DICTIONARY_URL"` | | +| sowerConfig[0].container.env[0].valueFrom.configMapKeyRef.key | string | `"dictionary_url"` | | +| sowerConfig[0].container.env[0].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | +| sowerConfig[0].container.env[1].name | string | `"GEN3_HOSTNAME"` | | +| sowerConfig[0].container.env[1].valueFrom.configMapKeyRef.key | string | `"hostname"` | | +| sowerConfig[0].container.env[1].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | +| sowerConfig[0].container.env[2].name | string | `"ROOT_NODE"` | | +| sowerConfig[0].container.env[2].value | string | `"subject"` | | +| sowerConfig[0].container.image | string | `"quay.io/cdis/pelican-export:master"` | | +| sowerConfig[0].container.memory-limit | string | `"12Gi"` | | +| sowerConfig[0].container.name | string | `"job-task"` | | +| sowerConfig[0].container.pull_policy | string | `"Always"` | | +| sowerConfig[0].container.volumeMounts[0].mountPath | string | `"/pelican-creds.json"` | | +| sowerConfig[0].container.volumeMounts[0].name | string | `"pelican-creds-volume"` | | +| sowerConfig[0].container.volumeMounts[0].readOnly | bool | `true` | | +| sowerConfig[0].container.volumeMounts[0].subPath | string | `"config.json"` | | +| sowerConfig[0].container.volumeMounts[1].mountPath | string | `"/peregrine-creds.json"` | | +| sowerConfig[0].container.volumeMounts[1].name | string | `"peregrine-creds-volume"` | | +| sowerConfig[0].container.volumeMounts[1].readOnly | bool | `true` | | +| sowerConfig[0].container.volumeMounts[1].subPath | string | `"creds.json"` | | +| sowerConfig[0].name | string | `"pelican-export"` | | +| sowerConfig[0].restart_policy | string | `"Never"` | | +| sowerConfig[0].volumes[0].name | string | `"pelican-creds-volume"` | | +| sowerConfig[0].volumes[0].secret.secretName | string | `"pelicanservice-g3auto"` | | +| sowerConfig[0].volumes[1].name | string | `"peregrine-creds-volume"` | | +| sowerConfig[0].volumes[1].secret.secretName | string | `"peregrine-creds"` | | +| sowerConfig[1].action | string | `"export-files"` | | +| sowerConfig[1].container.cpu-limit | string | `"1"` | | +| sowerConfig[1].container.env[0].name | string | `"DICTIONARY_URL"` | | +| sowerConfig[1].container.env[0].valueFrom.configMapKeyRef.key | string | `"dictionary_url"` | | +| sowerConfig[1].container.env[0].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | +| sowerConfig[1].container.env[1].name | string | `"GEN3_HOSTNAME"` | | +| sowerConfig[1].container.env[1].valueFrom.configMapKeyRef.key | string | `"hostname"` | | +| sowerConfig[1].container.env[1].valueFrom.configMapKeyRef.name | string | `"manifest-global"` | | +| sowerConfig[1].container.env[2].name | string | `"ROOT_NODE"` | | +| sowerConfig[1].container.env[2].value | string | `"file"` | | +| sowerConfig[1].container.env[3].name | string | `"EXTRA_NODES"` | | +| sowerConfig[1].container.env[3].value | string | `""` | | +| sowerConfig[1].container.image | string | `"quay.io/cdis/pelican-export:master"` | | +| sowerConfig[1].container.memory-limit | string | `"12Gi"` | | +| sowerConfig[1].container.name | string | `"job-task"` | | +| sowerConfig[1].container.pull_policy | string | `"Always"` | | +| sowerConfig[1].container.volumeMounts[0].mountPath | string | `"/pelican-creds.json"` | | +| sowerConfig[1].container.volumeMounts[0].name | string | `"pelican-creds-volume"` | | +| sowerConfig[1].container.volumeMounts[0].readOnly | bool | `true` | | +| sowerConfig[1].container.volumeMounts[0].subPath | string | `"config.json"` | | +| sowerConfig[1].container.volumeMounts[1].mountPath | string | `"/peregrine-creds.json"` | | +| sowerConfig[1].container.volumeMounts[1].name | string | `"peregrine-creds-volume"` | | +| sowerConfig[1].container.volumeMounts[1].readOnly | bool | `true` | | +| sowerConfig[1].container.volumeMounts[1].subPath | string | `"creds.json"` | | +| sowerConfig[1].name | string | `"pelican-export-files"` | | +| sowerConfig[1].restart_policy | string | `"Never"` | | +| sowerConfig[1].volumes[0].name | string | `"pelican-creds-volume"` | | +| sowerConfig[1].volumes[0].secret.secretName | string | `"pelicanservice-g3auto"` | | +| sowerConfig[1].volumes[1].name | string | `"peregrine-creds-volume"` | | +| sowerConfig[1].volumes[1].secret.secretName | string | `"peregrine-creds"` | | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| tolerations | list | `[]` | Tolerations for the pods | +| volumeMounts | list | `[{"mountPath":"/sower_config.json","name":"sower-config","readOnly":true,"subPath":"sower_config.json"}]` | Volumes to mount to the container. | +| volumes | list | `[{"configMap":{"items":[{"key":"json","path":"sower_config.json"}],"name":"manifest-sower"},"name":"sower-config"}]` | Volumes to attach to the container. | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/sower/templates/NOTES.txt b/helm/sower/templates/NOTES.txt new file mode 100644 index 00000000..c1e7e1ae --- /dev/null +++ b/helm/sower/templates/NOTES.txt @@ -0,0 +1 @@ +{{ .Chart.Name }} has been deployed successfully. diff --git a/helm/elasticsearch/templates/_helpers.tpl b/helm/sower/templates/_helpers.tpl similarity index 63% rename from helm/elasticsearch/templates/_helpers.tpl rename to helm/sower/templates/_helpers.tpl index 4e828574..e9a7c298 100644 --- a/helm/elasticsearch/templates/_helpers.tpl +++ b/helm/sower/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "elasticsearch.name" -}} +{{- define "sower.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "elasticsearch.fullname" -}} +{{- define "sower.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,36 +26,42 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "elasticsearch.chart" -}} +{{- define "sower.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "elasticsearch.labels" -}} -helm.sh/chart: {{ include "elasticsearch.chart" . }} -{{ include "elasticsearch.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- define "sower.labels" -}} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} -{{- define "elasticsearch.selectorLabels" -}} -app.kubernetes.io/name: {{ include "elasticsearch.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- define "sower.selectorLabels" -}} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* Create the name of the service account to use */}} -{{- define "elasticsearch.serviceAccountName" -}} +{{- define "sower.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "elasticsearch.fullname" .) .Values.serviceAccount.name }} +{{- default (include "sower.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/helm/sower/templates/deployment.yaml b/helm/sower/templates/deployment.yaml new file mode 100644 index 00000000..879a74a0 --- /dev/null +++ b/helm/sower/templates/deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sower + annotations: + gen3.io/network-ingress: "pidgin" + labels: + {{- include "sower.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "sower.selectorLabels" . | nindent 8 }} + revisionHistoryLimit: 2 + strategy: + {{- toYaml .Values.strategy | nindent 8 }} + template: + metadata: + labels: + {{- include "sower.selectorLabels" . | nindent 8 }} + public: "yes" + netnolimit: "yes" + spec: + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "sower.serviceAccountName" . }} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + containers: + - name: sower + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + optional: true + - name: GEN3_HOSTNAME + value: {{ .Values.global.hostname }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + livenessProbe: + httpGet: + path: /_status + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 30 + readinessProbe: + httpGet: + path: /_status + port: 8000 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/elasticsearch/templates/hpa.yaml b/helm/sower/templates/hpa.yaml similarity index 83% rename from helm/elasticsearch/templates/hpa.yaml rename to helm/sower/templates/hpa.yaml index 88b71733..cf898b78 100644 --- a/helm/elasticsearch/templates/hpa.yaml +++ b/helm/sower/templates/hpa.yaml @@ -2,14 +2,14 @@ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "elasticsearch.fullname" . }} + name: {{ include "sower.fullname" . }} labels: - {{- include "elasticsearch.labels" . | nindent 4 }} + {{- include "sower.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "elasticsearch.fullname" . }} + name: {{ include "sower.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: diff --git a/helm/sower/templates/manifest-sower.yaml b/helm/sower/templates/manifest-sower.yaml new file mode 100644 index 00000000..8c70a330 --- /dev/null +++ b/helm/sower/templates/manifest-sower.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: manifest-sower +data: + json: |- + {{ .Values.sowerConfig | toJson | nindent 4 }} diff --git a/helm/sower/templates/pelican-creds.yaml b/helm/sower/templates/pelican-creds.yaml new file mode 100644 index 00000000..cc6f526c --- /dev/null +++ b/helm/sower/templates/pelican-creds.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: pelicanservice-g3auto +type: Opaque +{{- if .Values.global.aws.enabled }} +stringData: + config.json: |- +{ + "manifest_bucket_name": "{{ .Values.pelican.bucket }}", + "hostname": "{{ .Values.global.hostname }}", + "aws_access_key_id": "{{ .Values.global.aws.pelican_user.access_key }}", + "aws_secret_access_key": "{{ .Values.global.aws.pelican_user.access_secret }}" +} +{{- end }} diff --git a/helm/sower/templates/role-binding.yaml b/helm/sower/templates/role-binding.yaml new file mode 100644 index 00000000..94d7e189 --- /dev/null +++ b/helm/sower/templates/role-binding.yaml @@ -0,0 +1,12 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: sower-binding +subjects: +- kind: ServiceAccount + name: {{ include "sower.serviceAccountName" . }} + apiGroup: "" +roleRef: + kind: ClusterRole + name: admin + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/helm/elasticsearch/templates/service.yaml b/helm/sower/templates/service.yaml similarity index 50% rename from helm/elasticsearch/templates/service.yaml rename to helm/sower/templates/service.yaml index 794cb991..eb027642 100644 --- a/helm/elasticsearch/templates/service.yaml +++ b/helm/sower/templates/service.yaml @@ -1,14 +1,16 @@ apiVersion: v1 kind: Service metadata: - name: elasticsearch + name: sower-service labels: - {{- include "elasticsearch.labels" . | nindent 4 }} + {{- include "sower.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: 9200 + targetPort: http protocol: TCP + name: http selector: - {{- include "elasticsearch.selectorLabels" . | nindent 4 }} + {{- include "sower.selectorLabels" . | nindent 4 }} + \ No newline at end of file diff --git a/helm/sower/templates/serviceaccount.yaml b/helm/sower/templates/serviceaccount.yaml new file mode 100644 index 00000000..a3bedfee --- /dev/null +++ b/helm/sower/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "sower.serviceAccountName" . }} + labels: + {{- include "sower.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + {{- if .Values.global.aws.enabled }} + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.global.aws.account }}:role/{{ .Values.global.aws.sower_role }} + {{- end }} +{{- end }} diff --git a/helm/sower/templates/tests/test-connection.yaml b/helm/sower/templates/tests/test-connection.yaml new file mode 100644 index 00000000..6890bcd7 --- /dev/null +++ b/helm/sower/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "sower-test-connection" + labels: + {{- include "sower.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['sower-service:{{ .Values.service.port }}/_status'] + restartPolicy: Never diff --git a/helm/sower/values.yaml b/helm/sower/values.yaml new file mode 100644 index 00000000..bb327782 --- /dev/null +++ b/helm/sower/values.yaml @@ -0,0 +1,286 @@ +# Default values for sower. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- (map) Global configuration options. +global: + # -- (map) AWS configuration + aws: + # -- (bool) Set to true if deploying to AWS. Controls ingress annotations. + enabled: false + # -- (string) Credentials for AWS stuff. + awsAccessKeyId: + # -- (string) Credentials for AWS stuff. + awsSecretAccessKey: + # -- (bool) Whether the deployment is for development purposes. + dev: true + # -- (map) Postgres database configuration. + postgres: + # -- (bool) Whether the database should be created. + dbCreate: true + # -- (map) Master credentials to postgres. This is going to be the default postgres server being used for each service, unless each service specifies their own postgres + master: + # -- (string) hostname of postgres server + host: + # -- (string) username of superuser in postgres. This is used to create or restore databases + username: postgres + # -- (string) password for superuser in postgres. This is used to create or restore databases + password: + # -- (string) Port for Postgres. + port: "5432" + # -- (string) Environment name. This should be the same as vpcname if you're doing an AWS deployment. Currently this is being used to share ALB's if you have multiple namespaces. Might be used other places too. + environment: default + # -- (string) Hostname for the deployment. + hostname: localhost + # -- (string) ARN of the reverse proxy certificate. + revproxyArn: arn:aws:acm:us-east-1:123456:certificate + # -- (string) URL of the data dictionary. + dictionaryUrl: https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json + # -- (string) Portal application name. + portalApp: gitops + # -- (string) S3 bucket name for Kubernetes manifest files. + kubeBucket: kube-gen3 + # -- (string) S3 bucket name for log files. + logsBucket: logs-gen3 + # -- (bool) Whether public datasets are enabled. + publicDataSets: true + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` + tierAccessLevel: libre + # -- (bool) Whether network policies are enabled. + netPolicy: true + # -- (int) Number of dispatcher jobs. + dispatcherJobNum: 10 + # -- (bool) Whether Datadog is enabled. + ddEnabled: false + +# -- (int) Number of replicas for the deployment. +replicaCount: 1 + +# -- (map) Docker image information. +image: + # -- (string) Docker repository. + repository: quay.io/cdis/sower + # -- (string) Docker pull policy. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "" + +# -- (list) Docker image pull secrets. +imagePullSecrets: [] + +# -- (string) Override the name of the chart. +nameOverride: "" + +# -- (string) Override the full name of the deployment. +fullnameOverride: "" + +# -- (map) Security context for the containers in the pod +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# -- (map) Kubernetes service information. +service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". + type: ClusterIP + # -- (int) The port number that the service exposes. + port: 80 + +# -- (map) Configuration for autoscaling the number of replicas +autoscaling: + # -- (bool) Whether autoscaling is enabled + enabled: false + # -- (int) The minimum number of replicas to scale down to + minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to + maxReplicas: 100 + # -- (int) Target CPU utilization percentage + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# -- (map) Node Selector for the pods +nodeSelector: {} + +# -- (list) Tolerations for the pods +tolerations: [] + +# -- (map) Security context to apply to the pod +podSecurityContext: + # -- (int) User that all the processes will run under in the container. + runAsUser: 1000 + # -- (int) Group that Kubernetes will change the permissions of all files in volumes to when volumes are mounted by a pod. + fsGroup: 1000 + +# -- (map) Affinity to use for the deployment. +affinity: + podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. + preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + # -- (list) Label key for match expression. + - key: app + # -- (string) Operation type for the match expression. + operator: In + # -- (list) Value for the match expression key. + values: + - sower + # -- (string) Value for topology key label. + topologyKey: "kubernetes.io/hostname" + +# -- (list) Volumes to attach to the container. +volumes: + - name: sower-config + configMap: + name: manifest-sower + items: + - key: json + path: sower_config.json +# -- (list) Volumes to mount to the container. +volumeMounts: + - name: sower-config + readOnly: true + mountPath: /sower_config.json + subPath: sower_config.json + +# -- (string) AWS region to be used. +awsRegion: us-east-1 +# -- (string) AWS STS to issue temporary credentials to users and roles that make an AWS STS request. Values regional or global. +awsStsRegionalEndpoints: regional +# -- (string) Namespace to deploy the job. +gen3Namespace: default + +# -- (map) Resource requests and limits for the containers in the pod +resources: + # -- (map) The amount of resources that the container requests + requests: + # -- (string) The amount of CPU requested + cpu: 100m + # -- (string) The amount of memory requested + memory: 20Mi + # -- (map) The maximum amount of resources that the container is allowed to use + limits: + # -- (string) The maximum amount of memory the container can use + memory: 400Mi + +# -- (map) Rolling update deployment strategy +strategy: + type: RollingUpdate + rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. + maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. + maxUnavailable: 0 + +# -- (bool) Automount the default service account token +automountServiceAccountToken: true + +sowerConfig: + - name: pelican-export + action: export + container: + name: job-task + image: quay.io/cdis/pelican-export:master + pull_policy: Always + env: + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: GEN3_HOSTNAME + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: ROOT_NODE + value: subject + volumeMounts: + - name: pelican-creds-volume + readOnly: true + mountPath: "/pelican-creds.json" + subPath: config.json + - name: peregrine-creds-volume + readOnly: true + mountPath: "/peregrine-creds.json" + subPath: creds.json + cpu-limit: '1' + memory-limit: 12Gi + volumes: + - name: pelican-creds-volume + secret: + secretName: pelicanservice-g3auto + - name: peregrine-creds-volume + secret: + secretName: peregrine-creds + restart_policy: Never + - name: pelican-export-files + action: export-files + container: + name: job-task + image: quay.io/cdis/pelican-export:master + pull_policy: Always + env: + - name: DICTIONARY_URL + valueFrom: + configMapKeyRef: + name: manifest-global + key: dictionary_url + - name: GEN3_HOSTNAME + valueFrom: + configMapKeyRef: + name: manifest-global + key: hostname + - name: ROOT_NODE + value: file + - name: EXTRA_NODES + value: '' + volumeMounts: + - name: pelican-creds-volume + readOnly: true + mountPath: "/pelican-creds.json" + subPath: config.json + - name: peregrine-creds-volume + readOnly: true + mountPath: "/peregrine-creds.json" + subPath: creds.json + cpu-limit: '1' + memory-limit: 12Gi + volumes: + - name: pelican-creds-volume + secret: + secretName: pelicanservice-g3auto + - name: peregrine-creds-volume + secret: + secretName: peregrine-creds + restart_policy: Never + + +# -- (map) Service account to use or create. +serviceAccount: + # -- (bool) Specifies whether a service account should be created. + create: true + # -- (map) Annotations to add to the service account. + annotations: {} + # -- (string) The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "sower-service-account" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "false" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Core-Service" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: diff --git a/helm/ssjdispatcher/Chart.yaml b/helm/ssjdispatcher/Chart.yaml index 7f034766..4f97d26b 100644 --- a/helm/ssjdispatcher/Chart.yaml +++ b/helm/ssjdispatcher/Chart.yaml @@ -15,10 +15,15 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.7 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "master" + +dependencies: +- name: common + version: 0.1.8 + repository: file://../common diff --git a/helm/ssjdispatcher/README.md b/helm/ssjdispatcher/README.md index 1c5c06ef..e09ea9f2 100644 --- a/helm/ssjdispatcher/README.md +++ b/helm/ssjdispatcher/README.md @@ -1,29 +1,43 @@ # ssjdispatcher -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.7](https://img.shields.io/badge/Version-0.1.7-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 ssjdispatcher +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../common | common | 0.1.8 | + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].key | string | `"app"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values[0] | string | `"ssjdispatcher"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| automountServiceAccountToken | bool | `true` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| awsRegion | string | `"us-east-1"` | | -| awsStsRegionalEndpoints | string | `"regional"` | | -| dispatcherJobNum | string | `"10"` | | -| fullnameOverride | string | `""` | | -| gen3Namespace | string | `"default"` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["ssjdispatcher"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}}` | Affinity to use for the deployment. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution | map | `[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["ssjdispatcher"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]` | Option for scheduling to be required or preferred. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0] | int | `{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["ssjdispatcher"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}` | Weight value for preferred scheduling. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0] | list | `{"key":"app","operator":"In","values":["ssjdispatcher"]}` | Label key for match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].operator | string | `"In"` | Operation type for the match expression. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values | list | `["ssjdispatcher"]` | Value for the match expression key. | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | Value for topology key label. | +| automountServiceAccountToken | bool | `true` | Automount the default service account token | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| awsRegion | string | `"us-east-1"` | AWS region to be used. | +| awsStsRegionalEndpoints | string | `"regional"` | AWS STS to issue temporary credentials to users and roles that make an AWS STS request. Values regional or global. | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| dispatcherJobNum | string | `"10"` | Ssjdispater job number. | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| gen3Namespace | string | `"default"` | Namespace to deploy the job. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -36,7 +50,9 @@ A Helm chart for gen3 ssjdispatcher | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -47,54 +63,55 @@ A Helm chart for gen3 ssjdispatcher | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"nginx"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| indexing | string | `"707767160287.dkr.ecr.us-east-1.amazonaws.com/gen3/indexs3client:2022.08"` | | -| labels.netnolimit | string | `"yes"` | | -| labels.public | string | `"yes"` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podSecurityContext.fsGroup | int | `1000` | | -| podSecurityContext.runAsUser | int | `1000` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `1` | | -| resources.limits.memory | string | `"2400Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"128Mi"` | | -| securityContext | object | `{}` | | -| selectorLabels.app | string | `"ssjdispatcher"` | | -| selectorLabels.release | string | `"production"` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `"ssjdispatcher-service-account"` | | -| ssjcreds.jobName | string | `"indexing"` | | -| ssjcreds.jobPassword | string | `"replace_with_password"` | | -| ssjcreds.jobPattern | string | `"s3://test-12345678901234-upload/*"` | | -| ssjcreds.jobRequestCpu | string | `"500m"` | | -| ssjcreds.jobRequestMem | string | `"0.5Gi"` | | -| ssjcreds.jobUrl | string | `"http://indexd-service/index"` | | -| ssjcreds.jobUser | string | `"ssj"` | | -| ssjcreds.metadataservicePassword | string | `"replace_with_password"` | | -| ssjcreds.metadataserviceUrl | string | `"http://revproxy-service/mds"` | | -| ssjcreds.metadataserviceUsername | string | `"gateway"` | | -| ssjcreds.sqsUrl | string | `"https://sqs.us-east-1.amazonaws.com/12345678901234/test-upload_data_upload"` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| tolerations | list | `[]` | | -| volumeMounts[0].mountPath | string | `"/credentials.json"` | | -| volumeMounts[0].name | string | `"ssjdispatcher-creds-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"credentials.json"` | | -| volumes[0].name | string | `"ssjdispatcher-creds-volume"` | | -| volumes[0].secret.secretName | string | `"ssjdispatcher-creds"` | | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/ssjdispatcher","tag":"2022.08"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/ssjdispatcher"` | Docker repository. | +| image.tag | string | `"2022.08"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| indexing | string | `"707767160287.dkr.ecr.us-east-1.amazonaws.com/gen3/indexs3client:2022.08"` | Image to use for the "indexing" job. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node Selector for the pods | +| partOf | string | `"Workspace-Tab"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podSecurityContext | map | `{"fsGroup":1000,"runAsUser":1000}` | Security context to apply to the pod | +| podSecurityContext.fsGroup | int | `1000` | Group that Kubernetes will change the permissions of all files in volumes to when volumes are mounted by a pod. | +| podSecurityContext.runAsUser | int | `1000` | User that all the processes will run under in the container. | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":1,"memory":"2400Mi"},"requests":{"cpu":0.1,"memory":"128Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":1,"memory":"2400Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `1` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"2400Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.1,"memory":"128Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.1` | The amount of CPU requested | +| resources.requests.memory | string | `"128Mi"` | The amount of memory requested | +| securityContext | map | `{}` | Security context for the containers in the pod | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"port":80,"type":"ClusterIP"}` | Kubernetes service information. | +| service.port | int | `80` | The port number that the service exposes. | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":"ssjdispatcher-service-account"}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `"ssjdispatcher-service-account"` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| ssjcreds | map | `{"jobName":"indexing","jobPassword":"replace_with_password","jobPattern":"s3://test-12345678901234-upload/*","jobRequestCpu":"500m","jobRequestMem":"0.5Gi","jobUrl":"http://indexd-service/index","jobUser":"ssj","metadataservicePassword":"replace_with_password","metadataserviceUrl":"http://revproxy-service/mds","metadataserviceUsername":"gateway","sqsUrl":"https://sqs.us-east-1.amazonaws.com/12345678901234/test-upload_data_upload"}` | Values for ssjdispatcher secret. | +| ssjcreds.jobName | string | `"indexing"` | Name of the ssj job. | +| ssjcreds.jobPassword | string | `"replace_with_password"` | Password for the job. | +| ssjcreds.jobPattern | string | `"s3://test-12345678901234-upload/*"` | URL upload pattern that will trigger an event in S3 to send a message to SQS. | +| ssjcreds.jobRequestCpu | string | `"500m"` | The amount of CPU the job requests. | +| ssjcreds.jobRequestMem | string | `"0.5Gi"` | The amount of memory the job requests. | +| ssjcreds.jobUrl | string | `"http://indexd-service/index"` | Indexd service URL. | +| ssjcreds.jobUser | string | `"ssj"` | Name of the user the job will run with. | +| ssjcreds.metadataservicePassword | string | `"replace_with_password"` | Password for the metadata service. | +| ssjcreds.metadataserviceUrl | string | `"http://revproxy-service/mds"` | URL to reach metadata service endpoint. | +| ssjcreds.metadataserviceUsername | string | `"gateway"` | Username for the metadata service. | +| ssjcreds.sqsUrl | string | `"https://sqs.us-east-1.amazonaws.com/12345678901234/test-upload_data_upload"` | Sqs queue to monitor. | +| strategy | map | `{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}` | Rolling update deployment strategy | +| strategy.rollingUpdate.maxSurge | int | `1` | Number of additional replicas to add during rollout. | +| strategy.rollingUpdate.maxUnavailable | int | `0` | Maximum amount of pods that can be unavailable during the update. | +| tolerations | list | `[]` | Tolerations for the pods | +| volumeMounts | list | `[{"mountPath":"/credentials.json","name":"ssjdispatcher-creds-volume","readOnly":true,"subPath":"credentials.json"}]` | Volumes to mount to the container. | +| volumes | list | `[{"name":"ssjdispatcher-creds-volume","secret":{"secretName":"ssjdispatcher-creds"}}]` | Volumes to attach to the container. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/ssjdispatcher/templates/_helpers.tpl b/helm/ssjdispatcher/templates/_helpers.tpl index 2fbe0deb..c7ffa9d8 100644 --- a/helm/ssjdispatcher/templates/_helpers.tpl +++ b/helm/ssjdispatcher/templates/_helpers.tpl @@ -34,20 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "ssjdispatcher.labels" -}} -helm.sh/chart: {{ include "ssjdispatcher.chart" . }} -{{ include "ssjdispatcher.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "ssjdispatcher.selectorLabels" -}} -app.kubernetes.io/name: {{ include "ssjdispatcher.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/ssjdispatcher/templates/deployment.yaml b/helm/ssjdispatcher/templates/deployment.yaml index f9c8baba..2f0f86b9 100644 --- a/helm/ssjdispatcher/templates/deployment.yaml +++ b/helm/ssjdispatcher/templates/deployment.yaml @@ -2,24 +2,27 @@ apiVersion: apps/v1 kind: Deployment metadata: name: ssjdispatcher + labels: + {{- include "ssjdispatcher.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} spec: selector: - {{- with .Values.selectorLabels }} matchLabels: - {{- toYaml . | nindent 8 }} - {{- end }} + {{- include "ssjdispatcher.selectorLabels" . | nindent 8 }} revisionHistoryLimit: 2 strategy: {{- toYaml .Values.strategy | nindent 8 }} template: metadata: - {{- with .Values.labels }} labels: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.selectorLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} + {{- include "ssjdispatcher.selectorLabels" . | nindent 8 }} + netnolimit: "yes" + public: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} @@ -33,11 +36,14 @@ spec: {{- toYaml .Values.volumes | nindent 8 }} containers: - name: ssjdispatcher - image: "quay.io/cdis/ssjdispatcher:2022.08" - imagePullPolicy: Always + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: {{- toYaml .Values.volumeMounts | nindent 12 }} env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 12 }} + {{- end }} {{- with .Values.awsRegion }} - name: AWS_REGION value: {{ . }} diff --git a/helm/ssjdispatcher/templates/pdb.yaml b/helm/ssjdispatcher/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/ssjdispatcher/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/ssjdispatcher/values.yaml b/helm/ssjdispatcher/values.yaml index 716a8f0d..d1b028d7 100644 --- a/helm/ssjdispatcher/values.yaml +++ b/helm/ssjdispatcher/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,19 +52,33 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: - repository: nginx - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - + # -- (string) Docker repository. + repository: quay.io/cdis/ssjdispatcher + # -- (string) Docker pull policy. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "2022.08" + +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (map) Security context for the containers in the pod securityContext: {} # capabilities: # drop: @@ -77,96 +87,159 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 +# -- (map) Kubernetes service information. service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) The port number that the service exposes. port: 80 +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) Target CPU utilization percentage targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node Selector for the pods nodeSelector: {} +# -- (list) Tolerations for the pods tolerations: [] -selectorLabels: - app: ssjdispatcher - release: production - -labels: - netnolimit: "yes" - public: "yes" - +# -- (map) Security context to apply to the pod podSecurityContext: + # -- (int) User that all the processes will run under in the container. runAsUser: 1000 + # -- (int) Group that Kubernetes will change the permissions of all files in volumes to when volumes are mounted by a pod. fsGroup: 1000 +# -- (map) Affinity to use for the deployment. affinity: podAntiAffinity: + # -- (map) Option for scheduling to be required or preferred. preferredDuringSchedulingIgnoredDuringExecution: + # -- (int) Weight value for preferred scheduling. - weight: 100 podAffinityTerm: labelSelector: matchExpressions: + # -- (list) Label key for match expression. - key: app + # -- (string) Operation type for the match expression. operator: In + # -- (list) Value for the match expression key. values: - ssjdispatcher + # -- (string) Value for topology key label. topologyKey: "kubernetes.io/hostname" + +# -- (list) Volumes to attach to the container. volumes: - name: ssjdispatcher-creds-volume secret: secretName: "ssjdispatcher-creds" +# -- (list) Volumes to mount to the container. volumeMounts: - name: "ssjdispatcher-creds-volume" readOnly: true mountPath: "/credentials.json" subPath: credentials.json +# -- (string) AWS region to be used. awsRegion: us-east-1 +# -- (string) AWS STS to issue temporary credentials to users and roles that make an AWS STS request. Values regional or global. awsStsRegionalEndpoints: regional +# -- (string) Namespace to deploy the job. gen3Namespace: default +# -- (string) Ssjdispater job number. dispatcherJobNum: "10" - +# -- (string) Image to use for the "indexing" job. indexing: 707767160287.dkr.ecr.us-east-1.amazonaws.com/gen3/indexs3client:2022.08 +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: + # -- (string) The amount of CPU requested cpu: 0.1 + # -- (string) The amount of memory requested memory: 128Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 1.0 + # -- (string) The maximum amount of memory the container can use memory: 2400Mi +# -- (map) Rolling update deployment strategy strategy: type: RollingUpdate rollingUpdate: + # -- (int) Number of additional replicas to add during rollout. maxSurge: 1 + # -- (int) Maximum amount of pods that can be unavailable during the update. maxUnavailable: 0 +# -- (bool) Automount the default service account token automountServiceAccountToken: true +# -- (map) Values for ssjdispatcher secret. ssjcreds: + # -- (string) Sqs queue to monitor. sqsUrl: "https://sqs.us-east-1.amazonaws.com/12345678901234/test-upload_data_upload" + # -- (string) Name of the ssj job. jobName: "indexing" + # -- (string) URL upload pattern that will trigger an event in S3 to send a message to SQS. jobPattern: "s3://test-12345678901234-upload/*" + # -- (string) Indexd service URL. jobUrl: "http://indexd-service/index" + # -- (string) Name of the user the job will run with. jobUser: "ssj" + # -- (string) Password for the job. jobPassword: "replace_with_password" + # -- (string) The amount of CPU the job requests. jobRequestCpu: "500m" + # -- (string) The amount of memory the job requests. jobRequestMem: "0.5Gi" + # -- (string) URL to reach metadata service endpoint. metadataserviceUrl: "http://revproxy-service/mds" + # -- (string) Username for the metadata service. metadataserviceUsername: "gateway" + # -- (string) Password for the metadata service. metadataservicePassword: "replace_with_password" +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. + # -- (string) The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "ssjdispatcher-service-account" + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Workspace-Tab" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/helm/wts/Chart.yaml b/helm/wts/Chart.yaml index 959cdd61..c572f64a 100644 --- a/helm/wts/Chart.yaml +++ b/helm/wts/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.4 +version: 0.1.11 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,5 +25,9 @@ appVersion: "master" dependencies: - name: common - version: 0.1.3 + version: 0.1.8 repository: file://../common +- name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/wts/README.md b/helm/wts/README.md index ec862127..f320739e 100644 --- a/helm/wts/README.md +++ b/helm/wts/README.md @@ -1,6 +1,6 @@ # wts -![Version: 0.1.4](https://img.shields.io/badge/Version-0.1.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) +![Version: 0.1.11](https://img.shields.io/badge/Version-0.1.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: master](https://img.shields.io/badge/AppVersion-master-informational?style=flat-square) A Helm chart for gen3 workspace token service @@ -8,19 +8,26 @@ A Helm chart for gen3 workspace token service | Repository | Name | Version | |------------|------|---------| -| file://../common | common | 0.1.3 | +| file://../common | common | 0.1.8 | +| https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | | -| autoscaling.enabled | bool | `false` | | -| autoscaling.maxReplicas | int | `100` | | -| autoscaling.minReplicas | int | `1` | | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| fullnameOverride | string | `""` | | -| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","netPolicy":true,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","syncFromDbgap":false,"tierAccessLevel":"libre","userYamlS3Path":"s3://cdis-gen3-users/test/user.yaml"}` | Global configuration options. | +| affinity | map | `{}` | Affinity to use for the deployment. | +| autoscaling | map | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Configuration for autoscaling the number of replicas | +| autoscaling.enabled | bool | `false` | Whether autoscaling is enabled or not | +| autoscaling.maxReplicas | int | `100` | The maximum number of replicas to scale up to | +| autoscaling.minReplicas | int | `1` | The minimum number of replicas to scale down to | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | The target CPU utilization percentage for autoscaling | +| commonLabels | map | `nil` | Will completely override the commonLabels defined in the common chart's _label_setup.tpl | +| criticalService | string | `"true"` | Valid options are "true" or "false". If invalid option is set- the value will default to "false". | +| datadogLogsInjection | bool | `true` | If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. | +| datadogProfilingEnabled | bool | `true` | If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. | +| datadogTraceSampleRate | int | `1` | A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global | map | `{"aws":{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false},"ddEnabled":false,"dev":true,"dictionaryUrl":"https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json","dispatcherJobNum":10,"environment":"default","hostname":"localhost","kubeBucket":"kube-gen3","logsBucket":"logs-gen3","minAvialable":1,"netPolicy":true,"pdb":false,"portalApp":"gitops","postgres":{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}},"publicDataSets":true,"revproxyArn":"arn:aws:acm:us-east-1:123456:certificate","tierAccessLevel":"libre"}` | Global configuration options. | | global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false}` | AWS configuration | | global.aws.awsAccessKeyId | string | `nil` | Credentials for AWS stuff. | | global.aws.awsSecretAccessKey | string | `nil` | Credentials for AWS stuff. | @@ -33,7 +40,9 @@ A Helm chart for gen3 workspace token service | global.hostname | string | `"localhost"` | Hostname for the deployment. | | global.kubeBucket | string | `"kube-gen3"` | S3 bucket name for Kubernetes manifest files. | | global.logsBucket | string | `"logs-gen3"` | S3 bucket name for log files. | +| global.minAvialable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | | global.netPolicy | bool | `true` | Whether network policies are enabled. | +| global.pdb | bool | `false` | If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. | | global.portalApp | string | `"gitops"` | Portal application name. | | global.postgres | map | `{"dbCreate":true,"master":{"host":null,"password":null,"port":"5432","username":"postgres"}}` | Postgres database configuration. | | global.postgres.dbCreate | bool | `true` | Whether the database should be created. | @@ -44,48 +53,51 @@ A Helm chart for gen3 workspace token service | global.postgres.master.username | string | `"postgres"` | username of superuser in postgres. This is used to create or restore databases | | global.publicDataSets | bool | `true` | Whether public datasets are enabled. | | global.revproxyArn | string | `"arn:aws:acm:us-east-1:123456:certificate"` | ARN of the reverse proxy certificate. | -| global.syncFromDbgap | bool | `false` | Whether to sync data from dbGaP. | -| global.tierAccessLevel | string | `"libre"` | Access level for tiers. | -| global.userYamlS3Path | string | `"s3://cdis-gen3-users/test/user.yaml"` | Path to the user.yaml file in S3. | -| hostname | string | `nil` | | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/workspace-token-service"` | | -| image.tag | string | `"feat_wts_internalfence"` | | -| imagePullSecrets | list | `[]` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| oidc_client_id | string | `nil` | | -| oidc_client_secret | string | `nil` | | -| podAnnotations | object | `{}` | | -| podLabels."tags.datadoghq.com/service" | string | `"token-service"` | | -| podLabels.netnolimit | string | `"yes"` | | -| podLabels.public | string | `"yes"` | | -| podLabels.release | string | `"production"` | | -| podLabels.userhelper | string | `"yes"` | | -| podSecurityContext | object | `{}` | | -| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | +| global.tierAccessLevel | string | `"libre"` | Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` | +| hostname | string | `nil` | Hostname for the deployment. | +| image | map | `{"pullPolicy":"Always","repository":"quay.io/cdis/workspace-token-service","tag":"feat_wts_internalfence"}` | Docker image information. | +| image.pullPolicy | string | `"Always"` | Docker pull policy. | +| image.repository | string | `"quay.io/cdis/workspace-token-service"` | Docker repository. | +| image.tag | string | `"feat_wts_internalfence"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| nameOverride | string | `""` | Override the name of the chart. | +| nodeSelector | map | `{}` | Node Selector for the pods | +| oidc_client_id | string | `nil` | Id for the OIDC client. | +| oidc_client_secret | string | `nil` | Secret for the OIDC client. | +| partOf | string | `"Authentication"` | Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. | +| podAnnotations | map | `{}` | Annotations to add to the pod. | +| podSecurityContext | map | `{}` | Security context for the pod | +| postgres | map | `{"database":null,"dbCreate":null,"dbRestore":false,"host":null,"password":null,"port":"5432","separate":false,"username":null}` | Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you | | postgres.database | string | `nil` | Database name for postgres. This is a service override, defaults to - | | postgres.dbCreate | bool | `nil` | Whether the database should be created. Default to global.postgres.dbCreate | | postgres.host | string | `nil` | Hostname for postgres server. This is a service override, defaults to global.postgres.host | | postgres.password | string | `nil` | Password for Postgres. Will be autogenerated if left empty. | | postgres.port | string | `"5432"` | Port for Postgres. | +| postgres.separate | string | `false` | Will create a Database for the individual service to help with developing it. | | postgres.username | string | `nil` | Username for postgres. This is a service override, defaults to - | -| release | string | `"production"` | | -| replicaCount | int | `1` | | -| resources.limits.cpu | float | `0.5` | | -| resources.limits.memory | string | `"512Mi"` | | -| resources.requests.cpu | float | `0.1` | | -| resources.requests.memory | string | `"12Mi"` | | -| roleName | string | `"workspace-token-service"` | | -| secrets.external_oidc | string | `nil` | | -| securityContext | object | `{}` | | -| service.httpPort | int | `80` | | -| service.httpsPort | int | `443` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| tolerations | list | `[]` | | +| postgresql | map | `{"primary":{"persistence":{"enabled":false}}}` | Postgresql subchart settings if deployed separately option is set to "true". Disable persistence by default so we can spin up and down ephemeral environments | +| release | string | `"production"` | Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". | +| replicaCount | int | `1` | Number of replicas for the deployment. | +| resources | map | `{"limits":{"cpu":0.5,"memory":"512Mi"},"requests":{"cpu":0.2,"memory":"120Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"cpu":0.5,"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.cpu | string | `0.5` | The maximum amount of CPU the container can use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"cpu":0.2,"memory":"120Mi"}` | The amount of resources that the container requests | +| resources.requests.cpu | string | `0.2` | The amount of CPU requested | +| resources.requests.memory | string | `"120Mi"` | The amount of memory requested | +| roleName | string | `"workspace-token-service"` | Name of the role to be used for the role binding. | +| secrets | map | `{"external_oidc":null}` | Values for wts secret. | +| securityContext | map | `{}` | Security context for the containers in the pod | +| selectorLabels | map | `nil` | Will completely override the selectorLabels defined in the common chart's _label_setup.tpl | +| service | map | `{"httpPort":80,"httpsPort":443,"type":"ClusterIP"}` | Configuration for the service | +| service.httpPort | int | `80` | Port on which the service is exposed | +| service.httpsPort | int | `443` | Secure port on which the service is exposed | +| service.type | string | `"ClusterIP"` | Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". | +| serviceAccount | map | `{"annotations":{},"create":true,"name":""}` | Service account to use or create. | +| serviceAccount.annotations | map | `{}` | Annotations to add to the service account. | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| tolerations | list | `[]` | Tolerations for the pods | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/helm/wts/templates/_helpers.tpl b/helm/wts/templates/_helpers.tpl index d13a9fca..f8cebb83 100644 --- a/helm/wts/templates/_helpers.tpl +++ b/helm/wts/templates/_helpers.tpl @@ -34,19 +34,26 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "wts.labels" -}} -helm.sh/chart: {{ include "wts.chart" . }} -{{ include "wts.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "wts.selectorLabels" -}} -app: wts +{{- if .Values.selectorLabels }} + {{- with .Values.selectorLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.selectorLabels" .)}} +{{- end }} {{- end }} {{/* diff --git a/helm/wts/templates/deployment.yaml b/helm/wts/templates/deployment.yaml index 8d76f320..e3f20dec 100644 --- a/helm/wts/templates/deployment.yaml +++ b/helm/wts/templates/deployment.yaml @@ -4,6 +4,9 @@ metadata: name: wts-deployment labels: {{- include "wts.labels" . | nindent 4 }} + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 4 }} + {{- end }} annotations: gen3.io/network-ingress: "mariner" spec: @@ -26,11 +29,14 @@ spec: annotations: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.podLabels }} labels: {{- include "wts.selectorLabels" . | nindent 8 }} - {{- toYaml . | nindent 8 }} - {{- end }} + public: "yes" + netnolimit: "yes" + userhelper: "yes" + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogLabels" . | nindent 8 }} + {{- end }} spec: affinity: podAntiAffinity: @@ -78,6 +84,9 @@ spec: path: /_status port: 80 env: + {{- if .Values.global.ddEnabled }} + {{- include "common.datadogEnvVar" . | nindent 11 }} + {{- end }} - name: OIDC_CLIENT_ID valueFrom: secretKeyRef: @@ -173,9 +182,7 @@ spec: - name: SECRET_CONFIG value: "/var/www/wts/appcreds.json" resources: - limits: - cpu: 0.8 - memory: 512Mi + {{- toYaml .Values.resources | nindent 12 }} command: ["/bin/sh"] args: - "-c" diff --git a/helm/wts/templates/pdb.yaml b/helm/wts/templates/pdb.yaml new file mode 100644 index 00000000..2ef2de13 --- /dev/null +++ b/helm/wts/templates/pdb.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.global.pdb (gt (int .Values.replicaCount) 1) }} +{{ include "common.pod_disruption_budget" . }} +{{- end }} \ No newline at end of file diff --git a/helm/wts/templates/tests/test-connection.yaml b/helm/wts/templates/tests/test-connection.yaml index 7f3f42c3..250d8c47 100644 --- a/helm/wts/templates/tests/test-connection.yaml +++ b/helm/wts/templates/tests/test-connection.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "wts.fullname" . }}-test-connection" + name: "wts-test-connection" labels: {{- include "wts.labels" . | nindent 4 }} annotations: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "wts.fullname" . }}:{{ .Values.service.port }}'] + args: ['workspace-token-service:80/_status'] restartPolicy: Never diff --git a/helm/wts/templates/wts-oidc.yaml b/helm/wts/templates/wts-oidc.yaml index 082bd7bf..769a3c47 100644 --- a/helm/wts/templates/wts-oidc.yaml +++ b/helm/wts/templates/wts-oidc.yaml @@ -24,7 +24,7 @@ spec: containers: - name: fence-client # TODO: Make this configurable - image: "quay.io/cdis/fence:feat_dbenvvar" + image: "quay.io/cdis/fence:master" imagePullPolicy: {{ .Values.image.pullPolicy }} # TODO: ADD RESOURCES # resources: @@ -100,10 +100,11 @@ spec: args: - "-c" - | + echo "waiting for /shared/client_id" while [ ! -e /shared/client_id ] do - echo "waiting for /shared/client_id" - sleep 30 + echo "..." + sleep 5 done echo "Updating k8s secret wts-oidc-client" CLIENT_ID=$(cat /shared/client_id | base64) diff --git a/helm/wts/values.yaml b/helm/wts/values.yaml index 4ef4b4bd..2eb8c2d1 100644 --- a/helm/wts/values.yaml +++ b/helm/wts/values.yaml @@ -42,13 +42,9 @@ global: kubeBucket: kube-gen3 # -- (string) S3 bucket name for log files. logsBucket: logs-gen3 - # -- (bool) Whether to sync data from dbGaP. - syncFromDbgap: false - # -- (string) Path to the user.yaml file in S3. - userYamlS3Path: s3://cdis-gen3-users/test/user.yaml # -- (bool) Whether public datasets are enabled. publicDataSets: true - # -- (string) Access level for tiers. + # -- (string) Access level for tiers. acceptable values for `tier_access_level` are: `libre`, `regular` and `private`. If omitted, by default common will be treated as `private` tierAccessLevel: libre # -- (bool) Whether network policies are enabled. netPolicy: true @@ -56,6 +52,10 @@ global: dispatcherJobNum: 10 # -- (bool) Whether Datadog is enabled. ddEnabled: false + # -- (bool) If the service will be deployed with a Pod Disruption Budget. Note- you need to have more than 2 replicas for the pdb to be deployed. + pdb: false + # -- (int) The minimum amount of pods that are available at all times if the PDB is deployed. + minAvialable: 1 # -- (map) Postgres database configuration. If db does not exist in postgres cluster and dbCreate is set ot true then these databases will be created for you postgres: @@ -73,45 +73,62 @@ postgres: port: "5432" # -- (string) Password for Postgres. Will be autogenerated if left empty. password: + # -- (string) Will create a Database for the individual service to help with developing it. + separate: false +# -- (map) Postgresql subchart settings if deployed separately option is set to "true". +# Disable persistence by default so we can spin up and down ephemeral environments +postgresql: + primary: + persistence: + enabled: false + +# -- (int) Number of replicas for the deployment. replicaCount: 1 +# -- (map) Docker image information. image: + # -- (string) Docker repository. repository: quay.io/cdis/workspace-token-service + # -- (string) Docker pull policy. pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. + # -- (string) Overrides the image tag whose default is the chart appVersion. tag: "feat_wts_internalfence" +# -- (list) Docker image pull secrets. imagePullSecrets: [] + +# -- (string) Override the name of the chart. nameOverride: "" + +# -- (string) Override the full name of the deployment. fullnameOverride: "" +# -- (string) Hostname for the deployment. hostname: +# -- (string) Id for the OIDC client. oidc_client_id: +# -- (string) Secret for the OIDC client. oidc_client_secret: +# -- (map) Service account to use or create. serviceAccount: - # Specifies whether a service account should be created + # -- (bool) Specifies whether a service account should be created. create: true - # Annotations to add to the service account + # -- (map) Annotations to add to the service account. annotations: {} - # The name of the service account to use. + # -- (string) The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" -podLabels: - release: production - public: "yes" - netnolimit: "yes" - userhelper: "yes" - tags.datadoghq.com/service: "token-service" - - +# -- (map) Annotations to add to the pod. podAnnotations: {} +# -- (map) Security context for the pod podSecurityContext: {} # fsGroup: 2000 +# -- (map) Security context for the containers in the pod securityContext: {} # capabilities: # drop: @@ -120,37 +137,55 @@ securityContext: {} # runAsNonRoot: true # runAsUser: 1000 - +# -- (map) Configuration for the service service: + # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + # -- (int) Port on which the service is exposed httpPort: 80 + # -- (int) Secure port on which the service is exposed httpsPort: 443 +# -- (map) Resource requests and limits for the containers in the pod resources: + # -- (map) The amount of resources that the container requests requests: - cpu: 0.1 - memory: 12Mi + # -- (string) The amount of CPU requested + cpu: 0.2 + # -- (string) The amount of memory requested + memory: 120Mi + # -- (map) The maximum amount of resources that the container is allowed to use limits: + # -- (string) The maximum amount of CPU the container can use cpu: 0.5 + # -- (string) The maximum amount of memory the container can use memory: 512Mi +# -- (map) Configuration for autoscaling the number of replicas autoscaling: + # -- (bool) Whether autoscaling is enabled or not enabled: false + # -- (int) The minimum number of replicas to scale down to minReplicas: 1 + # -- (int) The maximum number of replicas to scale up to maxReplicas: 100 + # -- (int) The target CPU utilization percentage for autoscaling targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 +# -- (map) Node Selector for the pods nodeSelector: {} +# -- (list) Tolerations for the pods tolerations: [] +# -- (map) Affinity to use for the deployment. affinity: {} +# -- (string) Name of the role to be used for the role binding. roleName: workspace-token-service -release: production - +# -- (map) Values for wts secret. secrets: external_oidc: # - base_url: @@ -180,3 +215,23 @@ secrets: # "db_passwurd": "WTS_DB_PWD.REPLACE", # "db_database": "wts_default" # } + +# Values to determine the labels that are used for the deployment, pod, etc. +# -- (string) Valid options are "production" or "dev". If invalid option is set- the value will default to "dev". +release: "production" +# -- (string) Valid options are "true" or "false". If invalid option is set- the value will default to "false". +criticalService: "true" +# -- (string) Label to help organize pods and their use. Any value is valid, but use "_" or "-" to divide words. +partOf: "Authentication" +# -- (map) Will completely override the selectorLabels defined in the common chart's _label_setup.tpl +selectorLabels: +# -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl +commonLabels: + +# Values to configure datadog if ddEnabled is set to "true". +# -- (bool) If enabled, the Datadog Agent will automatically inject Datadog-specific metadata into your application logs. +datadogLogsInjection: true +# -- (bool) If enabled, the Datadog Agent will collect profiling data for your application using the Continuous Profiler. This data can be used to identify performance bottlenecks and optimize your application. +datadogProfilingEnabled: true +# -- (int) A value between 0 and 1, that represents the percentage of requests that will be traced. For example, a value of 0.5 means that 50% of requests will be traced. +datadogTraceSampleRate: 1 diff --git a/sample-values/fence-config.yaml b/sample-values/fence-config.yaml deleted file mode 100644 index 11873115..00000000 --- a/sample-values/fence-config.yaml +++ /dev/null @@ -1,885 +0,0 @@ -#### FENCE CONFIG #### -fence: - FENCE_CONFIG: - APP_NAME: 'Gen3 Data Commons' - - # A URL-safe base64-encoded 32-byte key for encrypting keys in db - # in python you can use the following script to generate one: - # import base64 - # import os - # key = base64.urlsafe_b64encode(os.urandom(32)) - # print(key) - ENCRYPTION_KEY: REPLACEME - - # ////////////////////////////////////////////////////////////////////////////////////// - # DEBUG & SECURITY SETTINGS - # - Modify based on whether you're in a dev environment or in production - # ////////////////////////////////////////////////////////////////////////////////////// - # flask's debug setting - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - DEBUG: false - # if true, will automatically login a user with username "test" - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - MOCK_AUTH: false - # if true, will fake a successful login response from Google in /login/google - # NOTE: this will also modify the behavior of /link/google endpoints - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - # will login as the username set in cookie DEV_LOGIN_COOKIE_NAME - MOCK_GOOGLE_AUTH: false - DEV_LOGIN_COOKIE_NAME: "dev_login" - # if true, will ignore anything configured in STORAGE_CREDENTIALS - MOCK_STORAGE: false - # allow OIDC traffic on http for development. By default it requires https. - # - # WARNING: ONLY set to true when fence will be deployed in such a way that it will - # ONLY receive traffic from internal clients and can safely use HTTP. - AUTHLIB_INSECURE_TRANSPORT: true - # enable Prometheus Metrics for observability purposes - # - # WARNING: Any counters, gauges, histograms, etc. should be carefully - # reviewed to make sure its labels do not contain any PII / PHI - ENABLE_PROMETHEUS_METRICS: false - - # set if you want browsers to only send cookies with requests over HTTPS - SESSION_COOKIE_SECURE: true - - ENABLE_CSRF_PROTECTION: true - - # Signing key for WTForms to sign CSRF tokens with - WTF_CSRF_SECRET_KEY: '{{ENCRYPTION_KEY}}' - - # fence (at the moment) attempts a migration on startup. setting this to false will disable that - # WARNING: ONLY set to false if you do NOT want to automatically migrate your database. - # You should be careful about incompatible versions of your db schema with what - # fence expects. In other words, things could be broken if you update to a later - # fence that expects a schema your database isn't migrated to. - # NOTE: We are working to improve the migration process in the near future - ENABLE_DB_MIGRATION: true - - # ////////////////////////////////////////////////////////////////////////////////////// - # OPEN ID CONNECT (OIDC) - # - Fully configure at least one client so login works - # - WARNING: Be careful changing the *_ALLOWED_SCOPES as you can break basic - # and optional functionality - # ////////////////////////////////////////////////////////////////////////////////////// - OPENID_CONNECT: - # any OIDC IDP that does not differ from the generic implementation can be - # configured without code changes - generic_oidc_idp: # choose a unique ID and replace this key - name: 'some_idp' # optional; display name for this IDP - client_id: '' - client_secret: '' - redirect_url: '{{BASE_URL}}/login/some_idp/login' # replace IDP name - # use `discovery` to configure IDPs that do not expose a discovery - # endpoint. One of `discovery_url` or `discovery` should be configured - discovery_url: 'https://server.com/.well-known/openid-configuration' - discovery: - authorization_endpoint: '' - token_endpoint: '' - jwks_uri: '' - user_id_field: '' # optional (default "sub"); claims field to get the user_id from - email_field: '' # optional (default "email"); claims field to get the user email from - scope: '' # optional (default "openid") - # These Google values must be obtained from Google's Cloud Console - # Follow: https://developers.google.com/identity/protocols/OpenIDConnect - # - # You'll need to obtain a Client ID and Client Secret. Set the redirect URIs - # in Google to be '{{BASE_URL}}/login/google/login', but expand BASE_URL to - # whatever you set it to above. - google: - discovery_url: 'https://accounts.google.com/.well-known/openid-configuration' - client_id: '' - client_secret: '' - # this is be the allowed redirect back to fence, should not need to change - redirect_url: '{{BASE_URL}}/login/google/login/' - scope: 'openid email' - # if mock is true, will fake a successful login response from Google in /login/google - # NOTE: this will also modify the behavior of /link/google endpoints - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - # will login as the username set in cookie DEV_LOGIN_COOKIE_NAME or default provided - # here - mock: '{{MOCK_GOOGLE_AUTH}}' # for backwards compatibility with older cfg files - mock_default_user: 'test@example.com' - # Support for multi-tenant fence (another fence is this fence's IDP) - # If this fence instance is a client of another fence, fill this cfg out. - # REMOVE if not needed - fence: - # this api_base_url should be the root url for the OTHER fence - # something like: https://example.com - api_base_url: '' - # this client_id and client_secret should be obtained by registering THIS fence as - # a new client of the OTHER fence - client_id: '' - client_secret: '' - client_kwargs: - # openid is required to use OIDC flow - scope: 'openid' - # callback after logging in through the other fence - redirect_uri: '{{BASE_URL}}/login/fence/login' - # The next 3 should not need to be changed if the provider is following - # Oauth2 endpoint naming conventions - authorize_url: '{{api_base_url}}/oauth2/authorize' - access_token_url: '{{api_base_url}}/oauth2/token' - refresh_token_url: '{{api_base_url}}/oauth2/token' - # Custom name to display for consent screens. If not provided, will use `fence`. - # If the other fence is using NIH Login, you should make name: `NIH Login` - name: '' - # if mock is true, will fake a successful login response for login - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - mock: false - mock_default_user: 'test@example.com' - # this is needed to enable InCommon login, if some LOGIN_OPTIONS are configured with idp=fence and a list of shib_idps: - shibboleth_discovery_url: 'https://login.bionimbus.org/Shibboleth.sso/DiscoFeed' - # you can setup up an orcid client here: https://orcid.org/developer-tools - orcid: - discovery_url: 'https://orcid.org/.well-known/openid-configuration' - client_id: '' - client_secret: '' - # make sure you put the FULL url for this deployment in the allowed redirects in - # ORCID.org. DO NOT include {{BASE_URL}} at ORCID.org, you need to actually put the - # full url - redirect_url: '{{BASE_URL}}/login/orcid/login/' - scope: 'openid' - # if mock is true, will fake a successful login response for login - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - mock: false - mock_default_user: '0000-0002-2601-8132' - ras: - discovery_url: 'https://sts.nih.gov/.well-known/openid-configuration' - client_id: '' - client_secret: '' - redirect_url: '{{BASE_URL}}/login/ras/callback' - scope: 'openid email profile ga4gh_passport_v1' - # if mock is true, will fake a successful login response for login - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - mock: false - mock_default_user: 'test@example.com' - # Create a client in Azure here: - # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview - # Currently supports organizational account only, so when registering a new App in - # Azure, make sure to select the `Accounts in any organizational directory` for - # supported account types. - microsoft: - discovery_url: 'https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration' - # after registering a new appl, client_id can be found as - # "APPLICATION (CLIENT) ID" in Microsoft Azure - client_id: '' - # You have a generate a secret in Azure for this app, there should be a - # "Certificates & secrets" section where you can create a "New client secret" - client_secret: '' - # make sure you put the FULL url for this deployment in the allowed redirects in - # your app in Azure. DO NOT include {{BASE_URL}} in Azure, you need to actually put the - # full url - redirect_url: '{{BASE_URL}}/login/microsoft/login/' - scope: 'openid email' - # if mock is true, will fake a successful login response for login - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - mock: false - mock_default_user: 'test@example.com' - # For information on configuring an Okta tenant as an OIDC IdP refer to Okta documentation at: - # https://developer.okta.com/docs/reference/api/oidc/#2-okta-as-the-identity-platform-for-your-app-or-api - okta: - discovery_url: '' - client_id: '' - client_secret: '' - redirect_url: '{{BASE_URL}}/login/okta/login/' - scope: 'openid email' - cognito: - # You must create a user pool in order to have a discovery url - discovery_url: 'https://cognito-idp.{REGION}.amazonaws.com/{USER-POOL-ID}/.well-known/openid-configuration' - client_id: '' - client_secret: '' - redirect_url: '{{BASE_URL}}/login/cognito/login/' - scope: 'openid email' - # In the case where Cognito is being used solely as an intermediary to a single IdP, - # and that IdP is a SAML IdP with no 'email_verified' outgoing claim, but it is safe - # to assume all emails from this SAML IdP are in fact verified, we may set this to True - # assume_emails_verified: False - # CILogon subscribers can create and manage OIDC clients using COmanage Registry. - # Free tier users may request OIDC clients at https://cilogon.org/oauth2/register - cilogon: - discovery_url: 'https://cilogon.org/.well-known/openid-configuration' - client_id: '' - client_secret: '' - # When registering the Callback URLs for your CILogon OIDC client be - # sure to include the FULL url for this deployment, including the https:// scheme - # and server FQDN. - redirect_url: '{{BASE_URL}}/login/cilogon/login/' - scope: 'openid email profile' - # if mock is true, will fake a successful login response for login - # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) - mock: false - mock_default_user: 'http://cilogon.org/serverT/users/64703' - synapse: - discovery_url: '' - client_id: '' - client_secret: '' - redirect_url: '' - scope: 'openid' - shibboleth: - client_id: '' - client_secret: '' - redirect_url: '{{BASE_URL}}/login/shib/login' - - # these are the *possible* scopes a client can be given, NOT scopes that are - # given to all clients. You can be more restrictive during client creation - CLIENT_ALLOWED_SCOPES: - - "openid" - - "user" - - "data" - - "google_credentials" - - "google_service_account" - - "google_link" - - "ga4gh_passport_v1" - - # these are the scopes that CAN be included in a user's own access_token - USER_ALLOWED_SCOPES: - - "fence" - - "openid" - - "user" - - "data" - - "admin" - - "google_credentials" - - "google_service_account" - - "google_link" - - "ga4gh_passport_v1" - - # these are the scopes that a browser session can create for a user (very - # similar to USER_ALLOWED_SCOPES, as the session will actually create access_tokens - # for an actively logged in user) - SESSION_ALLOWED_SCOPES: - - "openid" - - "user" - - "credentials" - - "data" - - "admin" - - "google_credentials" - - "google_service_account" - - "google_link" - - "ga4gh_passport_v1" - - # ////////////////////////////////////////////////////////////////////////////////////// - # LOGIN - # - Modify based on which OIDC provider(s) you configured above - # - NOTE: You can have multiple IDPs for users to login with, but one has to be set - # as the default - # ////////////////////////////////////////////////////////////////////////////////////// - - # List of enabled login options (used by data-portal to display login buttons). - # Each option must be configured with a "name" and an "idp". - # - "idp" must be a configured provider in OPENID_CONNECT section. - # Multiple options can be configured with the same idp. - # - if provider_id is "fence", "fence_idp" can be any of the providers - # supported by the other Fence. If not specified, will default to NIH login. - # - if provider_id is "fence" and fence_idp is "shibboleth", a list of - # "shib_idps" can be configured for InCommon login. If not specified, will - # default to NIH login. - # - Optional parameters: "desc" (description) and "secondary" (boolean - can - # be used by the frontend to display secondary buttons differently). - LOGIN_OPTIONS: - - name: 'Login from Google' - desc: 'description' - idp: google - # secondary: True - # - name: 'ORCID Login' - # idp: orcid - # - name: 'Microsoft Login' - # idp: microsoft - # - name: 'Okta Login'/sqz-krfi-ynw - # idp: okta - # # Cognito login: You may want to edit the name to reflect Cognito's IdP, - # # especially if Cognito is only using one IdP - # - name: 'Login from Cognito' - # desc: 'Amazon Cognito login' - # idp: cognito - # - name: 'Login from RAS' - # idp: ras - # - name: 'NIH Login' - # idp: fence - # fence_idp: shibboleth - # - name: 'ORCID Login through other Fence' - # idp: fence - # fence_idp: orcid - # - name: 'CILogon Login' - # idp: cilogon - # - name: 'InCommon Login' - # idp: fence - # fence_idp: shibboleth - # # "shib_idps" can be '*' or a list of one or more entity IDs - # shib_idps: - # - urn:mace:incommon:nih.gov - # - urn:mace:incommon:uchicago.edu - # The following can be used for shibboleth login, simply uncomment. - # NOTE: Don't enable shibboleth if the deployment is not protected by - # shibboleth module, the shib module takes care of preventing header - # spoofing. - # - name: 'Shibboleth Login' - # idp: shibboleth - - # Default login provider: - # - must be configured in LOGIN_OPTIONS and OPENID_CONNECT - # - if several options in LOGIN_OPTIONS are defined for this IDP, will default - # to the first one. - DEFAULT_LOGIN_IDP: google - - # Default login URL: DEPRECATED and replaced by LOGIN_OPTIONS + DEFAULT_LOGIN_IDP configs - # - Google? Use: '{{BASE_URL}}/login/google' - # - Multi-tenant fence (e.g. another fence instance)? Use: '{{BASE_URL}}/login/fence' - # - Sibboleth? Use: '{{BASE_URL}}/login/shib' - DEFAULT_LOGIN_URL: '{{BASE_URL}}/login/google' - - # `LOGIN_REDIRECT_WHITELIST` is a list of extra whitelisted URLs which can be redirected - # to by the `/login/*` endpoints. Fence automatically populates this with the redirect - # URLs for any registered OAuth clients, and its own URL. When validating the redirects, - # fence chesk whether the domain for the redirect matches a domain in the whitelist (so - # only the domains for the additional desired redirects are necessary here). - LOGIN_REDIRECT_WHITELIST: [] - - ### DEPRECATED and replaced by OPENID_CONNECT + LOGIN_OPTIONS configs - ENABLED_IDENTITY_PROVIDERS: {} - - - # ////////////////////////////////////////////////////////////////////////////////////// - # LIBRARY CONFIGURATION (authlib & flask) - # - Already contains reasonable defaults - # ////////////////////////////////////////////////////////////////////////////////////// - # authlib-specific configs for OIDC flow and JWTs - # NOTE: the OAUTH2_JWT_KEY cfg gets set automatically by fence if keys are setup - # correctly - OAUTH2_JWT_ALG: 'RS256' - OAUTH2_JWT_ENABLED: true - OAUTH2_JWT_ISS: '{{BASE_URL}}' - OAUTH2_PROVIDER_ERROR_URI: '/api/oauth2/errors' - - # used for flask, "path mounted under by the application / web server" - # since we deploy as microservices, fence is typically under {{base}}/user - # this is also why our BASE_URL default ends in /user - APPLICATION_ROOT: '/user' - - - # ////////////////////////////////////////////////////////////////////////////////////// - # Tokens, Lifetimes, & Expirations - # - Already contains reasonable defaults - # ////////////////////////////////////////////////////////////////////////////////////// - # The name of the browser cookie in which the access token will be stored. - ACCESS_TOKEN_COOKIE_NAME: "access_token" - - # The name of the browser cookie in which the session token will be stored. - # Note that the session token also stores information for the - # ``flask.session`` in the ``context`` field of the token. - SESSION_COOKIE_NAME: "fence" - - # The domain of the browser cookie in which the session token will be stored. - # Leave unset (not empty string!) for normal single-site deployment. - SESSION_COOKIE_DOMAIN: - - OAUTH2_TOKEN_EXPIRES_IN: - "authorization_code": 1200 - "implicit": 1200 - - # The number of seconds after an access token is issued until it expires. - ACCESS_TOKEN_EXPIRES_IN: 1200 - - # The number of seconds after a refresh token is issued until it expires. - REFRESH_TOKEN_EXPIRES_IN: 2592000 - - # The number of seconds after which a browser session is considered stale. - SESSION_TIMEOUT: 1800 - - # The maximum session lifetime in seconds. - SESSION_LIFETIME: 28800 - - # The number of seconds the user's Google service account key used for - # url signing will last before being expired/rotated - # 30 days: 2592000 seconds - GOOGLE_SERVICE_ACCOUNT_KEY_FOR_URL_SIGNING_EXPIRES_IN: 2592000 - - # The number of seconds after a User's Google Service account is added to bucket - # access until it expires. - # 7 days: 604800 seconds - GOOGLE_USER_SERVICE_ACCOUNT_ACCESS_EXPIRES_IN: 604800 - - # The number of seconds after a User's Google account is added to bucket - # access until it expires. - GOOGLE_ACCOUNT_ACCESS_EXPIRES_IN: 86400 - - # The number of seconds after a pre-signed url is issued until it expires. - MAX_PRESIGNED_URL_TTL: 3600 - - # The number of seconds after an API KEY is issued until it expires. - MAX_API_KEY_TTL: 2592000 - - # The number of seconds after an access token is issued until it expires. - MAX_ACCESS_TOKEN_TTL: 3600 - - # TEMPORARY: The maximum number of projects allowed in token claims. - # This config var should be removed after sheepdog and peregrine support - # auth checks against Arborist, and no longer check the token. - TOKEN_PROJECTS_CUTOFF: 10 - - # If set to true, will generate an new access token each time when a browser session update happens - RENEW_ACCESS_TOKEN_BEFORE_EXPIRATION: false - - # The maximum lifetime of a Gen3 passport in seconds - GEN3_PASSPORT_EXPIRES_IN: 43200 - - ######################################################################################## - # OPTIONAL CONFIGURATIONS # - ######################################################################################## - - # For displaying a privacy policy to users, we can either link to the URL specified by - # PRIVACY_POLICY_URL, or default to the `static/privacy_policy.md` file in fence. - PRIVACY_POLICY_URL: null - - # ////////////////////////////////////////////////////////////////////////////////////// - # RELIABILITY OPTS - # ////////////////////////////////////////////////////////////////////////////////////// - # Configurations related to resiliency, fault-tolerance and availability - # This is the number of requests per second that the Nginx proxy will accept before reaching fence - # The value defined in fence-config-public.yaml takes precedence over this one - # In the absence of this OVERRIDE prefixed config, the legacy NGINX_RATE_LIMIT from the k8s deployment yaml is applied - OVERRIDE_NGINX_RATE_LIMIT: 18 - - # ////////////////////////////////////////////////////////////////////////////////////// - # SUPPORT INFO - # ////////////////////////////////////////////////////////////////////////////////////// - # If you want an email address to show up when an unhandled error occurs, provide one - # here. Something like: support@example.com - SUPPORT_EMAIL_FOR_ERRORS: null - - # ////////////////////////////////////////////////////////////////////////////////////// - # SHIBBOLETH - # - Support using `shibboleth` in LOGIN_OPTIONS - # - Contains defaults for using NIH's Login. - # ////////////////////////////////////////////////////////////////////////////////////// - # assumes shibboleth is deployed under {{BASE_URL}}/shibboleth - SHIBBOLETH_HEADER: 'persistent_id' - SSO_URL: 'https://auth.nih.gov/affwebservices/public/saml2sso?SPID={{BASE_URL}}/shibboleth&RelayState=' - ITRUST_GLOBAL_LOGOUT: 'https://auth.nih.gov/siteminderagent/smlogout.asp?mode=nih&AppReturnUrl=' - - # ////////////////////////////////////////////////////////////////////////////////////// - # dbGaP USER SYNCING SUPPORT - # - Support syncing authorization information from dbGaP - # ////////////////////////////////////////////////////////////////////////////////////// - # "dbGaP project serves as an access gateway for researchers seeking to gain - # access to genotype and phenotype data" - # - # User syncing and access can also be done throught a User Access file. See - # fence's README for more information - dbGaP: - - info: - host: '' - username: '' - password: '' - port: 22 - proxy: '' - proxy_user: '' - protocol: 'sftp' - decrypt_key: '' - # parse out the consent from the dbgap accession number such that something - # like "phs000123.v1.p1.c2" becomes "phs000123.c2". - # - # NOTE: when this is "false" the above would become "phs000123" - parse_consent_code: true - # A consent of "c999" can indicate access to that study's "exchange area data" - # and when a user has access to one study's exchange area data, they - # have access to the parent study's "common exchange area data" that is not study - # specific. The following config is whether or not to parse/handle "c999" codes - # for access to the common exchange area data - # - # NOTE: When enabled you MUST also provide a mapping to the - # `study_common_exchange_areas` from study -> parent common exchange area resource - enable_common_exchange_area_access: false - # The below configuration is a mapping from studies to their "common exchange area data" - # Fence project name a user gets access to when parsing c999 exchange area codes (and - # subsequently gives access to an Arborist resource representing this common area - # as well) - study_common_exchange_areas: - 'example': 'test_common_exchange_area' - # 'studyX': 'test_common_exchange_area' - # 'studyY': 'test_common_exchange_area' - # 'studyZ': 'test_common_exchange_area' - # A mapping from the dbgap study / Fence project to which authorization namespaces the - # actual data lives in. For example, `studyX` data may exist in multiple organizations, so - # we need to know how to map authorization to all orgs resources - study_to_resource_namespaces: - '_default': ['/'] - 'test_common_exchange_area': ['/dbgap/'] - # above are for default support and exchange area support - # below are further examples - # - # 'studyX': ['/orgA/', '/orgB/'] - # 'studyX.c2': ['/orgB/', '/orgC/'] - # 'studyZ': ['/orgD/'] - # Regex to match an assession number that has consent information in forms like: - # phs00301123.c999 - # phs000123.v3.p1.c3 - # phs000123.c3 - # phs00301123.v3.p4.c999 - # Will NOT MATCH forms like: phs000123 - # - # WARNING: Do not change this without consulting the code that uses it - DBGAP_ACCESSION_WITH_CONSENT_REGEX: '(?Pphs[0-9]+)(.(?Pv[0-9]+)){0,1}(.(?Pp[0-9]+)){0,1}.(?Pc[0-9]+)' - - # ////////////////////////////////////////////////////////////////////////////////////// - # STORAGE BACKENDS AND CREDENTIALS - # - Optional: Used for `/admin` & `/credentials` endpoints for user management. - # Also used during User Syncing process to automate managing Storage - # access for users. - # ////////////////////////////////////////////////////////////////////////////////////// - # When true, this modifies usersync (not fence service itself) such that when syncing user - # access to a Google storage backend happens in "bulk" by doing a diff *per google group* - # between what's in Google and what's expected. Then it adds, removes only as necessary. - # This is in contrast to the default logic which does blind updates per user and ignores - # 409s from Google. - # NOTE: This reduces the number of API calls to Google in the general case, but increases - # memory usages by usersync (as it has to track all the Google groups and user access) - GOOGLE_BULK_UPDATES: false - - # Configuration for various storage systems for the backend - # NOTE: Remove the {} and supply backends if needed. Example in comments below - STORAGE_CREDENTIALS: {} - # Google Cloud Storage backend - # - # 'google': - # backend: 'google' - # # this should be the project id where the Google Groups for data access are managed - # google_project_id: 'some-project-id-12378923' - - # Cleversafe data storage backend - # - # 'cleversafe-server-a': - # backend: 'cleversafe' - # aws_access_key_id: '' - # aws_secret_access_key: '' - # host: 'somemanager.osdc.io' - # public_host: 'someobjstore.example.com' - # port: 443 - # is_secure: true - # username: 'someone' - # password: 'somepass' - # is_mocked: true - - # ////////////////////////////////////////////////////////////////////////////////////// - # AWS BUCKETS AND CREDENTIALS - # - Support `/data` endpoints - # ////////////////////////////////////////////////////////////////////////////////////// - AWS_CREDENTIALS: {} - # NOTE: Remove the {} and supply creds if needed. Example in comments below - # 'CRED1': - # aws_access_key_id: '' - # aws_secret_access_key: '' - # 'CRED2': - # aws_access_key_id: '' - # aws_secret_access_key: '' - - # NOTE: the region is optonal for s3_buckets, however it should be specified to avoid a - # call to GetBucketLocation which you make lack the AWS ACLs for. - # public buckets do not need the region field. - # the cred values should be keys in section `AWS_CREDENTIALS`. - S3_BUCKETS: {} - # NOTE: Remove the {} and supply buckets if needed. Example in comments below - # bucket1: - # cred: 'CRED1' - # region: 'us-east-1' - # # optionally you can manually specify an s3-compliant endpoint for this bucket - # endpoint_url: 'https://cleversafe.example.com/' - # bucket2: - # cred: 'CRED2' - # region: 'us-east-1' - # bucket3: - # cred: '*' # public bucket - # bucket4: - # cred: 'CRED1' - # region: 'us-east-1' - # role-arn: 'arn:aws:iam::role1' - - # `DATA_UPLOAD_BUCKET` specifies an S3 bucket to which data files are uploaded, - # using the `/data/upload` endpoint. This must be one of the first keys under - # `S3_BUCKETS` (since these are the buckets fence has credentials for). - DATA_UPLOAD_BUCKET: 'bucket1' - - # ////////////////////////////////////////////////////////////////////////////////////// - # PROXY - # - Optional: If the api is behind firewall that needs to set http proxy - # ////////////////////////////////////////////////////////////////////////////////////// - # NOTE: leave as-is to not use proxy - # this is only used by the Google Oauth2Client at the moment if provided - HTTP_PROXY: - host: null - port: 3128 - - # ////////////////////////////////////////////////////////////////////////////////////// - # MICROSERVICE PATHS - # - Support `/data` endpoints & authz functionality - # ////////////////////////////////////////////////////////////////////////////////////// - # url where indexd microservice is running (for signed urls primarily) - # NOTE: Leaving as null will force fence to default to {{BASE_URL}}/index - # example value: 'https://example.com/index' - INDEXD: null - - # this is the username which fence uses to make authenticated requests to indexd - INDEXD_USERNAME: 'fence' - # this is the password which fence uses to make authenticated requests to indexd - INDEXD_PASSWORD: '' - - # ////////////////////////////////////////////////////////////////////////////////////// - # AZURE STORAGE BLOB CONFIGURATION - # - Support Azure Blob Data Access Methods - # ////////////////////////////////////////////////////////////////////////////////////// - - # https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&tabs=azure-portal#view-account-access-keys - # AZ_BLOB_CREDENTIALS: 'fake connection string' - AZ_BLOB_CREDENTIALS: - - # AZ_BLOB_CONTAINER_URL: 'https://storageaccount.blob.core.windows.net/container/' - # this is the container used for uploading, and should match the storage account - # used in the connection string for AZ_BLOB_CREDENTIALS - AZ_BLOB_CONTAINER_URL: 'https://myfakeblob.blob.core.windows.net/my-fake-container/' - - # url where authz microservice is running - ARBORIST: null - - # url where the audit-service is running - AUDIT_SERVICE: 'http://audit-service' - ENABLE_AUDIT_LOGS: - presigned_url: false - login: false - # `PUSH_AUDIT_LOGS_CONFIG.type` is one of: [api, aws_sqs]. - # - if type == api: logs are created by hitting the log creation endpoint. - # - if type == aws_sqs: logs are pushed to an SQS and `aws_sqs_config` fields - # `sqs_url` and `region` are required. Field `aws_cred` is optional and it - # should be a key in section `AWS_CREDENTIALS`. - PUSH_AUDIT_LOGS_CONFIG: - type: aws_sqs - aws_sqs_config: - sqs_url: - region: - aws_cred: - - # ////////////////////////////////////////////////////////////////////////////////////// - # CLOUD API LIBRARY (CIRRUS) AND GOOGLE CONFIGURATION - # - Support Google Data Access Methods - # ////////////////////////////////////////////////////////////////////////////////////// - # Setting this up allows fence to create buckets, manage Google groups, etc. - # See directions here for setting up cirrus: https://github.com/uc-cdis/cirrus - CIRRUS_CFG: - GOOGLE_API_KEY: '' - GOOGLE_PROJECT_ID: '' - GOOGLE_APPLICATION_CREDENTIALS: '' - GOOGLE_STORAGE_CREDS: '' - GOOGLE_ADMIN_EMAIL: '' - GOOGLE_IDENTITY_DOMAIN: '' - GOOGLE_CLOUD_IDENTITY_ADMIN_EMAIL: '' - - # Prefix to namespace Google Groups on a single Cloud Identity (see cirrus - # setup for more info on Cloud Identity) - # - # NOTE: Make this short! Less than 8 characters if possible. Google has - # length restrictions on group names. - GOOGLE_GROUP_PREFIX: '' - - # Prefix to namespace Google Service Accounts in a single Google Cloud Platform Project. - # This is primarily to support multiple instances of fence references the same Google - # project. If that is not something you need to support, then you can leave this blank. - # - # NOTE: Make this short! Less than 8 characters if possible. Google has - # length restrictions on service account names. - GOOGLE_SERVICE_ACCOUNT_PREFIX: '' - - # A Google Project identitifier representing the default project to bill to for - # accessing Google Requester Pays buckets (for signed urls and/or temporary service account - # credentials). If this is provided and the API call for - # Google access does not include a `userProject`, this will be used instead. - # - # WARNING: Setting this WITHOUT setting "ENABLE_AUTOMATIC_BILLING_*" to `true` below, - # means that clients and end-users will be responsible for making sure that - # the service account used in either of these methods actually has billing - # permission in the specified project. - BILLING_PROJECT_FOR_SIGNED_URLS: - BILLING_PROJECT_FOR_SA_CREDS: - - # Setting this to `true` will make Fence automatically attempt to create a Custom Role - # in the billing project and give the necessary Google Service Account that role - # (which will allow it to bill to the project). - # - # NOTE: The Fence SA will need the necessary permissions in the specified project to - # both create a custom role and update the Project's IAM Policy to include the - # necessary SA. At the time of writing, there are pre-defined roles in Google's - # IAM that provide the necessary permissions. Those are "Project IAM Admin" and - # "Role Administrator" - # - # NOTE2: It may be possible to further restrict the permissions in the future to - # be more fine-grained. - # - ENABLE_AUTOMATIC_BILLING_PERMISSION_SIGNED_URLS: false - ENABLE_AUTOMATIC_BILLING_PERMISSION_SA_CREDS: false - - # ////////////////////////////////////////////////////////////////////////////////////// - # EMAIL - # - Support for sending emails from fence. Used for user certificates - # and `/google/service_accounts` endpoints - # ////////////////////////////////////////////////////////////////////////////////////// - # Gun Mail Service (for sending emails from fence) - # - # NOTE: Example in comments below - GUN_MAIL: - 'datacommons.io': - smtp_hostname: 'smtp.mailgun.org' - api_key: '' - default_login: 'postmaster@mailgun.example.com' - api_url: 'https://api.mailgun.net/v3/mailgun.example.com' - smtp_password: '' - - # For emails regarding users certificates - EMAIL_SERVER: 'localhost' - SEND_FROM: 'example@gmail.com' - SEND_TO: 'example@gmail.com' - - # ////////////////////////////////////////////////////////////////////////////////////// - # DATA ACCESS: GOOGLE LINKING & SERVICE ACCOUNT REGISTRATION - # - Support `/google/service_accounts` endpoints - # ////////////////////////////////////////////////////////////////////////////////////// - # whether or not to allow access to the /link/google endpoints - ALLOW_GOOGLE_LINKING: true - - # A Google Project with controlled data access will be determined INVALID if - # if it has a parent organization UNLESS that parent organization's ID is in this - # whitelist. - # - # NOTE: Remove the [] and Google Organization IDs if needed. Example in comments below - WHITE_LISTED_GOOGLE_PARENT_ORGS: [] - # - '12345678910' - - # A Google Project with Google Service Accounts determined INVALID will result in the - # the entire project being invalid UNLESS that service accounts's email is in this - # whitelist. - # - # NOTE: Remove the [] and service account emails if needed. Example in comments below - WHITE_LISTED_SERVICE_ACCOUNT_EMAILS: [] - # - 'example@developer.gserviceaccount.com' - # - 'example@test.iam.gserviceaccount.com' - - # when service accounts or google projects are determined invalid, an email is sent - # to the project owners. These settings are for that email - REMOVE_SERVICE_ACCOUNT_EMAIL_NOTIFICATION: - enable: false - # this domain MUST exist in GUN_MAIL config - domain: 'example.com' - from: 'do-not-reply@example.com' - subject: 'User service account removal notification' - # the {} gets replaced dynamically in the Python code to be the Project ID - content: > - Service accounts were removed from access control data because some users or - service accounts of GCP Project {} are not authorized to access the data sets - associated to the service accounts, or do not adhere to the security policies. - # this admin email will be included as a recipient to *any* email to anyone about - # service account removal. - # - # WARNING: This is NOT a bcc so the email is visible to the end-user - admin: - - 'admin@example.edu' - - PROBLEM_USER_EMAIL_NOTIFICATION: - # this domain MUST exist in GUN_MAIL config - domain: 'example.com' - from: 'do-not-reply@example.com' - subject: 'Account access error notification' - # the {} gets replaced dynamically in the Python code to be the Project ID - content: > - The Data Commons Framework utilizes dbGaP for data access authorization. - Another member of a Google project you belong to ({}) is attempting to - register a service account to the following additional datasets ({}). - Please contact dbGaP to request access. - # this admin email will be included as a recipient to *any* email to anyone about - # service account removal. - # - # WARNING: This is NOT a bcc so the email is visible to the end-user - admin: - - 'admin@example.edu' - - # Service account email domains that represent a service account that Google owns. - # These are usually created when a sepcific GCP service is enabled. - # This is used for Service Account Validation for Data Access. - GOOGLE_MANAGED_SERVICE_ACCOUNT_DOMAINS: - - 'dataflow-service-producer-prod.iam.gserviceaccount.com' - - 'cloudbuild.gserviceaccount.com' - - 'cloud-ml.google.com.iam.gserviceaccount.com' - - 'container-engine-robot.iam.gserviceaccount.com' - - 'dataflow-service-producer-prod.iam.gserviceaccount.com' - - 'sourcerepo-service-accounts.iam.gserviceaccount.com' - - 'dataproc-accounts.iam.gserviceaccount.com' - - 'gae-api-prod.google.com.iam.gserviceaccount.com' - - 'genomics-api.google.com.iam.gserviceaccount.com' - - 'containerregistry.iam.gserviceaccount.com' - - 'container-analysis.iam.gserviceaccount.com' - - 'cloudservices.gserviceaccount.com' - - 'stackdriver-service.iam.gserviceaccount.com' - - 'appspot.gserviceaccount.com' - - 'partnercontent.gserviceaccount.com' - - 'trifacta-gcloud-prod.iam.gserviceaccount.com' - - 'gcf-admin-robot.iam.gserviceaccount.com' - - 'compute-system.iam.gserviceaccount.com' - - 'gcp-sa-websecurityscanner.iam.gserviceaccount.com' - - 'storage-transfer-service.iam.gserviceaccount.com' - - 'firebase-sa-management.iam.gserviceaccount.com' - - 'firebase-rules.iam.gserviceaccount.com' - - 'gcp-sa-cloudbuild.iam.gserviceaccount.com' - - 'gcp-sa-automl.iam.gserviceaccount.com' - - 'gcp-sa-datalabeling.iam.gserviceaccount.com' - - 'gcp-sa-cloudscheduler.iam.gserviceaccount.com' - - # The types of service accounts that are allowed to be registered at - # /google/service_accounts endpoints - ALLOWED_USER_SERVICE_ACCOUNT_DOMAINS: - # compute engine default service account - - 'developer.gserviceaccount.com' - # app engine default service account - - 'appspot.gserviceaccount.com' - # user-managed service account - - 'iam.gserviceaccount.com' - - # Synapse integration and DREAM challenge mapping. Team is from Synapse, and group is - # providing the actual permission in Arborist. User will be added to the group for TTL - # seconds if the team matches. - DREAM_CHALLENGE_TEAM: 'DREAM' - DREAM_CHALLENGE_GROUP: 'DREAM' - SYNAPSE_URI: 'https://repo-prod.prod.sagebase.org/auth/v1' - SYNAPSE_JWKS_URI: - # deprecated, use the discovery_url in the OPENID_CONNECT block for the synapse client - SYNAPSE_DISCOVERY_URL: - SYNAPSE_AUTHZ_TTL: 86400 - - # Role caching for generating presigned urls if max role session increase is true - # then we can increase the amount of time that a session is valid for - MAX_ROLE_SESSION_INCREASE: false - ASSUME_ROLE_CACHE_SECONDS: 1800 - - # Optional user registration feature: Ask users to register (provide firstname/lastname/org/email) on login. - # If user registers, add them to configured Arborist group; idea is that the Arborist group - # will have access to download data. - REGISTER_USERS_ON: false - REGISTERED_USERS_GROUP: '' - # RAS refresh_tokens expire in 15 days - RAS_REFRESH_EXPIRATION: 1296000 - # List of JWT issuers from which Fence will accept GA4GH visas - GA4GH_VISA_ISSUER_ALLOWLIST: - - '{{BASE_URL}}' - - 'https://sts.nih.gov' - - 'https://stsstg.nih.gov' - # Number of projects that can be registered to a Google Service Accont - SERVICE_ACCOUNT_LIMIT: 6 - - # Global sync visas during login - # None(Default): Allow per client i.e. a fence client can pick whether or not to sync their visas during login with parse_visas param in /authorization endpoint - # True: Parse for all clients i.e. a fence client will always sync their visas during login - # False: Parse for no clients i.e. a fence client will not be able to sync visas during login even with parse_visas param - GLOBAL_PARSE_VISAS_ON_LOGIN: - # Settings for usersync with visas - USERSYNC: - sync_from_visas: false - # fallback to dbgap sftp when there are no valid visas for a user i.e. if they're expired or if they're malformed - fallback_to_dbgap_sftp: false - visa_types: - ras: ["https://ras.nih.gov/visas/v1", "https://ras.nih.gov/visas/v1.1"] - RAS_USERINFO_ENDPOINT: '/openid/connect/v1.1/userinfo' diff --git a/sample-values/user.yaml b/sample-values/user.yaml deleted file mode 100644 index 42a25552..00000000 --- a/sample-values/user.yaml +++ /dev/null @@ -1,92 +0,0 @@ -fence: - USER_YAML: | - authz: - # policies automatically given to anyone, even if they are not authenticated - anonymous_policies: - - open_data_reader - - # policies automatically given to authenticated users (in addition to their other policies) - all_users_policies: [] - - # each group can contain multiple policies and multiple users - groups: - - name: program1_readers - policies: - - program1_reader - users: - - username1@domain.com - - # resource tree - resources: - - name: open - - name: programs - subresources: - - name: program1 - - # each policy can contain multiple roles and multiple resources - policies: - - id: open_data_reader - role_ids: - - reader - - storage_reader - resource_paths: - - /open - - id: program1_reader - description: Read access to program1 - role_ids: - - reader - - storage_reader - resource_paths: - - /programs/program1 - - id: program1_indexd_admin - description: Admin access to program1 - role_ids: - - indexd_admin - resource_paths: - - /programs/program1 - - # currently existing methods are `read`, `create`, `update`, - # `delete`, `read-storage` and `write-storage` - roles: - - id: reader - permissions: - - id: reader - action: - method: read - service: '*' - - id: storage_reader - permissions: - - id: storage_reader - action: - method: read-storage - service: '*' - - id: creator - permissions: - - id: creator - action: - method: create - service: '*' - - id: indexd_admin - permissions: - - id: indexd_admin - action: - method: '*' - service: indexd - - # OIDC clients - clients: - client1: - policies: - - open_data_reader - - # all users must be defined here, even if they are not granted - # any individual permissions outside of the groups they are in. - # additional arbitrary information can be added in `tags`. - users: - username1@domain.com: {} - username2: - tags: - name: John Doe - email: johndoe@domain.com - policies: - - program1_reader diff --git a/sample-values/values_aws_dev.yaml b/sample-values/values_aws_dev.yaml deleted file mode 100644 index 9907dd06..00000000 --- a/sample-values/values_aws_dev.yaml +++ /dev/null @@ -1,69 +0,0 @@ -global: - environment: devplanetv1 - aws: - enabled: true - hostname: qureshi.planx-pla.net - revproxyArn: arn:aws:acm:us-east-1:707767160287:certificate/520ede2f-fc82-4bb9-af96-4b4af7deabbd -fence: - FENCE_CONFIG: - OPENID_CONNECT: - google: - client_id: "" - client_secret: "" - - -secrets: - awsAccessKeyId: - awsSecretAccessKey: - -hatchery: - image: - tag: feat_localdev - hatchery: - containers: - - target-port: 8888 - cpu-limit: '0.5' - memory-limit: 1Gi - name: "(Tutorials) Example Analysis Jupyter Lab Notebooks" - image: quay.io/cdis/heal-notebooks:combined_tutorials__latest - env: - FRAME_ANCESTORS: https://{{ .Values.global.hostname }} - args: - - "--NotebookApp.base_url=/lw-workspace/proxy/" - - "--NotebookApp.default_url=/lab" - - "--NotebookApp.password=''" - - "--NotebookApp.token=''" - - "--NotebookApp.shutdown_no_activity_timeout=5400" - - "--NotebookApp.quit_button=False" - command: - - start-notebook.sh - path-rewrite: "/lw-workspace/proxy/" - use-tls: 'false' - ready-probe: "/lw-workspace/proxy/" - lifecycle-post-start: - - "/bin/sh" - - "-c" - - export IAM=`whoami`; rm -rf /home/$IAM/pd/dockerHome; rm -rf /home/$IAM/pd/lost+found; - ln -s /data /home/$IAM/pd/; true - user-uid: 1000 - fs-gid: 100 - user-volume-location: "/home/jovyan/pd" - gen3-volume-location: "/home/jovyan/.gen3" - - -portal: - image: - tag: dev - -guppy: - enabled: true - dbRestore: true - -indexd: - dbRestore: true - -metadata: - dbRestore: true - -sheepdog: - dbRestore: true \ No newline at end of file diff --git a/sample-values/values_google_cloud_dev.yaml b/sample-values/values_google_cloud_dev.yaml deleted file mode 100644 index 4bb66aec..00000000 --- a/sample-values/values_google_cloud_dev.yaml +++ /dev/null @@ -1,9 +0,0 @@ -global: - dev: true - hostname: qureshi.planx-pla.net -fence: - FENCE_CONFIG: - OPENID_CONNECT: - google: - client_id: "" - client_secret: "" diff --git a/sample-values/values_local_dev.yaml b/sample-values/values_local_dev.yaml deleted file mode 100644 index d2c905d8..00000000 --- a/sample-values/values_local_dev.yaml +++ /dev/null @@ -1,10 +0,0 @@ -global: - dev: true - hostname: localhost -fence: - FENCE_CONFIG: - OPENID_CONNECT: - google: - client_id: "" - client_secret: "" -