diff --git a/.github/workflows/sync-cdis-with-pcdc.yml b/.github/workflows/sync-cdis-with-pcdc.yml new file mode 100644 index 000000000..6a1d3a686 --- /dev/null +++ b/.github/workflows/sync-cdis-with-pcdc.yml @@ -0,0 +1,135 @@ +name: Sync from Upstream + +on: + schedule: + - cron: '0 6 * * 1' # Runs at 06:00 UTC every Monday + workflow_dispatch: + +jobs: + sync-and-create-pr: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + actions: read + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + - name: Set up Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote and fetch + run: | + git remote add upstream https://github.com/uc-cdis/gen3-helm.git + git fetch upstream master + git fetch origin # Ensure we have latest origin refs + + - name: Create and push sync branch + run: | + # Delete existing sync branch if it exists + git push origin --delete sync-cdis-pcdc || true + + # Create sync branch from pcdc_dev + git checkout -b sync-cdis-pcdc origin/pcdc_dev + echo "Created sync-cdis-pcdc branch from pcdc_dev" + + - name: Attempt merge + id: merge + run: | + # Store the current HEAD before merge + BEFORE_MERGE=$(git rev-parse HEAD) + echo "conflict_branch=false" >> $GITHUB_OUTPUT + + if git merge upstream/master --no-edit; then + # Get the HEAD after merge + AFTER_MERGE=$(git rev-parse HEAD) + + # Check if the merge actually created any changes + if [ "$BEFORE_MERGE" = "$AFTER_MERGE" ]; then + echo "merge_success=false" >> $GITHUB_OUTPUT + echo "❌ No changes after merge - branches are already up to date" + exit 0 + fi + + # Count actual changes from the merge + CHANGES=$(git diff --name-only $BEFORE_MERGE $AFTER_MERGE | wc -l) + + + # Verify we have actual changes before proceeding + if [ "$CHANGES" -eq 0 ]; then + echo "merge_success=false" >> $GITHUB_OUTPUT + echo "❌ No file changes detected after merge" + exit 0 + fi + + echo "merge_success=true" >> $GITHUB_OUTPUT + echo "✅ Merge successful with changes" + + else + echo "merge_success=false" >> $GITHUB_OUTPUT + echo "conflict_branch=true" >> $GITHUB_OUTPUT + echo "❌ Merge conflicts detected" + fi + + - name: Push successful merge + if: steps.merge.outputs.merge_success == 'true' + run: | + git push -u origin sync-cdis-pcdc + echo "✅ Pushed sync-cdis-pcdc branch" + + - name: Wait for branch to be available + if: steps.merge.outputs.merge_success == 'true' + run: | + echo "Waiting for branch to be available on GitHub..." + sleep 10 + + # Verify branches exist on GitHub + echo "Checking if branches exist on GitHub..." + gh api repos/chicagopcdc/gen3-helm/branches/pcdc_dev --jq .name + gh api repos/chicagopcdc/gen3-helm/branches/sync-cdis-pcdc --jq .name + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + + - name: Create Pull Request for successful merge + if: steps.merge.outputs.merge_success == 'true' + run: | + echo "Creating PR with the following details:" + echo "Base: pcdc_dev" + echo "Head: sync-cdis-pcdc" + echo "Files changed: ${{ steps.merge.outputs.files_changed }}" + + # Check if PR already exists + if gh pr list --base pcdc_dev --head sync-cdis-pcdc --json number | jq -e '.[0]' > /dev/null; then + echo "PR already exists, skipping creation" + exit 0 + fi + + # Create the PR with explicit repository context + gh pr create \ + --repo chicagopcdc/gen3-helm \ + --base pcdc_dev \ + --head sync-cdis-pcdc \ + --title "Sync: Upstream Master into pcdc_dev ($(date -u +%Y-%m-%d))" \ + --body "This PR automatically syncs changes from upstream/master into the pcdc_dev branch. Files changed: ${{ steps.merge.outputs.files_changed }}" + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + + - name: Create conflict resolution branch + if: steps.merge.outputs.merge_success == 'false' && steps.merge.outputs.conflict_branch == 'true' + run: | + # Create timestamped branch name + TIMESTAMP=$(date -u +%Y%m%d-%H%M%S) + CONFLICT_BRANCH="conflict-resolution-${TIMESTAMP}" + + # Create and push conflict resolution branch with unresolved conflicts + git checkout -b "$CONFLICT_BRANCH" + git push -u origin "$CONFLICT_BRANCH" + + echo "CONFLICT_BRANCH=${CONFLICT_BRANCH}" >> $GITHUB_ENV + echo "Created conflict resolution branch: $CONFLICT_BRANCH" \ No newline at end of file diff --git a/.gitignore b/.gitignore index df2d04fe8..528c85cea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,29 @@ -postgres.txt +# --- Folders --- **/charts/ notes/ +CA/ +gen3_scripts/ +gen3_etl/ +_sample-*/ + +# --- Files --- Chart.lock -.DS_Store -_sample-*/ \ No newline at end of file +# macOS system file (usually ignored) +.DS_Store +# Helm secrets/values file +secret-values.yaml +# Environment variables +.env +# Instance generated service account or credentials +credentials.json +temp.yaml +# Main Helm values file +/values.yaml +postgres.txt +# External reference data file for PCDC +pcdc_data/external/external_reference.json +# Script that creates external secrets IAM user +external_secrets.bash +secret-gearbox-default-values.yaml +secret-openshift-gearbox-default-values.yaml +secret-pcdc-default-values.yaml \ No newline at end of file diff --git a/helm/access-backend/README.md b/helm/access-backend/README.md index 2047b7f99..8b669cba7 100644 --- a/helm/access-backend/README.md +++ b/helm/access-backend/README.md @@ -6,141 +6,142 @@ A Helm chart for Kubernetes ## Requirements -| Repository | Name | Version | -|------------|------|---------| -| file://../common | common | 0.1.28 | +| Repository | Name | Version | +| ---------------- | ------ | ------- | +| file://../common | common | 0.1.28 | ## Values -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| adminUsers | string | `""` | | -| admin_extra_policies | string | `""` | | -| admin_owner | string | `""` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key | string | `"karpenter.sh/capacity-type"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].operator | string | `"In"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].values[0] | string | `"spot"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].key | string | `"eks.amazonaws.com/capacityType"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].operator | string | `"In"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].values[0] | string | `"SPOT"` | | -| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].weight | int | `99` | | -| 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 | `"access-backend"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | -| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `25` | | -| allow_origins | string | `""` | | -| autoscaling | object | `{}` | | -| base_user_yaml_path | string | `"/src/user.yaml"` | | -| cascade_new_access_to_users_under_this_admin | string | `""` | | -| db_namespace | string | `""` | | -| debug | bool | `false` | | -| deny_username_patterns | string | `""` | | -| disallow_access_subsetting | string | `""` | | -| externalSecrets | map | `{"accessBackendG3auto":null,"createK8sAccessBackendSecret":false}` | External Secrets settings. | -| externalSecrets.accessBackendG3auto | string | `nil` | Will override the name of the aws secrets manager secret. Default is "access-backend-g3auto" | -| externalSecrets.createK8sAccessBackendSecret | string | `false` | Will create the Helm "access-backend-g3auto" secret even if Secrets Manager is enabled. This is helpful if you are wanting to use External Secrets for some, but not all secrets. | -| extraArgs | string | `"{\"endpoint_url\": \"http://dynamodb:8000\", \"region_name\": \"us-east-1\"}"` | | -| fullnameOverride | string | `""` | | -| gh_file | string | `""` | | -| gh_key | string | `""` | | -| gh_org | string | `""` | | -| gh_repo | string | `""` | | -| global.autoscaling.averageCPUValue | string | `"500m"` | | -| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | -| global.autoscaling.enabled | bool | `false` | | -| global.autoscaling.maxReplicas | int | `10` | | -| global.autoscaling.minReplicas | int | `1` | | -| 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.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.externalSecrets | map | `{"deploy":false,"separateSecretStore":false}` | External Secrets settings. | -| global.externalSecrets.deploy | bool | `false` | Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any access-backend secrets you have deployed. | -| global.externalSecrets.separateSecretStore | string | `false` | Will deploy a separate External Secret Store for this 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.minAvailable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | -| global.netPolicy | map | `{"enabled":false}` | Controls network policy settings | -| 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.dbCreate | bool | `true` | Whether the database should be created. | -| global.postgres.externalSecret | string | `""` | Name of external secret. Disabled if empty | -| 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` | -| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | -| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | -| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | -| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | -| image.pullPolicy | string | `"Always"` | | -| image.repository | string | `"quay.io/cdis/access-backend"` | | -| image.tag | string | `"latest"` | | -| 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 | `[]` | | -| inherit_access_to_all_new_datasets_to_this_admin | string | `""` | | -| jwt_issuers | string | `"https://localhost/user"` | | -| jwt_signing_keys | string | `""` | | -| livenessProbe.httpGet.path | string | `"/"` | | -| livenessProbe.httpGet.port | string | `"http"` | | -| metricsEnabled | string | `nil` | | -| nameOverride | string | `""` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podLabels | object | `{}` | | -| podSecurityContext | object | `{}` | | -| 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 - | -| 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. | -| readinessProbe.httpGet.path | string | `"/"` | | -| readinessProbe.httpGet.port | string | `"http"` | | -| replicaCount | int | `1` | | -| resources.limits.memory | string | `"2048Mi"` | | -| resources.requests.memory | string | `"128Mi"` | | -| review_requests_access | string | `""` | | -| revisionHistoryLimit | int | `2` | | -| secrets | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null}` | Secret information to access the db restore job S3 bucket. | -| secrets.awsAccessKeyId | str | `nil` | AWS access key ID. Overrides global key. | -| secrets.awsSecretAccessKey | str | `nil` | AWS secret access key ID. Overrides global key. | -| securityContext | object | `{}` | | -| service.port | int | `80` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.automount | bool | `true` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `"access-backend-sa"` | | -| skip_pr | bool | `false` | | -| strategy.rollingUpdate.maxSurge | int | `1` | | -| strategy.rollingUpdate.maxUnavailable | int | `0` | | -| strategy.type | string | `"RollingUpdate"` | | -| superAdmins | string | `""` | | -| tolerations | list | `[]` | | -| volumeMounts[0].mountPath | string | `"/src/user.yaml"` | | -| volumeMounts[0].name | string | `"config-volume"` | | -| volumeMounts[0].readOnly | bool | `true` | | -| volumeMounts[0].subPath | string | `"user.yaml"` | | -| volumes | list | `[]` | | +| Key | Type | Default | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| adminUsers | string | `""` | | +| admin_extra_policies | string | `""` | | +| admin_owner | string | `""` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key | string | `"karpenter.sh/capacity-type"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].operator | string | `"In"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].values[0] | string | `"spot"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `100` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].key | string | `"eks.amazonaws.com/capacityType"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].operator | string | `"In"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].values[0] | string | `"SPOT"` | | +| affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].weight | int | `99` | | +| 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 | `"access-backend"` | | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey | string | `"kubernetes.io/hostname"` | | +| affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight | int | `25` | | +| allow_origins | string | `""` | | +| autoscaling | object | `{}` | | +| base_user_yaml_path | string | `"/src/user.yaml"` | | +| cascade_new_access_to_users_under_this_admin | string | `""` | | +| db_namespace | string | `""` | | +| debug | bool | `false` | | +| deny_username_patterns | string | `""` | | +| disallow_access_subsetting | string | `""` | | +| externalSecrets | map | `{"accessBackendG3auto":null,"createK8sAccessBackendSecret":false}` | External Secrets settings. | +| externalSecrets.accessBackendG3auto | string | `nil` | Will override the name of the aws secrets manager secret. Default is "access-backend-g3auto" | +| externalSecrets.createK8sAccessBackendSecret | string | `false` | Will create the Helm "access-backend-g3auto" secret even if Secrets Manager is enabled. This is helpful if you are wanting to use External Secrets for some, but not all secrets. | +| extraArgs | string | `"{\"endpoint_url\": \"http://dynamodb:8000\", \"region_name\": \"us-east-1\"}"` | | +| fullnameOverride | string | `""` | | +| gh_file | string | `""` | | +| gh_key | string | `""` | | +| gh_org | string | `""` | | +| gh_repo | string | `""` | | +| global.autoscaling.averageCPUValue | string | `"500m"` | | +| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | +| global.autoscaling.enabled | bool | `false` | | +| global.autoscaling.maxReplicas | int | `10` | | +| global.autoscaling.minReplicas | int | `1` | | +| 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.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.externalSecrets | map | `{"deploy":false,"separateSecretStore":false}` | External Secrets settings. | +| global.externalSecrets.deploy | bool | `false` | Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any access-backend secrets you have deployed. | +| global.externalSecrets.separateSecretStore | string | `false` | Will deploy a separate External Secret Store for this 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.minAvailable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.netPolicy | map | `{"enabled":false}` | Controls network policy settings | +| 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.dbCreate | bool | `true` | Whether the database should be created. | +| global.postgres.externalSecret | string | `""` | Name of external secret. Disabled if empty | +| 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` | +| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | +| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | +| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | +| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | +| image.pullPolicy | string | `"Always"` | | +| image.repository | string | `"quay.io/cdis/access-backend"` | | +| image.tag | string | `"latest"` | | +| 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 | `[]` | | +| inherit_access_to_all_new_datasets_to_this_admin | string | `""` | | +| jwt_issuers | string | `"https://localhost/user"` | | +| jwt_signing_keys | string | `""` | | +| livenessProbe.httpGet.path | string | `"/"` | | +| livenessProbe.httpGet.port | string | `"http"` | | +| metricsEnabled | string | `nil` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podLabels | object | `{}` | | +| podSecurityContext | object | `{}` | | +| 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 - | +| 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. | +| readinessProbe.httpGet.path | string | `"/"` | | +| readinessProbe.httpGet.port | string | `"http"` | | +| replicaCount | int | `1` | | +| resources.limits.memory | string | `"2048Mi"` | | +| resources.requests.memory | string | `"128Mi"` | | +| review_requests_access | string | `""` | | +| revisionHistoryLimit | int | `2` | | +| secrets | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null}` | Secret information to access the db restore job S3 bucket. | +| secrets.awsAccessKeyId | str | `nil` | AWS access key ID. Overrides global key. | +| secrets.awsSecretAccessKey | str | `nil` | AWS secret access key ID. Overrides global key. | +| securityContext | object | `{}` | | +| service.port | int | `80` | | +| service.targetPort | int | `80` | | +| service.type | string | `"ClusterIP"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.automount | bool | `true` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `"access-backend-sa"` | | +| skip_pr | bool | `false` | | +| strategy.rollingUpdate.maxSurge | int | `1` | | +| strategy.rollingUpdate.maxUnavailable | int | `0` | | +| strategy.type | string | `"RollingUpdate"` | | +| superAdmins | string | `""` | | +| tolerations | list | `[]` | | +| volumeMounts[0].mountPath | string | `"/src/user.yaml"` | | +| volumeMounts[0].name | string | `"config-volume"` | | +| volumeMounts[0].readOnly | bool | `true` | | +| volumeMounts[0].subPath | string | `"user.yaml"` | | +| volumes | list | `[]` | | diff --git a/helm/access-backend/templates/deployment.yaml b/helm/access-backend/templates/deployment.yaml index c89097c0a..cb455a483 100644 --- a/helm/access-backend/templates/deployment.yaml +++ b/helm/access-backend/templates/deployment.yaml @@ -72,7 +72,7 @@ spec: port: 80 ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/access-backend/values.yaml b/helm/access-backend/values.yaml index 34ae36fc1..0433cca90 100644 --- a/helm/access-backend/values.yaml +++ b/helm/access-backend/values.yaml @@ -274,6 +274,7 @@ service: type: ClusterIP # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports port: 80 + targetPort: 80 # This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ ingress: diff --git a/helm/amanuensis/.helmignore b/helm/amanuensis/.helmignore new file mode 100644 index 000000000..691fa13d6 --- /dev/null +++ b/helm/amanuensis/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ \ No newline at end of file diff --git a/helm/amanuensis/Chart.yaml b/helm/amanuensis/Chart.yaml new file mode 100644 index 000000000..ca8fa95c2 --- /dev/null +++ b/helm/amanuensis/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +name: amanuensis +description: A Helm chart for gen3 Amanuensis + +# 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: 1.0.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: "master" + +dependencies: + - name: common + version: 0.1.28 + repository: file://../common + - name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/amanuensis/amanuensis-secret/amanuensis_settings.py b/helm/amanuensis/amanuensis-secret/amanuensis_settings.py new file mode 100644 index 000000000..8522a7521 --- /dev/null +++ b/helm/amanuensis/amanuensis-secret/amanuensis_settings.py @@ -0,0 +1,79 @@ +import os +import json +from boto.s3.connection import OrdinaryCallingFormat + + +DB = "postgresql://test:test@localhost:5432/amanuensis" + +MOCK_AUTH = False +MOCK_STORAGE = False + +SERVER_NAME = "http://localhost/user" +BASE_URL = SERVER_NAME +APPLICATION_ROOT = "/user" + +ROOT_DIR = "/amanuensis" + +# If using multi-tenant setup, configure this to the base URL for the provider +# amanuensis (i.e. ``BASE_URL`` in the provider amanuensis config). +# OIDC_ISSUER = 'http://localhost:8080/user + + +HMAC_ENCRYPTION_KEY = "" + + + +""" +If the api is behind firewall that need to set http proxy: + HTTP_PROXY = {'host': 'cloud-proxy', 'port': 3128} +""" +HTTP_PROXY = None +STORAGES = ["/cleversafe"] + + + + +SESSION_COOKIE_SECURE = False +ENABLE_CSRF_PROTECTION = True + +INDEXD = "/index" + +INDEXD_AUTH = ("gdcapi", "") + +ARBORIST = "/rbac" + +AWS_CREDENTIALS = { + "CRED1": {"aws_access_key_id": "", "aws_secret_access_key": ""}, + "CRED2": {"aws_access_key_id": "", "aws_secret_access_key": ""}, +} + +ASSUMED_ROLES = {"arn:aws:iam::role1": "CRED1"} + +DATA_UPLOAD_BUCKET = "bucket1" + +S3_BUCKETS = { + "bucket1": {"cred": "CRED1"}, + "bucket2": {"cred": "CRED2"}, + "bucket3": {"cred": "CRED1", "role-arn": "arn:aws:iam::role1"}, +} + + +APP_NAME = "" + + + +# dir_path = "/secrets" +# fence_creds = os.path.join(dir_path, "fence_credentials.json") + + +# SUPPORT_EMAIL_FOR_ERRORS = None +# dbGaP = {} +# if os.path.exists(fence_creds): +# with open(fence_creds, "r") as f: +# data = json.load(f) +# AWS_CREDENTIALS = data["AWS_CREDENTIALS"] +# S3_BUCKETS = data["S3_BUCKETS"] +# OIDC_ISSUER = data["OIDC_ISSUER"] +# APP_NAME = data["APP_NAME"] +# HTTP_PROXY = data["HTTP_PROXY"] +# dbGaP = data["dbGaP"] diff --git a/helm/amanuensis/amanuensis-secret/config_helper.py b/helm/amanuensis/amanuensis-secret/config_helper.py new file mode 100644 index 000000000..869ca25af --- /dev/null +++ b/helm/amanuensis/amanuensis-secret/config_helper.py @@ -0,0 +1,486 @@ +import json +import os +import copy +import argparse +import re +import types + +# +# make it easy to change this for testing +XDG_DATA_HOME = os.getenv("XDG_DATA_HOME", "/usr/share/") + + +def default_search_folders(app_name): + """ + Return the list of folders to search for configuration files + """ + return [ + "%s/cdis/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/cdis/%s" % app_name, + "%s/gen3/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/gen3/%s" % app_name, + "/var/www/%s" % app_name, + "/etc/gen3/%s" % app_name, + ] + + +def find_paths(file_name, app_name, search_folders=None): + """ + Search the given folders for file_name + search_folders defaults to default_search_folders if not specified + return the first path to file_name found + """ + search_folders = search_folders or default_search_folders(app_name) + possible_files = [os.path.join(folder, file_name) for folder in search_folders] + return [path for path in possible_files if os.path.exists(path)] + + +def load_json(file_name, app_name, search_folders=None): + """ + json.load(file_name) after finding file_name in search_folders + + return the loaded json data or None if file not found + """ + actual_files = find_paths(file_name, app_name, search_folders) + if not actual_files: + return None + with open(actual_files[0], "r") as reader: + return json.load(reader) + + +def inject_creds_into_fence_config(creds_file_path, config_file_path): + creds_file = open(creds_file_path, "r") + creds = json.load(creds_file) + creds_file.close() + + # get secret values from creds.json file + db_host = _get_nested_value(creds, "db_host") + db_username = _get_nested_value(creds, "db_username") + db_password = _get_nested_value(creds, "db_password") + db_database = _get_nested_value(creds, "db_database") + hostname = _get_nested_value(creds, "hostname") + indexd_password = _get_nested_value(creds, "indexd_password") + google_client_secret = _get_nested_value(creds, "google_client_secret") + google_client_id = _get_nested_value(creds, "google_client_id") + hmac_key = _get_nested_value(creds, "hmac_key") + db_path = "postgresql://{}:{}@{}:5432/{}".format( + db_username, db_password, db_host, db_database + ) + + config_file = open(config_file_path, "r").read() + + print(" DB injected with value(s) from creds.json") + config_file = _replace(config_file, "DB", db_path) + + print(" BASE_URL injected with value(s) from creds.json") + config_file = _replace(config_file, "BASE_URL", "https://{}/user".format(hostname)) + + print(" INDEXD_PASSWORD injected with value(s) from creds.json") + config_file = _replace(config_file, "INDEXD_PASSWORD", indexd_password) + config_file = _replace(config_file, "INDEXD_USERNAME", "fence") + + print(" ENCRYPTION_KEY injected with value(s) from creds.json") + config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) + + print( + " OPENID_CONNECT/google/client_secret injected with value(s) " + "from creds.json" + ) + config_file = _replace( + config_file, "OPENID_CONNECT/google/client_secret", google_client_secret + ) + + print(" OPENID_CONNECT/google/client_id injected with value(s) from creds.json") + config_file = _replace( + config_file, "OPENID_CONNECT/google/client_id", google_client_id + ) + + open(config_file_path, "w+").write(config_file) + +def inject_creds_into_amanuensis_config(creds_file_path, config_file_path): + creds_file = open(creds_file_path, "r") + creds = json.load(creds_file) + creds_file.close() + + # get secret values from creds.json file + db_host = _get_nested_value(creds, "db_host") + db_username = _get_nested_value(creds, "db_username") + db_password = _get_nested_value(creds, "db_password") + db_database = _get_nested_value(creds, "db_database") + hostname = _get_nested_value(creds, "hostname") + data_delivery_bucket = _get_nested_value(creds, "data_delivery_bucket") + data_delivery_bucket_aws_key_id = _get_nested_value(creds, "data_delivery_bucket_aws_key_id") + data_delivery_bucket_aws_access_key = _get_nested_value(creds, "data_delivery_bucket_aws_access_key") + csl_key = _get_nested_value(creds, "csl_key") + + db_path = "postgresql://{}:{}@{}:5432/{}".format( + db_username, db_password, db_host, db_database + ) + + config_file = open(config_file_path, "r").read() + + print(" DB injected with value(s) from creds.json") + config_file = _replace(config_file, "DB", db_path) + + print(" BASE_URL injected with value(s) from creds.json") + config_file = _replace(config_file, "BASE_URL", "https://{}/amanuensis".format(hostname)) + + print(" HOSTNAME injected with value(s) from creds.json") + config_file = _replace(config_file, "HOSTNAME", "{}".format(hostname)) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_access_key_id injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_access_key_id", data_delivery_bucket_aws_key_id + ) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_secret_access_key injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_secret_access_key", data_delivery_bucket_aws_access_key + ) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/bucket_name injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/bucket_name", data_delivery_bucket + ) + + print(" CSL_KEY injected with value(s) from creds.json") + config_file = _replace( + config_file, "CSL_KEY", csl_key + ) + + # modify USER_API to http://user-service/ if hostname is localhost + + if hostname == "localhost": + print(" USER_API set to http://fence-service/") + config_file = _replace(config_file, "USER_API", "http://fence-service/") + # print(" ENCRYPTION_KEY injected with value(s) from creds.json") + # config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) + + + open(config_file_path, "w+").write(config_file) + + +def set_prod_defaults(config_file_path): + config_file = open(config_file_path, "r").read() + + print( + " CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS set as " + "var/www/fence/fence_google_app_creds_secret.json" + ) + config_file = _replace( + config_file, + "CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS", + "/var/www/fence/fence_google_app_creds_secret.json", + ) + + print( + " CIRRUS_CFG/GOOGLE_STORAGE_CREDS set as " + "var/www/fence/fence_google_storage_creds_secret.json" + ) + config_file = _replace( + config_file, + "CIRRUS_CFG/GOOGLE_STORAGE_CREDS", + "/var/www/fence/fence_google_storage_creds_secret.json", + ) + + print(" INDEXD set as http://indexd-service/") + config_file = _replace(config_file, "INDEXD", "http://indexd-service/") + + print(" ARBORIST set as http://arborist-service/") + config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") + + print(" HTTP_PROXY/host set as cloud-proxy.internal.io") + config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") + + print(" HTTP_PROXY/port set as 3128") + config_file = _replace(config_file, "HTTP_PROXY/port", 3128) + + print(" DEBUG set to false") + config_file = _replace(config_file, "DEBUG", False) + + print(" MOCK_AUTH set to false") + config_file = _replace(config_file, "MOCK_AUTH", False) + + print(" MOCK_GOOGLE_AUTH set to false") + config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) + + print(" AUTHLIB_INSECURE_TRANSPORT set to true") + config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) + + print(" SESSION_COOKIE_SECURE set to true") + config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) + + print(" ENABLE_CSRF_PROTECTION set to true") + config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) + + open(config_file_path, "w+").write(config_file) + +def set_prod_defaults_amanuensis(config_file_path): + config_file = open(config_file_path, "r").read() + + print(" INDEXD set as http://indexd-service/") + config_file = _replace(config_file, "INDEXD", "http://indexd-service/") + + print(" ARBORIST set as http://arborist-service/") + config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") + + print(" HTTP_PROXY/host set as cloud-proxy.internal.io") + config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") + + print(" HTTP_PROXY/port set as 3128") + config_file = _replace(config_file, "HTTP_PROXY/port", 3128) + + print(" DEBUG set to false") + config_file = _replace(config_file, "DEBUG", False) + + print(" MOCK_AUTH set to false") + config_file = _replace(config_file, "MOCK_AUTH", False) + + print(" MOCK_GOOGLE_AUTH set to false") + config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) + + print(" AUTHLIB_INSECURE_TRANSPORT set to true") + config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) + + print(" SESSION_COOKIE_SECURE set to true") + config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) + + print(" ENABLE_CSRF_PROTECTION set to true") + config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) + + open(config_file_path, "w+").write(config_file) + +def inject_other_files_into_fence_config(other_files, config_file_path): + additional_cfgs = _get_all_additional_configs(other_files) + + config_file = open(config_file_path, "r").read() + + for key, value in additional_cfgs.iteritems(): + print(" {} set to {}".format(key, value)) + config_file = _nested_replace(config_file, key, value) + + open(config_file_path, "w+").write(config_file) + + +def _get_all_additional_configs(other_files): + """ + Attempt to parse given list of files and extract configuration variables and values + """ + additional_configs = dict() + for file_path in other_files: + try: + file_ext = file_path.strip().split(".")[-1] + if file_ext == "json": + json_file = open(file_path, "r") + configs = json.load(json_file) + json_file.close() + elif file_ext == "py": + configs = from_pyfile(file_path) + else: + print( + "Cannot load config vars from a file with extention: {}".format( + file_ext + ) + ) + except Exception as exc: + # if there's any issue reading the file, exit + print( + "Error reading {}. Cannot get configuration. Skipping this file. " + "Details: {}".format(other_files, str(exc)) + ) + continue + + if configs: + additional_configs.update(configs) + + return additional_configs + + +def _nested_replace(config_file, key, value, replacement_path=None): + replacement_path = replacement_path or key + try: + for inner_key, inner_value in value.iteritems(): + temp_path = replacement_path + temp_path = temp_path + "/" + inner_key + config_file = _nested_replace( + config_file, inner_key, inner_value, temp_path + ) + except AttributeError: + # not a dict so replace + if value is not None: + config_file = _replace(config_file, replacement_path, value) + + return config_file + + +def _replace(yaml_config, path_to_key, replacement_value, start=0, nested_level=0, key_only=False): + """ + Replace a nested value in a YAML file string with the given value without + losing comments. Uses a regex to do the replacement. + + Args: + yaml_config (str): a string representing a full configuration file + path_to_key (str): nested/path/to/key. The value of this key will be + replaced + replacement_value (str): Replacement value for the key from + path_to_key + """ + nested_path_to_replace = path_to_key.split("/") + + # our regex looks for a specific number of spaces to ensure correct + # level of nesting. It matches to the end of the line + search_string = ( + " " * nested_level + ".*" + nested_path_to_replace[0] + "(')?(\")?:.*\n" + ) + matches = re.search(search_string, yaml_config[start:]) + + # early return if we haven't found anything + if not matches: + return yaml_config + + # if we're on the last item in the path, we need to get the value and + # replace it in the original file + if len(nested_path_to_replace) == 1: + # replace the current key:value with the new replacement value + match_start = start + matches.start(0) + len(" " * nested_level) + match_end = start + matches.end(0) + if not key_only: + yaml_config = ( + yaml_config[:match_start] + + "{}: {}\n".format( + nested_path_to_replace[0], + _get_yaml_replacement_value(replacement_value, nested_level), + ) + + yaml_config[match_end:] + ) + else: + yaml_config = ( + yaml_config[:match_start] + + "{}:\n".format( + _get_yaml_replacement_value(replacement_value, nested_level), + ) + + yaml_config[match_end:] + ) + + return yaml_config + + # set new start point to past current match and move on to next match + start = start + matches.end(0) + nested_level += 1 + del nested_path_to_replace[0] + + return _replace( + yaml_config, + "/".join(nested_path_to_replace), + replacement_value, + start, + nested_level, + key_only=key_only, + ) + + +def from_pyfile(filename, silent=False): + """ + Modeled after flask's ability to load in python files: + https://github.com/pallets/flask/blob/master/flask/config.py + + Some alterations were made but logic is essentially the same + """ + filename = os.path.abspath(filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except IOError as e: + print("Unable to load configuration file ({})".format(e.strerror)) + if silent: + return False + raise + return _from_object(d) + + +def _from_object(obj): + configs = {} + for key in dir(obj): + if key.isupper(): + configs[key] = getattr(obj, key) + return configs + + +def _get_yaml_replacement_value(value, nested_level=0): + if isinstance(value, str): + return "'" + value + "'" + elif isinstance(value, bool): + return str(value).lower() + elif isinstance(value, list) or isinstance(value, set): + output = "" + for item in value: + # spaces for nested level then spaces and hyphen for each list item + output += ( + "\n" + + " " * nested_level + + " - " + + _get_yaml_replacement_value(item) + + "" + ) + return output + else: + return value + + +def _get_nested_value(dictionary, nested_path): + """ + Return a value from a dictionary given a path-like nesting of keys. + + Will default to an empty string if value cannot be found. + + Args: + dictionary (dict): a dictionary + nested_path (str): nested/path/to/key + + Returns: + ?: Value from dict + """ + replacement_value_path = nested_path.split("/") + replacement_value = copy.deepcopy(dictionary) + + for item in replacement_value_path: + replacement_value = replacement_value.get(item, {}) + + if replacement_value == {}: + replacement_value = "" + + return replacement_value + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", + "--creds_file_to_inject", + default="creds.json", + help="creds file to inject into the configuration yaml", + ) + parser.add_argument( + "--other_files_to_inject", + nargs="+", + help="fence_credentials.json, local_settings.py, fence_settings.py file(s) to " + "inject into the configuration yaml", + ) + parser.add_argument( + "-c", "--config_file", default="config.yaml", help="configuration yaml" + ) + args = parser.parse_args() + + if args.config_file == "new-amanuensis-config.yaml": + inject_creds_into_amanuensis_config(args.creds_file_to_inject, args.config_file) + set_prod_defaults_amanuensis(args.config_file) + else: + inject_creds_into_fence_config(args.creds_file_to_inject, args.config_file) + set_prod_defaults(args.config_file) + + if args.other_files_to_inject: + inject_other_files_into_fence_config( + args.other_files_to_inject, args.config_file + ) diff --git a/helm/amanuensis/logo/logo.svg b/helm/amanuensis/logo/logo.svg new file mode 100644 index 000000000..721ee23fa --- /dev/null +++ b/helm/amanuensis/logo/logo.svg @@ -0,0 +1 @@ +fresh diff --git a/helm/amanuensis/scripts/yaml_merge.py b/helm/amanuensis/scripts/yaml_merge.py new file mode 100644 index 000000000..5da248af7 --- /dev/null +++ b/helm/amanuensis/scripts/yaml_merge.py @@ -0,0 +1,56 @@ +import sys +import yaml + +''' +Helper script to merge arbitraly number of yaml files + +Usage: python yaml_merge.py file1.yaml file2.yaml ... amanuensis-config.yaml + +Example: python yaml_merge.py file1.yaml file2.yaml amanuensis-config.yaml +file1.yaml key(s) will overriden by items in file2.yaml if they exist, + +''' +def merge_yaml_files(file_paths): + merged_data = {} + + for file_path in file_paths: + try: + with open(file_path, 'r') as file: + data = yaml.safe_load(file) + merged_data = merge_dicts(merged_data, data) + except FileNotFoundError as e: + print('WARNING! File not found: {}. Will be ignored!'.format(file_path)) + + return merged_data + +def merge_dicts(dict1, dict2): + if dict2 is not None: #Fix AttributeError + for key, value in dict2.items(): + if key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict): + dict1[key] = merge_dicts(dict1[key], value) + else: + dict1[key] = value + + return dict1 + +def save_merged_file(merged_data, output_file_path): + with open(output_file_path, 'w') as output_file: + yaml.dump(merged_data, output_file, default_flow_style=False) + +if __name__ == "__main__": + # Check if at least two arguments are provided (including the script name) + if len(sys.argv) < 3: + print("Usage: python yaml_merge.py config-file1.yaml config-file2.yaml ... amanuensis-config.yaml") + sys.exit(1) + + # Extract input file paths and output file path + input_files = sys.argv[1:-1] + output_file = sys.argv[-1] + + # Merge YAML files + merged_data = merge_yaml_files(input_files) + + # Save the merged data to the output file + save_merged_file(merged_data, output_file) + + print(f"Merged Configuration saved to {output_file}") diff --git a/helm/amanuensis/templates/NOTES.txt b/helm/amanuensis/templates/NOTES.txt new file mode 100644 index 000000000..772641bfe --- /dev/null +++ b/helm/amanuensis/templates/NOTES.txt @@ -0,0 +1,2 @@ +{{ .Chart.Name }} has been deployed successfully. + diff --git a/helm/amanuensis/templates/_helpers.tpl b/helm/amanuensis/templates/_helpers.tpl new file mode 100644 index 000000000..56ac8a8c4 --- /dev/null +++ b/helm/amanuensis/templates/_helpers.tpl @@ -0,0 +1,104 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "amanuensis.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- 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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "amanuensis.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "amanuensis.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "amanuensis.labels" -}} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "amanuensis.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 "amanuensis.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "amanuensis.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* + Postgres Password lookup +*/}} +{{- define "amanuensis.postgres.password" -}} +{{- $localpass := (lookup "v1" "Secret" "postgres" "postgres-postgresql" ) -}} +{{- if $localpass }} +{{- default (index $localpass.data "postgres-password" | b64dec) }} +{{- else }} +{{- default .Values.postgres.password }} +{{- end }} +{{- end }} + + +{{/* + amanuensis Config Secrets Manager Name +*/}} +{{- define "amanuensis-config" -}} +{{- default "amanuensis-config" .Values.externalSecrets.amanuensisConfig }} +{{- end }} + +{{/* + amanuensis should config job run +*/}} +{{- define "amanuensis.shouldRunJob" -}} +{{- $existingSecretConfig := lookup "v1" "Secret" .Release.Namespace (printf "amanuensis-config") }} +{{- $existingSecretCreds := lookup "v1" "Secret" .Release.Namespace (printf "amanuensis-creds") }} +{{- if and + (and $existingSecretConfig $existingSecretConfig.data (hasKey $existingSecretConfig.data "amanuensis-config.yaml")) + (and $existingSecretCreds $existingSecretCreds.data (hasKey $existingSecretCreds.data "creds.json")) +}} + false +{{- else }} + true +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/amanuensis-clear-filter-set-cronjob.yaml b/helm/amanuensis/templates/amanuensis-clear-filter-set-cronjob.yaml new file mode 100644 index 000000000..4065028ab --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-clear-filter-set-cronjob.yaml @@ -0,0 +1,86 @@ +{{- if .Values.amanuensisJobs.clearFilterSetCronjob }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: amanuensis-clear-unused-filter-sets + labels: + redeploy-hash: "{{ .Release.Revision }}" + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "1" + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app: gen3-created-by-hook +spec: + schedule: "0 0 1 * *" + concurrencyPolicy: Forbid + # This is defualt but do we want to keep long term record of deleted filter sets + # the original filter-sets will not be deleted unless manually done by the user + successfulJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + metadata: + labels: + app: gen3job + spec: + automountServiceAccountToken: false + volumes: + - name: yaml-merge + configMap: + name: "amanuensis-yaml-merge" + optional: true + - name: config-volume + secret: + secretName: "amanuensis-config" + items: + - key: amanuensis-config.yaml + path: amanuensis-config.yaml + optional: false + - name: amanuensis-volume + secret: + secretName: "amanuensis-creds" + items: + - key: creds.json + path: creds.json + optional: false + - name: tmp-pod + emptyDir: {} + containers: + - name: amanuensis + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: PYTHONPATH + value: /var/www/amanuensis + - name: AMANUENSIS_PUBLIC_CONFIG + valueFrom: + configMapKeyRef: + name: manifest-amanuensis + key: amanuensis-config-public.yaml + optional: true + volumeMounts: + - name: "config-volume" + readOnly: true + mountPath: "/var/www/amanuensis/amanuensis-config.yaml" + subPath: amanuensis-config.yaml + - name: "yaml-merge" + readOnly: true + mountPath: "/var/www/amanuensis/yaml_merge.py" + subPath: yaml_merge.py + command: ["/bin/bash"] + args: + - "-c" + - | + echo "${AMANUENSIS_PUBLIC_CONFIG:-""}" > "/var/www/amanuensis/amanuensis-config-public.yaml" + python /var/www/amanuensis/yaml_merge.py /var/www/amanuensis/amanuensis-config-public.yaml /var/www/amanuensis/amanuensis-config-secret.yaml /var/www/amanuensis/amanuensis-config.yaml + cd /amanuensis + clear-old-filter-sets + if [[ $? != 0 ]]; then + echo "WARNING: non zero exit code: $?" + exit 1 + fi + restartPolicy: Never + +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/amanuensis-config-public.yaml b/helm/amanuensis/templates/amanuensis-config-public.yaml new file mode 100644 index 000000000..36c3672da --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-config-public.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: manifest-amanuensis +data: + amanuensis-config-public.yaml: | + {{- with .Values.amanuensis_CONFIG_PUBLIC }} + {{- toYaml . | nindent 4 }} + {{ end }} + diff --git a/helm/amanuensis/templates/amanuensis-db-migrate-job.yaml b/helm/amanuensis/templates/amanuensis-db-migrate-job.yaml new file mode 100644 index 000000000..55e89ee40 --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-db-migrate-job.yaml @@ -0,0 +1,81 @@ +{{- if .Values.amanuensisJobs.dbMigrateJob }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: amanuensis-db-migrate + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app: gen3-created-by-hook +spec: + template: + metadata: + labels: + app: gen3job + spec: + automountServiceAccountToken: false + volumes: + - name: yaml-merge + configMap: + name: "amanuensis-yaml-merge" + optional: true + - name: config-volume + secret: + secretName: "amanuensis-config" + items: + - key: amanuensis-config.yaml + path: amanuensis-config.yaml + optional: false + - name: amanuensis-volume + secret: + secretName: "amanuensis-creds" + items: + - key: creds.json + path: creds.json + optional: false + - name: tmp-pod + emptyDir: {} + containers: + - name: amanuensis + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: PYTHONPATH + value: /var/www/amanuensis + - name: AMANUENSIS_PUBLIC_CONFIG + valueFrom: + configMapKeyRef: + name: manifest-amanuensis + key: amanuensis-config-public.yaml + optional: true + volumeMounts: + - name: "config-volume" + readOnly: true + mountPath: "/var/www/amanuensis/amanuensis-config.yaml" + subPath: amanuensis-config.yaml + - name: "yaml-merge" + readOnly: true + mountPath: "/var/www/amanuensis/yaml_merge.py" + subPath: yaml_merge.py + - name: "amanuensis-volume" + readOnly: true + mountPath: "/var/www/amanuensis/creds.json" + subPath: creds.json + command: ["/bin/bash"] + args: + - "-c" + - | + # echo "${AMANUENSIS_PUBLIC_CONFIG:-""}" > "/var/www/amanuensis/amanuensis-config-public.yaml" + # python /var/www/amanuensis/yaml_merge.py /var/www/amanuensis/amanuensis-config-public.yaml /var/www/amanuensis/amanuensis-config-secret.yaml /var/www/amanuensis/amanuensis-config.yaml + cd /amanuensis + fence-create migrate + if [[ $? != 0 ]]; then + echo "WARNING: non zero exit code: $?" + exit 1 + fi + restartPolicy: OnFailure + +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/amanuensis-secret.yaml b/helm/amanuensis/templates/amanuensis-secret.yaml new file mode 100644 index 000000000..ed9076b5d --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-secret.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: amanuensis-secret + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-1" + labels: + app: gen3-created-by-hook +type: Opaque +data: +{{ (.Files.Glob "amanuensis-secret/*").AsSecrets | indent 2 }} +--- + diff --git a/helm/amanuensis/templates/amanuensis-secrets.yaml b/helm/amanuensis/templates/amanuensis-secrets.yaml new file mode 100644 index 000000000..0449672a0 --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-secrets.yaml @@ -0,0 +1,218 @@ +{{- if or (not .Values.global.externalSecrets.deploy) (and .Values.global.externalSecrets.deploy .Values.externalSecrets.createK8sAmanuensisConfigSecret) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: amanuensis-jobs + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-3" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: amanuensis-jobs-role + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-3" +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: amanuensis-jobs-role-binding + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-3" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: amanuensis-jobs-role +subjects: + - kind: ServiceAccount + name: amanuensis-jobs + namespace: default +--- +apiVersion: v1 +kind: Secret +metadata: + name: amanuensis-config + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-2" + labels: + app: gen3-created-by-hook +data: + {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (printf "amanuensis-config")) }} + {{- if and $existingSecret $existingSecret.data (hasKey $existingSecret.data "amanuensis-config.yaml") }} + amanuensis-config.yaml: {{ index $existingSecret.data "amanuensis-config.yaml" | quote }} + {{- else }} + {} + {{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: amanuensis-creds + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-2" + labels: + app: gen3-created-by-hook +data: + {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (printf "amanuensis-creds")) }} + {{- if and $existingSecret $existingSecret.data (hasKey $existingSecret.data "creds.json") }} + creds.json: {{ index $existingSecret.data "creds.json" | quote }} + {{- else }} + {} + {{- end }} +--- +{{- $shouldRunJob := include "amanuensis.shouldRunJob" . | trim }} +{{- if eq $shouldRunJob "true" }} +apiVersion: batch/v1 +kind: Job +metadata: + name: amanuensis-secrets + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-1" + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app: gen3-created-by-hook +spec: + backoffLimit: 0 + template: + metadata: + labels: + app: gen3job + spec: + serviceAccountName: "amanuensis-jobs" + volumes: + - name: shared-data + emptyDir: {} + - name: config-helper + secret: + secretName: "amanuensis-secret" + initContainers: + - name: amanuensis-creds + image: "quay.io/cdis/awshelper:master" + imagePullPolicy: Always + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: amanuensis-dbcreds + key: password + optional: false + - name: PGUSER + valueFrom: + secretKeyRef: + name: amanuensis-dbcreds + key: username + optional: false + - name: PGDB + valueFrom: + secretKeyRef: + name: amanuensis-dbcreds + key: database + optional: false + - name: PGHOST + valueFrom: + secretKeyRef: + name: amanuensis-dbcreds + key: host + optional: false + - name: PGPORT + valueFrom: + secretKeyRef: + name: amanuensis-dbcreds + key: port + optional: false + - name: HOSTNAME + value: "{{ .Values.global.hostname }}" + - name: DATA_DOWNLOAD_BUCKET + value: "{{ .Values.AWS_CREDENTIALS.DATA_DELIVERY_S3_BUCKET.bucket_name }}" + - name: AWS_ACCESS_KEY_ID + value: "{{ .Values.AWS_CREDENTIALS.DATA_DELIVERY_S3_BUCKET.aws_access_key_id }}" + - name: AWS_SECRET_ACCESS_KEY + value: "{{ .Values.AWS_CREDENTIALS.DATA_DELIVERY_S3_BUCKET.aws_secret_access_key }}" + - name: CSL_KEY + value: "{{ .Values.CSL_KEY }}" + volumeMounts: + - name: shared-data + mountPath: /mnt/shared + command: ["/bin/bash"] + args: + - "-c" + - | + cat < /mnt/shared/creds.json + { + "db_host": "${PGHOST}", + "db_username": "${PGUSER}", + "db_password": "${PGPASSWORD}", + "db_database": "${PGDB}", + "hostname": "${HOSTNAME}", + "indexd_password": "", + "data_delivery_bucket": "${DATA_DOWNLOAD_BUCKET}", + "data_delivery_bucket_aws_key_id": "${AWS_ACCESS_KEY_ID}", + "data_delivery_bucket_aws_access_key": "${AWS_SECRET_ACCESS_KEY}", + "csl_key": "${CSL_KEY}" + } + EOF + + kubectl patch secret amanuensis-creds --type='json' -p='[{"op": "add", "path": "/data", "value": {"creds.json": "'$(cat /mnt/shared/creds.json | base64 -w 0)'"}}]' + + - name: create-amanuensis-config + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + volumeMounts: + - name: "config-helper" + readOnly: true + mountPath: "/var/www/amanuensis/config_helper.py" + subPath: config_helper.py + - name: shared-data + mountPath: /mnt/shared + command: ["/bin/bash"] + args: + - "-c" + # Script always succeeds if it runs (echo exits with 0) + - | + echo "generating default amanuensis configuration..." + python /amanuensis/cfg_help.py create --config_path new-amanuensis-config.yaml + + if [[ -f /mnt/shared/creds.json ]]; then + echo "" + echo "injecting creds.json into amanuensis configuration..." + if ! python /var/www/amanuensis/config_helper.py -i /mnt/shared/creds.json -c new-amanuensis-config.yaml; then + echo "ERROR: Failed to inject creds.json into amanuensis configuration!" + exit 1 + fi else + echo "ERROR: /mnt/shared/creds.json not found!" + echo " Only generating default config..." + fi + + cp new-amanuensis-config.yaml /mnt/shared/new-amanuensis-config.yaml + + containers: + - name: create-amanuensis-config-secret + image: "quay.io/cdis/awshelper:master" + imagePullPolicy: Always + volumeMounts: + - name: shared-data + mountPath: /mnt/shared + command: ["/bin/bash"] + args: + - "-c" + - | + if [[ -f /mnt/shared/new-amanuensis-config.yaml ]]; then + # load yaml file into secrets + echo "saving amanuensis configuration into amanuensis-config secret..." + kubectl patch secret amanuensis-config --type='json' -p='[{"op": "add", "path": "/data", "value": {"amanuensis-config.yaml": "'$(cat /mnt/shared/new-amanuensis-config.yaml | base64 -w 0)'"}}]' + fi + + restartPolicy: Never +{{- end }} +--- +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/amanuensis-validate-filter-sets-job.yaml b/helm/amanuensis/templates/amanuensis-validate-filter-sets-job.yaml new file mode 100644 index 000000000..8dac2b62a --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-validate-filter-sets-job.yaml @@ -0,0 +1,105 @@ +{{- if .Values.amanuensisJobs.validateFilterSetsJob }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: amanuensis-validate-filter-sets + labels: + redeploy-hash: "{{ .Release.Revision }}" + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "1" + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app: gen3-created-by-hook +spec: + # Job spec starts here directly (no schedule or jobTemplate needed) + template: + metadata: + labels: + app: gen3job + spec: + automountServiceAccountToken: false + volumes: + - name: config-volume + secret: + secretName: "amanuensis-config" + items: + - key: amanuensis-config.yaml + path: amanuensis-config.yaml + optional: false + - name: es-dd-config-volume + emptyDir: {} + - name: portal-config + secret: + secretName: "portal-config" + - name: invalid-filter-set-volume + emptyDir: {} + initContainers: + - name: amanuensis-db-filter-set-filler + image: "quay.io/pcdc/amanuensis-db-filter-set-filler:0.1.0" + imagePullPolicy: IfNotPresent + env: + - name: invalid_filters_list_path + value: "/var/www/amanuensis/invalid-filters/invalid-filters.json" + volumeMounts: + - name: "config-volume" + readOnly: true + mountPath: "/var/www/amanuensis/amanuensis-config.yaml" + subPath: amanuensis-config.yaml + - name: "invalid-filter-set-volume" + mountPath: "/var/www/amanuensis/invalid-filters/" + - name: create-es-dd-config + image: "quay.io/cdis/awshelper:stable" + imagePullPolicy: IfNotPresent + env: + - name: BASE_URL + value: "https://portal.pedscommons.org/" + - name: OUTPUT_FILE + value: "/tmp/es-dd-config/es_to_dd_map.json" + - name: DICTIONARY_URL + value: "https://portal.pedscommons.org/api/v0/submission/_dictionary/_all" + volumeMounts: + - name: "es-dd-config-volume" + mountPath: "/tmp/es-dd-config" + args: + - /bin/bash + - -c + - | + cd /tmp + export PATH="/home/ubuntu/.local/bin:$PATH" + git clone https://github.com/chicagopcdc/gen3_etl.git + echo "Repository cloned successfully." + cd gen3_etl/elasticsearch + pip install -r requirements-ES-DD.txt + cd etl + python create_es_dd_mapping.py add-manual-fields + echo "ES-DD config created successfully." + containers: + - name: validate-filter-sets + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + volumeMounts: + - name: "es-dd-config-volume" + mountPath: "/var/www/amanuensis/es_to_dd_map.json" + subPath: es_to_dd_map.json + - name: "config-volume" + readOnly: true + mountPath: "/var/www/amanuensis/amanuensis-config.yaml" + subPath: amanuensis-config.yaml + - name: "portal-config" + readOnly: true + mountPath: "/var/www/amanuensis/gitops.json" + subPath: gitops.json + - name: "invalid-filter-set-volume" + mountPath: "/var/www/amanuensis/invalid-filters.json" + subPath: invalid-filters.json + args: + - /bin/bash + - -c + - | + validate-filter-sets + restartPolicy: Never + # Optional: add a backoff limit to control retries + backoffLimit: 3 +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/amanuensis-yaml-merge.yaml b/helm/amanuensis/templates/amanuensis-yaml-merge.yaml new file mode 100644 index 000000000..27b730044 --- /dev/null +++ b/helm/amanuensis/templates/amanuensis-yaml-merge.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: amanuensis-yaml-merge +data: +{{ (.Files.Glob "scripts/*").AsConfig | indent 2 }} \ No newline at end of file diff --git a/helm/amanuensis/templates/aws-config.yaml b/helm/amanuensis/templates/aws-config.yaml new file mode 100644 index 000000000..a49fb9f1b --- /dev/null +++ b/helm/amanuensis/templates/aws-config.yaml @@ -0,0 +1,3 @@ +{{- if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) }} +{{ include "common.awsconfig" . }} +{{- end -}} \ No newline at end of file diff --git a/helm/amanuensis/templates/crossplane.yaml b/helm/amanuensis/templates/crossplane.yaml new file mode 100644 index 000000000..90ec9507b --- /dev/null +++ b/helm/amanuensis/templates/crossplane.yaml @@ -0,0 +1,65 @@ +{{- if .Values.global.crossplane.enabled }} +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Role +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "amanuensis.serviceAccountName" . }}" +spec: + providerConfigRef: + name: provider-aws + forProvider: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "amanuensis.serviceAccountName" . }}" + description: "Role for amanuensis service account for {{ .Values.global.environment }}" + assumeRolePolicyDocument: | + { + "Version":"2012-10-17", + "Statement":[ + { "Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole" }, + { + "Sid":"", + "Effect":"Allow", + "Principal":{"Federated":"arn:aws:iam::{{ .Values.global.crossplane.accountId }}:oidc-provider/{{ .Values.global.crossplane.oidcProviderUrl }}"}, + "Action":"sts:AssumeRoleWithWebIdentity", + "Condition":{ + "StringEquals":{ + "{{ .Values.global.crossplane.oidcProviderUrl }}:sub":"system:serviceaccount:{{ .Release.Namespace }}:{{ include "amanuensis.serviceAccountName" . }}", + "{{ .Values.global.crossplane.oidcProviderUrl }}:aud":"sts.amazonaws.com" + } + } + } + ] + } +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Policy +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-amanuensis-role-policy" +spec: + providerConfigRef: + name: provider-aws + forProvider: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-amanuensis-role-policy" + document: | + { + "Version":"2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":["sqs:SendMessage"], + "Resource":["arn:aws:sqs:{{ .Values.global.aws.region }}:{{ .Values.global.crossplane.accountId }}:{{ .Values.global.environment }}-{{ .Release.Namespace }}-audit-sqs-queue", "arn:aws:sqs:{{ .Values.global.aws.region }}:{{ .Values.global.crossplane.accountId }}:{{ .Values.global.environment }}-ssjdispatcher-sqs-queue"] + } + ] + } +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: RolePolicyAttachment +metadata: + name: "{{ include "amanuensis.serviceAccountName" . }}-{{ .Release.Namespace }}-managed-policy-attachment" +spec: + providerConfigRef: + name: provider-aws + forProvider: + roleName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-amanuensis-sa" + policyArnRef: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-amanuensis-role-policy" +{{- end}} + diff --git a/helm/amanuensis/templates/db-init.yaml b/helm/amanuensis/templates/db-init.yaml new file mode 100644 index 000000000..e43084729 --- /dev/null +++ b/helm/amanuensis/templates/db-init.yaml @@ -0,0 +1,12 @@ +{{ include "common.db-secret" . }} +--- +{{ include "common.db_setup_job" . }} +--- +{{ include "common.db_setup_sa" . }} +--- +{{- if and $.Values.global.externalSecrets.deploy (or $.Values.global.externalSecrets.pushSecret .Values.externalSecrets.pushSecret) }} +--- +{{ include "common.db-push-secret" . }} +--- +{{ include "common.secret.db.bootstrap" . }} +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/deployment.yaml b/helm/amanuensis/templates/deployment.yaml new file mode 100644 index 000000000..8b14daf1e --- /dev/null +++ b/helm/amanuensis/templates/deployment.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: amanuensis-deployment + labels: + {{- include "amanuensis.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "amanuensis.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- $metricsEnabled := .Values.metricsEnabled }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = .Values.global.metricsEnabled }} + {{- end }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = true }} + {{- end }} + + {{- if $metricsEnabled }} + {{- include "common.grafanaAnnotations" . | nindent 8 }} + {{- end }} + labels: + authprovider: "yes" + netnolimit: "yes" + userhelper: "yes" + {{- include "amanuensis.selectorLabels" . | nindent 8 }} + {{- include "common.extraLabels" . | nindent 8 }} + spec: + {{- if .Values.global.topologySpread.enabled }} + {{- include "common.TopologySpread" . | nindent 6 }} + {{- end }} + enableServiceLinks: false + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + serviceAccountName: {{ include "amanuensis.serviceAccountName" . }} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + containers: + - name: amanuensis + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + livenessProbe: + httpGet: + path: /_status + port: http + initialDelaySeconds: 60 + periodSeconds: 60 + timeoutSeconds: 30 + readinessProbe: + httpGet: + path: /_status + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.command }} + command: + {{- toYaml .Values.command | nindent 12 }} + {{- end }} + {{- if .Values.args }} + args: + {{- toYaml .Values.args | nindent 12 }} + {{- end }} + env: + {{- toYaml .Values.env | nindent 12 }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + {{- 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/amanuensis/templates/external-secret.yaml b/helm/amanuensis/templates/external-secret.yaml new file mode 100644 index 000000000..05bfb18c3 --- /dev/null +++ b/helm/amanuensis/templates/external-secret.yaml @@ -0,0 +1,28 @@ +{{ if .Values.global.externalSecrets.deploy }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: amanuensis-config +spec: + refreshInterval: 5m + secretStoreRef: + {{- if ne .Values.global.externalSecrets.clusterSecretStoreRef "" }} + name: {{ .Values.global.externalSecrets.clusterSecretStoreRef }} + kind: ClusterSecretStore + {{- else }} + name: {{include "common.SecretStore" .}} + kind: SecretStore + {{- end }} + target: + name: amanuensis-config + creationPolicy: Owner + data: + - secretKey: amanuensis-config.yaml + remoteRef: + #name of secret in secrets manager + key: {{include "amanuensis-config" .}} +--- +{{- if and .Values.global.externalSecrets.deploy (not .Values.global.externalSecrets.createLocalK8sSecret) }} +{{ include "common.externalSecret.db" . }} +{{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/amanuensis/templates/hpa.yaml b/helm/amanuensis/templates/hpa.yaml new file mode 100644 index 000000000..c3dee2ad8 --- /dev/null +++ b/helm/amanuensis/templates/hpa.yaml @@ -0,0 +1,3 @@ +{{- if default .Values.global.autoscaling.enabled .Values.autoscaling.enabled }} +{{ include "common.hpa" . }} +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/jwt-keys.yaml b/helm/amanuensis/templates/jwt-keys.yaml new file mode 100644 index 000000000..322abbf5b --- /dev/null +++ b/helm/amanuensis/templates/jwt-keys.yaml @@ -0,0 +1,5 @@ +{{include "common.jwt-key-pair-secret" .}} +--- +{{include "common.jwt_public_key_setup_sa" .}} +--- +{{include "common.create_public_key_job" .}} \ No newline at end of file diff --git a/helm/amanuensis/templates/netpolicy.yaml b/helm/amanuensis/templates/netpolicy.yaml new file mode 100644 index 000000000..70a5c3b5d --- /dev/null +++ b/helm/amanuensis/templates/netpolicy.yaml @@ -0,0 +1 @@ +{{ include "common.db_netpolicy" . }} \ No newline at end of file diff --git a/helm/amanuensis/templates/pdb.yaml b/helm/amanuensis/templates/pdb.yaml new file mode 100644 index 000000000..2ef2de13d --- /dev/null +++ b/helm/amanuensis/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/amanuensis/templates/secret-store.yaml b/helm/amanuensis/templates/secret-store.yaml new file mode 100644 index 000000000..771c7760d --- /dev/null +++ b/helm/amanuensis/templates/secret-store.yaml @@ -0,0 +1,3 @@ +{{ if .Values.global.externalSecrets.separateSecretStore }} +{{ include "common.secretstore" . }} +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/templates/service.yaml b/helm/amanuensis/templates/service.yaml new file mode 100644 index 000000000..445c6081c --- /dev/null +++ b/helm/amanuensis/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: amanuensis-service + labels: + {{- include "amanuensis.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "amanuensis.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/helm/amanuensis/templates/serviceaccount.yaml b/helm/amanuensis/templates/serviceaccount.yaml new file mode 100644 index 000000000..d16786c5c --- /dev/null +++ b/helm/amanuensis/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "amanuensis.serviceAccountName" . }} + labels: + {{- include "amanuensis.labels" . | nindent 4 }} + {{- if .Values.global.crossplane.enabled }} + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.global.crossplane.accountId }}:role/{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "amanuensis.serviceAccountName" . }} + {{- else }} + {{ with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/amanuensis/values.yaml b/helm/amanuensis/values.yaml new file mode 100644 index 000000000..6a8c777af --- /dev/null +++ b/helm/amanuensis/values.yaml @@ -0,0 +1,389 @@ +# Default values for audit. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Global configuration +global: + # -- (map) External Secrets settings. + externalSecrets: + # -- (bool) Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override secrets you have deployed. + deploy: false + # -- (bool) Will create the databases and store the creds in Kubernetes Secrets even if externalSecrets is deployed. Useful if you want to use ExternalSecrets for other secrets besides db secrets. + dbCreate: false + # -- (string) Will deploy a separate External Secret Store for this service. + separateSecretStore: false + # -- (string) Will use a manually deployed clusterSecretStore if defined. + clusterSecretStoreRef: "" + + # -- (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: + # -- (map) Local secret setting if using a pre-exising secret. + useLocalSecret: + # -- (bool) Set to true if you would like to use a secret that is already running on your cluster. + enabled: false + # -- (string) Name of the local secret. + localSecretName: + # -- (string) Namespace of the local secret. + localSecretNamespace: + externalSecrets: + # -- (bool) Whether to use External Secrets for aws config. + enabled: false + # -- (String) Name of Secrets Manager secret. + externalSecretAwsCreds: + # -- (bool) Whether the deployment is for development purposes. + dev: true + + postgres: + # -- (bool) Whether the database should be created. + createLocalK8sSecret: false + # -- (string) Name of external secret. Disabled if empty + externalSecret: "" + # -- (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 + # -- (int) Only relevant if tireAccessLevel is set to "regular". Summary charts below this limit will not appear for aggregated data. + tierAccessLimit: "1000" + # -- (map) Controls network policy settings + netPolicy: + enabled: false + # -- (int) Number of dispatcher jobs. + dispatcherJobNum: "10" + # -- (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. + minAvailable: 1 + # -- (map) Kubernetes configuration + crossplane: + # -- (bool) Set to true if deploying to AWS and want to use crossplane for AWS resources. + enabled: false + # -- (string) The name of the crossplane provider config. + providerConfigName: provider-aws + # -- (string) OIDC provider URL. This is used for authentication of roles/service accounts. + oidcProviderUrl: oidc.eks.us-east-1.amazonaws.com/id/12345678901234567890 + # -- (string) The account ID of the AWS account. + accountId: 123456789012 + s3: + # -- (string) The kms key id for the s3 bucket. + kmsKeyId: + # -- (bool) Whether to use s3 bucket versioning. + versioningEnabled: false + # -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + averageCPUValue: 500m + averageMemoryValue: 500Mi + # -- (map) Karpenter topology spread configuration. + topologySpread: + # -- (bool) Whether to enable topology spread constraints for all subcharts that support it. + enabled: false + # -- (string) The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". + topologyKey: "topology.kubernetes.io/zone" + # -- (int) The maxSkew to use for topology spread constraints. Defaults to 1. + maxSkew: 1 +# -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: {} + +# -- (bool) Whether Metrics are enabled. +metricsEnabled: + +# -- (map) External Secrets settings. +externalSecrets: + # -- (string) Will create the Helm "amanuensis-config" secret even if Secrets Manager is enabled. This is helpful if you are wanting to use External Secrets for some, but not all secrets. + createK8sAmanuensisConfigSecret: false + # -- (string) Will override the name of the aws secrets manager secret. Default is "amanuensis-config" + amanuensisConfig: + # -- (bool) Whether to create the database and Secrets Manager secrets via PushSecret. + pushSecret: false + # -- (string) Will override the name of the aws secrets manager secret. Default is "Values.global.environment-.Chart.Name-creds" + dbcreds: + +# -- (map) Secret information for Usersync and External Secrets. +secrets: + # -- (str) AWS access key ID. Overrides global key. + awsAccessKeyId: + # -- (str) AWS access key ID. Overrides global key. + awsSecretAccessKey: + +# -- (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: + # (bool) Whether the database should be restored from s3. Default to global.postgres.dbRestore + dbRestore: false + # -- (bool) Whether the database should be created. Default to global.postgres.dbCreate + dbCreate: + # -- (string) Hostname for postgres server. This is a service override, defaults to global.postgres.host + host: + # -- (string) Database name for postgres. This is a service override, defaults to - + database: + # -- (string) Username for postgres. This is a service override, defaults to - + username: + # -- (string) Port for 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 + +image: + # -- (string) The Docker image repository for the amanuensis service + repository: quay.io/pcdc/amanuensis + # -- (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: [] + +# -- (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: + # -- (bool) Specifies whether a service account should be created. + create: true + # -- (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: + # If not set and create is true, a name is generated using the fullname template + # -- (string) The name of the service account + name: "amanuensis-sa" + +# -- (map) Annotations to add to the pod +podAnnotations: {} + +# -- (map) Security context for the pod +podSecurityContext: + fsGroup: 101 + +# -- (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) 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 memory requested + memory: 128Mi + # -- (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) Node Selector for the pods +nodeSelector: {} + +# -- (list) Tolerations for the pods +tolerations: [] + +# -- (map) Labels to add to the pod. +labels: + # -- (string) Grants egress from all pods to pods labeled with authrpovider=yes. For network policy selectors. + authprovider: "yes" + # -- (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" + +# -- (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: + - amanuensis + # -- (string) Value for topology key label. + topologyKey: "kubernetes.io/hostname" + +# -- (list) Environment variables to pass to the container +env: + - name: GEN3_UWSGI_TIMEOUT + valueFrom: + configMapKeyRef: + name: manifest-global + key: uwsgi-timeout + optional: true + - name: AWS_STS_REGIONAL_ENDPOINTS + value: regional + - name: PYTHONPATH + value: /var/www/amanuensis + - name: GEN3_DEBUG + value: "False" + - name: AMANUENSIS_PUBLIC_CONFIG + valueFrom: + configMapKeyRef: + name: manifest-amanuensis + key: amanuensis-config-public.yaml + optional: true + +# -- (list) Volumes to attach to the container. +volumes: + - name: logo-volume + configMap: + name: "logo-config" + - name: config-volume + secret: + secretName: "amanuensis-config" + - name: amanuensis-volume + secret: + secretName: "amanuensis-creds" + - name: amanuensis-jwt-keys + secret: + secretName: "amanuensis-jwt-keys" + items: + - key: jwt_private_key.pem + path: jwt_private_key.pem + #need to add potentially + - name: yaml-merge + configMap: + name: "amanuensis-yaml-merge" + optional: true + +# -- (list) Volumes to mount to the container. +volumeMounts: + - name: "logo-volume" + readOnly: true + mountPath: "/amanuensis/amanuensis/static/img/logo.svg" + subPath: "logo.svg" + - name: "config-volume" + readOnly: true + mountPath: "/var/www/amanuensis/amanuensis-config.yaml" + subPath: amanuensis-config.yaml + - name: "yaml-merge" + readOnly: true + mountPath: "/var/www/amanuensis/yaml_merge.py" + subPath: yaml_merge.py + # - name: "amanuensis-volume" + # readOnly: true + # mountPath: "/var/www/amanuensis/creds.json" + # subPath: creds.json + - name: "amanuensis-jwt-keys" + readOnly: true + mountPath: "/var/www/amanuensis/jwt_private_key.pem" + subPath: "jwt_private_key.pem" + +# 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: "ProjectRequest" +# -- (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: + +# -- (map) Public configuration settings for Amanuensis app +AMANUENSIS_CONFIG_PUBLIC: {} + +# -- (map) AWS credentials for Amanuensis app +AWS_CREDENTIALS: + # -- (map) AWS SES settings for Amanuensis app + AWS_SES: + # -- (string) Sender email address for Amanuensis app + SENDER: "" + # -- (string) Recipient email address for Amanuensis app + RECIPIENT: "" + # -- (map) AWS S3 bucket settings for Amanuensis app + AWS_REGION: "us-east-1" + # -- (map) Data delivery S3 bucket settings for Amanuensis app + DATA_DELIVERY_S3_BUCKET: + # -- (string) Name of the S3 bucket for data delivery + bucket_name: "" + # -- (string) AWS access key ID for accessing the S3 bucket + aws_access_key_id: "" + # -- (string) AWS secret access key for accessing the S3 bucket + aws_secret_access_key: "" + +# -- (string) CSL key for Amanuensis app +CSL_KEY: "" + +# -- (map) which amanuensis jobs to run +amanuensisJobs: + clearFilterSetCronjob: false + dbMigrateJob: true + validateFilterSetsJob: false + +# -- (list) Override the default Command to run in the container. +command: ["/bin/bash"] + +# -- (list) Default Command and arguments to run in the container. +args: + - "-c" + - | + python /var/www/amanuensis/yaml_merge.py /var/www/amanuensis/amanuensis-config-public.yaml /var/www/amanuensis/amanuensis-config-secret.yaml /var/www/amanuensis/amanuensis-config.yaml + if [[ -f /amanuensis/dockerrun.bash ]]; then bash /amanuensis/dockerrun.bash; elif [[ -f /dockerrun.sh ]]; then bash /dockerrun.sh; else echo 'Error: Neither /amanuensis/dockerrun.bash nor /dockerrun.sh exists.' >&2; exit 1; fi diff --git a/helm/ambassador/README.md b/helm/ambassador/README.md index 5d77229e6..a92312082 100644 --- a/helm/ambassador/README.md +++ b/helm/ambassador/README.md @@ -6,58 +6,58 @@ A Helm chart for deploying ambassador for gen3 ## Requirements -| Repository | Name | Version | -|------------|------|---------| -| file://../common | common | 0.1.28 | +| Repository | Name | Version | +| ---------------- | ------ | ------- | +| file://../common | common | 0.1.28 | ## Values -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | map | `{}` | Affinity to use for the deployment. | -| autoscaling | object | `{}` | | -| 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". | -| fullnameOverride | string | `"ambassador-deployment"` | Override the full name of the deployment. | -| global.autoscaling.averageCPUValue | string | `"500m"` | | -| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | -| global.autoscaling.enabled | bool | `false` | | -| global.autoscaling.maxReplicas | int | `10` | | -| global.autoscaling.minReplicas | int | `1` | | -| 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.minAvailable | 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. | -| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | -| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | -| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | -| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | -| 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. | -| image.tag | string | `"1.4.2"` | Overrides the image tag whose default is the chart appVersion. | -| imagePullSecrets | list | `[]` | Docker image pull secrets. | -| jupyterNamespace | string | `""` | Namespace override to use for Jupyter resources. | -| metricsEnabled | bool | `nil` | Whether Metrics are enabled. | -| 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. | -| 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":{"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 | `{"memory":"100Mi"}` | The amount of resources that the container requests | -| resources.requests.memory | string | `"100Mi"` | The amount of memory requested | -| 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":8877,"type":"ClusterIP"}` | Kubernetes service information. | -| 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 | `""` | 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. | +| Key | Type | Default | Description | +| ------------------------------------- | ------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| affinity | map | `{}` | Affinity to use for the deployment. | +| autoscaling | object | `{}` | | +| 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". | +| fullnameOverride | string | `"ambassador-deployment"` | Override the full name of the deployment. | +| global.autoscaling.averageCPUValue | string | `"500m"` | | +| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | +| global.autoscaling.enabled | bool | `false` | | +| global.autoscaling.maxReplicas | int | `10` | | +| global.autoscaling.minReplicas | int | `1` | | +| 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.minAvailable | 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. | +| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | +| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | +| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | +| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | +| 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. | +| image.tag | string | `"1.4.2"` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| jupyterNamespace | string | `""` | Namespace override to use for Jupyter resources. | +| metricsEnabled | bool | `nil` | Whether Metrics are enabled. | +| 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. | +| 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":{"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 | `{"memory":"100Mi"}` | The amount of resources that the container requests | +| resources.requests.memory | string | `"100Mi"` | The amount of memory requested | +| 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":8877,"targetPort":8080,"type":"ClusterIP"}` | Kubernetes service information. | +| 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 | `""` | 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/deployment.yaml b/helm/ambassador/templates/deployment.yaml index 60b7f578e..ea9884d01 100644 --- a/helm/ambassador/templates/deployment.yaml +++ b/helm/ambassador/templates/deployment.yaml @@ -71,7 +71,7 @@ spec: value: "true" ports: - name: http - containerPort: 8080 + containerPort: {{ .Values.service.targetPort }} - name: https containerPort: 8443 - name: admin diff --git a/helm/ambassador/templates/service.yaml b/helm/ambassador/templates/service.yaml index 8fc57bfe9..b25331452 100644 --- a/helm/ambassador/templates/service.yaml +++ b/helm/ambassador/templates/service.yaml @@ -22,7 +22,7 @@ metadata: spec: ports: - port: 80 - targetPort: 8080 + targetPort: http name: proxy selector: {{- include "ambassador.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/helm/ambassador/values.yaml b/helm/ambassador/values.yaml index fe8e29024..425604ca5 100644 --- a/helm/ambassador/values.yaml +++ b/helm/ambassador/values.yaml @@ -86,6 +86,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 8877 + targetPort: 8080 # -- (string) Namespace to use for user resources. userNamespace: "jupyter-pods" diff --git a/helm/arborist/README.md b/helm/arborist/README.md index 9a5b7dd77..d57c192e4 100644 --- a/helm/arborist/README.md +++ b/helm/arborist/README.md @@ -6,105 +6,105 @@ A Helm chart for gen3 arborist ## Requirements -| Repository | Name | Version | -|------------|------|---------| -| file://../common | common | 0.1.28 | +| Repository | Name | Version | +| ---------------------------------- | ---------- | ------- | +| file://../common | common | 0.1.28 | | https://charts.bitnami.com/bitnami | postgresql | 11.9.13 | ## Values -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | map | `{}` | Affinity rules to apply to the pod | -| autoscaling | object | `{}` | | -| 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". | -| cronjob | bool | `{"enabled":true}` | Whether the arborist rm exipred access cronjob is enabled. | -| 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 | -| externalSecrets | map | `{"dbcreds":null,"pushSecret":false}` | External Secrets settings. | -| externalSecrets.dbcreds | string | `nil` | Will override the name of the aws secrets manager secret. Default is "Values.global.environment-.Chart.Name-creds" | -| externalSecrets.pushSecret | bool | `false` | Whether to create the database and Secrets Manager secrets via PushSecret. | -| fullnameOverride | string | `""` | Override the full name of the deployment. | -| global.autoscaling.averageCPUValue | string | `"500m"` | | -| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | -| global.autoscaling.enabled | bool | `false` | | -| global.autoscaling.maxReplicas | int | `10` | | -| global.autoscaling.minReplicas | int | `1` | | -| global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false,"externalSecrets":{"enabled":false,"externalSecretAwsCreds":null}}` | 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.aws.externalSecrets.enabled | bool | `false` | Whether to use External Secrets for aws config. | -| global.aws.externalSecrets.externalSecretAwsCreds | String | `nil` | Name of Secrets Manager secret. | -| 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.externalSecrets | map | `{"deploy":false,"separateSecretStore":false}` | External Secrets settings. | -| global.externalSecrets.deploy | bool | `false` | Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any arborist secrets you have deployed. | -| global.externalSecrets.separateSecretStore | string | `false` | Will deploy a separate External Secret Store for this 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.minAvailable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | -| global.netPolicy | map | `{"enabled":false}` | Controls network policy settings | -| 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.dbCreate | bool | `true` | Whether the database should be created. | -| global.postgres.externalSecret | string | `""` | Name of external secret. Disabled if empty | -| 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` | -| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | -| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | -| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | -| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | -| 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. | -| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | -| imagePullSecrets | list | `[]` | Docker image pull secrets. | -| metricsEnabled | bool | `nil` | Whether Metrics are enabled. | -| 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","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":{"memory":"512Mi"},"requests":{"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | -| resources.limits | map | `{"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | -| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | -| resources.requests | map | `{"memory":"12Mi"}` | The amount of resources that the container requests | -| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | -| secrets | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null}` | Secret information for External Secrets. | -| secrets.awsAccessKeyId | str | `nil` | AWS access key ID. Overrides global key. | -| secrets.awsSecretAccessKey | str | `nil` | AWS secret access key ID. Overrides global key. | -| 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 | -| 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 | +| Key | Type | Default | Description | +| ------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| affinity | map | `{}` | Affinity rules to apply to the pod | +| autoscaling | object | `{}` | | +| 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". | +| cronjob | bool | `{"enabled":true}` | Whether the arborist rm exipred access cronjob is enabled. | +| 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 | +| externalSecrets | map | `{"dbcreds":null,"pushSecret":false}` | External Secrets settings. | +| externalSecrets.dbcreds | string | `nil` | Will override the name of the aws secrets manager secret. Default is "Values.global.environment-.Chart.Name-creds" | +| externalSecrets.pushSecret | bool | `false` | Whether to create the database and Secrets Manager secrets via PushSecret. | +| fullnameOverride | string | `""` | Override the full name of the deployment. | +| global.autoscaling.averageCPUValue | string | `"500m"` | | +| global.autoscaling.averageMemoryValue | string | `"500Mi"` | | +| global.autoscaling.enabled | bool | `false` | | +| global.autoscaling.maxReplicas | int | `10` | | +| global.autoscaling.minReplicas | int | `1` | | +| global.aws | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null,"enabled":false,"externalSecrets":{"enabled":false,"externalSecretAwsCreds":null}}` | 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.aws.externalSecrets.enabled | bool | `false` | Whether to use External Secrets for aws config. | +| global.aws.externalSecrets.externalSecretAwsCreds | String | `nil` | Name of Secrets Manager secret. | +| 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.externalSecrets | map | `{"deploy":false,"separateSecretStore":false}` | External Secrets settings. | +| global.externalSecrets.deploy | bool | `false` | Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any arborist secrets you have deployed. | +| global.externalSecrets.separateSecretStore | string | `false` | Will deploy a separate External Secret Store for this 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.minAvailable | int | `1` | The minimum amount of pods that are available at all times if the PDB is deployed. | +| global.netPolicy | map | `{"enabled":false}` | Controls network policy settings | +| 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.dbCreate | bool | `true` | Whether the database should be created. | +| global.postgres.externalSecret | string | `""` | Name of external secret. Disabled if empty | +| 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` | +| global.topologySpread | map | `{"enabled":false,"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone"}` | Karpenter topology spread configuration. | +| global.topologySpread.enabled | bool | `false` | Whether to enable topology spread constraints for all subcharts that support it. | +| global.topologySpread.maxSkew | int | `1` | The maxSkew to use for topology spread constraints. Defaults to 1. | +| global.topologySpread.topologyKey | string | `"topology.kubernetes.io/zone"` | The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". | +| 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. | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | Docker image pull secrets. | +| metricsEnabled | bool | `nil` | Whether Metrics are enabled. | +| 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","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":{"memory":"512Mi"},"requests":{"memory":"12Mi"}}` | Resource requests and limits for the containers in the pod | +| resources.limits | map | `{"memory":"512Mi"}` | The maximum amount of resources that the container is allowed to use | +| resources.limits.memory | string | `"512Mi"` | The maximum amount of memory the container can use | +| resources.requests | map | `{"memory":"12Mi"}` | The amount of resources that the container requests | +| resources.requests.memory | string | `"12Mi"` | The amount of memory requested | +| secrets | map | `{"awsAccessKeyId":null,"awsSecretAccessKey":null}` | Secret information for External Secrets. | +| secrets.awsAccessKeyId | str | `nil` | AWS access key ID. Overrides global key. | +| secrets.awsSecretAccessKey | str | `nil` | AWS secret access key ID. Overrides global key. | +| 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,"targetPort":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 | +| 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/arbrorsit-expired-access-cronjob.yaml b/helm/arborist/templates/arbrorsit-expired-access-cronjob.yaml index 12900598e..51c3ca689 100644 --- a/helm/arborist/templates/arbrorsit-expired-access-cronjob.yaml +++ b/helm/arborist/templates/arbrorsit-expired-access-cronjob.yaml @@ -33,12 +33,18 @@ spec: values: - ONDEMAND automountServiceAccountToken: false + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} containers: - name: arborist image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: Always env: + {{- if .Values.env }} {{- toYaml .Values.env | nindent 12 }} + {{- end }} - name: PGPASSWORD valueFrom: secretKeyRef: @@ -77,6 +83,10 @@ spec: name: arborist-dbcreds key: dbcreated optional: false + {{- if .Values.resources }} + resources: + {{- toYaml .Values.resources | nindent 14 }} + {{- end }} command: ["sh"] args: - "-c" diff --git a/helm/arborist/templates/deployment.yaml b/helm/arborist/templates/deployment.yaml index daa62c379..b0d2c2e6e 100644 --- a/helm/arborist/templates/deployment.yaml +++ b/helm/arborist/templates/deployment.yaml @@ -57,7 +57,7 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: @@ -84,7 +84,7 @@ spec: /go/src/github.com/uc-cdis/arborist/migrations/latest # run arborist - /go/src/github.com/uc-cdis/arborist/bin/arborist + /go/src/github.com/uc-cdis/arborist/bin/arborist --port 8080 env: {{- toYaml .Values.env | nindent 12 }} diff --git a/helm/arborist/values.yaml b/helm/arborist/values.yaml index 1bd7574bd..59dea209d 100644 --- a/helm/arborist/values.yaml +++ b/helm/arborist/values.yaml @@ -196,6 +196,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Resource requests and limits for the containers in the pod resources: diff --git a/helm/argo-wrapper/templates/deployment.yaml b/helm/argo-wrapper/templates/deployment.yaml index 80c3b8b7e..44ffdc0be 100644 --- a/helm/argo-wrapper/templates/deployment.yaml +++ b/helm/argo-wrapper/templates/deployment.yaml @@ -59,13 +59,14 @@ spec: livenessProbe: httpGet: path: /test - port: 8000 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 8000 + - containerPort: {{ .Values.service.targetPort }} + name: http protocol: TCP {{- with .Values.volumeMounts }} volumeMounts: diff --git a/helm/argo-wrapper/values.yaml b/helm/argo-wrapper/values.yaml index 0b1facbd7..9d5cc9a31 100644 --- a/helm/argo-wrapper/values.yaml +++ b/helm/argo-wrapper/values.yaml @@ -117,6 +117,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 8000 + targetPort: 80 # -- (map) Configuration for network policies created by this chart. Only relevant if "global.netPolicy.enabled" is set to true netPolicy: diff --git a/helm/audit/README.md b/helm/audit/README.md index 88dab4cd6..5ad6cf5c0 100644 --- a/helm/audit/README.md +++ b/helm/audit/README.md @@ -133,7 +133,7 @@ A Helm chart for Kubernetes | server.sqs.region | string | `"us-east-1"` | SQS queue AWS region. | | server.sqs.url | string | `"http://sqs.com"` | The URL for the SQS queue. | | server.type | string | `"aws_sqs"` | Whether audit should use the api or aws_sqs. | -| service | map | `{"port":80,"type":"ClusterIP"}` | Configuration for the service | +| service | map | `{"port":80,"targetPort":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. | diff --git a/helm/audit/templates/deployment.yaml b/helm/audit/templates/deployment.yaml index 323458ce7..2b557397d 100644 --- a/helm/audit/templates/deployment.yaml +++ b/helm/audit/templates/deployment.yaml @@ -37,6 +37,8 @@ spec: {{- include "common.TopologySpread" . | nindent 6 }} {{- end }} serviceAccountName: {{ include "audit.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: - name: config-volume secret: @@ -49,20 +51,20 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} name: http protocol: TCP livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 80 + port: http resources: {{- toYaml .Values.resources | nindent 12 }} env: diff --git a/helm/audit/values.yaml b/helm/audit/values.yaml index ff9ff283b..7b2f05ba7 100644 --- a/helm/audit/values.yaml +++ b/helm/audit/values.yaml @@ -195,6 +195,7 @@ service: type: ClusterIP # -- (int) Port on which the service is exposed port: 80 + targetPort: 80 # -- (map) Configuration for network policies created by this chart. Only relevant if "global.netPolicy.enabled" is set to true netPolicy: diff --git a/helm/cedar/templates/deployment.yaml b/helm/cedar/templates/deployment.yaml index 906a25f49..3c867e8f8 100644 --- a/helm/cedar/templates/deployment.yaml +++ b/helm/cedar/templates/deployment.yaml @@ -97,19 +97,19 @@ spec: - /src/start.sh ports: - name: http - containerPort: 8000 + containerPort: {{ .Values.service.targetPort }} protocol: TCP readinessProbe: httpGet: path: /_status/ - port: 8000 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 livenessProbe: httpGet: path: /_status/ - port: 8000 + port: http initialDelaySeconds: 60 periodSeconds: 60 timeoutSeconds: 30 diff --git a/helm/cohort-middleware/templates/deployment.yaml b/helm/cohort-middleware/templates/deployment.yaml index 11c9d3761..9c76d647d 100644 --- a/helm/cohort-middleware/templates/deployment.yaml +++ b/helm/cohort-middleware/templates/deployment.yaml @@ -57,7 +57,8 @@ spec: mountPath: /config/development.yaml subPath: development.yaml ports: - - containerPort: 8080 + - containerPort: {{ .Values.service.targetPort }} + name: http livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: diff --git a/helm/cohort-middleware/templates/service.yaml b/helm/cohort-middleware/templates/service.yaml index c40a19239..3a945e8fa 100644 --- a/helm/cohort-middleware/templates/service.yaml +++ b/helm/cohort-middleware/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: 8080 + targetPort: http protocol: TCP name: http selector: diff --git a/helm/cohort-middleware/values.yaml b/helm/cohort-middleware/values.yaml index a5262858f..c319695a8 100644 --- a/helm/cohort-middleware/values.yaml +++ b/helm/cohort-middleware/values.yaml @@ -144,6 +144,7 @@ service: type: ClusterIP # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports port: 80 + targetPort: 8080 # This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ ingress: diff --git a/helm/common/templates/_db_setup_job.tpl b/helm/common/templates/_db_setup_job.tpl index f8746b1b2..4777e3c82 100644 --- a/helm/common/templates/_db_setup_job.tpl +++ b/helm/common/templates/_db_setup_job.tpl @@ -44,6 +44,12 @@ spec: app: gen3job spec: serviceAccountName: {{ .Chart.Name }}-dbcreate-sa + {{- if $.Values.podSecurityContext }} + securityContext: + {{- range $k, $v := $.Values.podSecurityContext }} + {{ $k }}: {{ $v }} + {{- end }} + {{- end }} restartPolicy: Never containers: - name: db-setup @@ -51,12 +57,14 @@ spec: image: quay.io/cdis/awshelper:master imagePullPolicy: Always command: ["/bin/bash", "-c"] + resources: + {{- toYaml .Values.resources | nindent 12 }} env: - name: PGPASSWORD {{- if $.Values.global.dev }} valueFrom: secretKeyRef: - name: {{ .Release.Name }}-postgresql + name: {{ .Values.postgres.host | default (printf "%s-postgresql" .Release.Name) }} key: postgres-password optional: false {{- else if $.Values.global.postgres.externalSecret }} @@ -66,7 +74,7 @@ spec: key: password optional: false {{- else }} - value: {{ .Values.global.postgres.master.password | quote}} + value: {{ .Values.global.postgres.master.password | quote }} {{- end }} - name: PGUSER {{- if $.Values.global.postgres.externalSecret }} @@ -90,7 +98,7 @@ spec: {{- end }} - name: PGHOST {{- if $.Values.global.dev }} - value: "{{ .Release.Name }}-postgresql" + value: {{ .Values.postgres.host | default (printf "%s-postgresql" .Release.Name) }} {{- else if $.Values.global.postgres.externalSecret }} valueFrom: secretKeyRef: @@ -125,8 +133,8 @@ spec: #!/bin/bash set -e - source "${GEN3_HOME}/gen3/lib/utils.sh" - gen3_load "gen3/gen3setup" + #source "${GEN3_HOME}/gen3/lib/utils.sh" + #gen3_load "gen3/gen3setup" echo "PGHOST=$PGHOST" echo "PGPORT=$PGPORT" @@ -143,21 +151,27 @@ spec: >&2 echo "Postgres is up - executing command" if psql -lqt | cut -d \| -f 1 | grep -qw $SERVICE_PGDB; then - gen3_log_info "Database exists" + #gen3_log_info "Database exists" PGPASSWORD=$SERVICE_PGPASS psql -d $SERVICE_PGDB -h $PGHOST -p $PGPORT -U $SERVICE_PGUSER -c "\conninfo" - - # Update secret to signal that db is ready, and services can start kubectl patch secret/{{ .Chart.Name }}-dbcreds -p '{"data":{"dbcreated":"dHJ1ZQo="}}' else - echo "database does not exist" - psql -tc "SELECT 1 FROM pg_database WHERE datname = '$SERVICE_PGDB'" | grep -q 1 || psql -c "CREATE DATABASE \"$SERVICE_PGDB\";" - gen3_log_info psql -tc "SELECT 1 FROM pg_user WHERE usename = '$SERVICE_PGUSER'" | grep -q 1 || psql -c "CREATE USER \"$SERVICE_PGUSER\" WITH PASSWORD '$SERVICE_PGPASS';" - psql -tc "SELECT 1 FROM pg_user WHERE usename = '$SERVICE_PGUSER'" | grep -q 1 || psql -c "CREATE USER \"$SERVICE_PGUSER\" WITH PASSWORD '$SERVICE_PGPASS';" - psql -c "GRANT ALL ON DATABASE \"$SERVICE_PGDB\" TO \"$SERVICE_PGUSER\" WITH GRANT OPTION;" - psql -d $SERVICE_PGDB -c "CREATE EXTENSION ltree; ALTER ROLE \"$SERVICE_PGUSER\" WITH LOGIN" - PGPASSWORD=$SERVICE_PGPASS psql -d $SERVICE_PGDB -h $PGHOST -p $PGPORT -U $SERVICE_PGUSER -c "\conninfo" + echo "Database does not exist — creating..." + psql -tc "SELECT 1 FROM pg_database WHERE datname = '$SERVICE_PGDB'" | grep -q 1 || \ + psql -c "CREATE DATABASE \"$SERVICE_PGDB\";" + psql -tc "SELECT 1 FROM pg_user WHERE usename = '$SERVICE_PGUSER'" | grep -q 1 || \ + psql -c "CREATE USER \"$SERVICE_PGUSER\" WITH PASSWORD '$SERVICE_PGPASS';" + + echo "Granting privileges to $SERVICE_PGUSER..." + psql -c "GRANT ALL PRIVILEGES ON DATABASE \"$SERVICE_PGDB\" TO \"$SERVICE_PGUSER\";" + psql -d $SERVICE_PGDB -c "ALTER SCHEMA public OWNER TO \"$SERVICE_PGUSER\";" + psql -d $SERVICE_PGDB -c "GRANT ALL ON SCHEMA public TO \"$SERVICE_PGUSER\";" + psql -d $SERVICE_PGDB -c "GRANT ALL ON ALL TABLES IN SCHEMA public TO \"$SERVICE_PGUSER\";" + psql -d $SERVICE_PGDB -c "ALTER ROLE \"$SERVICE_PGUSER\" WITH LOGIN;" + + echo "Creating ltree extension..." + psql -d $SERVICE_PGDB -c "CREATE EXTENSION IF NOT EXISTS ltree;" - # Update secret to signal that db has been created, and services can start + PGPASSWORD=$SERVICE_PGPASS psql -d $SERVICE_PGDB -h $PGHOST -p $PGPORT -U $SERVICE_PGUSER -c "\conninfo" kubectl patch secret/{{ .Chart.Name }}-dbcreds -p '{"data":{"dbcreated":"dHJ1ZQo="}}' fi {{- end }} @@ -174,15 +188,32 @@ apiVersion: v1 kind: Secret metadata: name: {{ $.Chart.Name }}-dbcreds + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + labels: + app: gen3-created-by-hook data: - database: {{ ( $.Values.postgres.database | default (printf "%s_%s" $.Chart.Name $.Release.Name) ) | b64enc | quote}} - username: {{ ( $.Values.postgres.username | default (printf "%s_%s" $.Chart.Name $.Release.Name) ) | b64enc | quote}} - port: {{ $.Values.postgres.port | b64enc | quote }} - password: {{ include "gen3.service-postgres" (dict "key" "password" "service" $.Chart.Name "context" $) | b64enc | quote }} - {{- if $.Values.global.dev }} - host: {{ (printf "%s-%s" $.Release.Name "postgresql" ) | b64enc | quote }} + {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (printf "%s-dbcreds" .Chart.Name)) }} + {{- if $existingSecret }} + database: {{ index $existingSecret.data "database" | quote }} + username: {{ index $existingSecret.data "username" | quote }} + port: {{ index $existingSecret.data "port" | quote }} + password: {{ index $existingSecret.data "password" | quote }} + host: {{ index $existingSecret.data "host" | quote }} + {{- if index $existingSecret.data "dbcreated" }} + dbcreated: {{ index $existingSecret.data "dbcreated" | quote }} + {{- end }} {{- else }} - host: {{ ( $.Values.postgres.host | default ( $.Values.global.postgres.master.host)) | b64enc | quote }} + database: {{ ( $.Values.postgres.database | default (printf "%s_%s" $.Chart.Name $.Release.Name) ) | b64enc | quote }} + username: {{ ( $.Values.postgres.username | default (printf "%s_%s" $.Chart.Name $.Release.Name) ) | b64enc | quote }} + port: {{ $.Values.postgres.port | b64enc | quote }} + password: {{ include "gen3.service-postgres" (dict "key" "password" "service" $.Chart.Name "context" $) | b64enc | quote }} + {{- if $.Values.global.dev }} + host: {{ ($.Values.postgres.host | default (printf "%s-%s" $.Release.Name "postgresql") ) | b64enc | quote }} + {{- else }} + host: {{ ( $.Values.postgres.host | default ( $.Values.global.postgres.master.host)) | b64enc | quote }} + {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/helm/common/templates/_jwt_key_pairs.tpl b/helm/common/templates/_jwt_key_pairs.tpl new file mode 100644 index 000000000..61c2ba834 --- /dev/null +++ b/helm/common/templates/_jwt_key_pairs.tpl @@ -0,0 +1,112 @@ +{{- define "common.jwt_public_key_setup_sa" -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Chart.Name }}-jwt-public-key-patch-sa + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Chart.Name }}-jwt-public-key-patch-role +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["*"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Chart.Name }}-jwt-public-key-patch-rolebinding +subjects: +- kind: ServiceAccount + name: {{ .Chart.Name }}-jwt-public-key-patch-sa + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ .Chart.Name }}-jwt-public-key-patch-role + apiGroup: rbac.authorization.k8s.io +{{- end }} + +--- + +{{- define "common.create_public_key_job" -}} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-jwt-keys" .Chart.Name) }} +{{- $shouldRunJob := true }} +{{- if and $existingSecret (index $existingSecret.data "jwt_public_key.pem") }} + {{- $shouldRunJob = false }} +{{- end }} + +{{- if $shouldRunJob }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Chart.Name }}-create-public-key-{{ .Release.Revision }} + labels: + app: gen3job +spec: + template: + spec: + serviceAccountName: {{ .Chart.Name }}-jwt-public-key-patch-sa + containers: + - name: public-key-gen + image: bitnamisecure/kubectl:latest + env: + - name: PRIVATE_KEY_PEM + valueFrom: + secretKeyRef: + name: {{ .Chart.Name }}-jwt-keys + key: jwt_private_key.pem + optional: false + - name: SERVICE + value: {{ .Chart.Name }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + command: ["/bin/sh", "-c"] + args: + - | + set -e + + echo "SERVICE=$SERVICE" + + # Read the private key from the secret + private_key=$(kubectl get secret $SERVICE-jwt-keys -o jsonpath='{.data.jwt_private_key\.pem}' | base64 --decode) + + # Create a temporary file for the private key + echo "${private_key}" > /tmp/private_key.pem + + # Generate the public key from the private key + openssl rsa -in /tmp/private_key.pem -pubout -out /tmp/public_key.pem + + # Base64 encode the public key + public_key=$(base64 -w 0 /tmp/public_key.pem) + + # Update the secret with the public key + kubectl patch secret $SERVICE-jwt-keys -p="{\"data\": {\"jwt_public_key.pem\": \"${public_key}\"}}" + + restartPolicy: OnFailure +{{- end }} +{{- end }} + +--- + +{{- define "common.jwt-key-pair-secret" -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $.Chart.Name }}-jwt-keys +type: Opaque +data: + {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (printf "%s-jwt-keys" .Chart.Name)) }} + {{- if $existingSecret }} + # Secret exists - don't regenerate the keys + jwt_private_key.pem: {{ index $existingSecret.data "jwt_private_key.pem" | quote }} + jwt_public_key.pem: {{ index $existingSecret.data "jwt_public_key.pem" | quote }} + {{- else }} + # Secret doesn't exist yet - generate a new key + jwt_private_key.pem: {{ genPrivateKey "rsa" | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/common/templates/_postgres_secrets.tpl b/helm/common/templates/_postgres_secrets.tpl index c256b10f7..4c086af1d 100644 --- a/helm/common/templates/_postgres_secrets.tpl +++ b/helm/common/templates/_postgres_secrets.tpl @@ -17,7 +17,7 @@ */}} {{- define "gen3.service-postgres" -}} - {{- $chartName := default "" .context.Chart.Name }} + {{- $chartName := default .context.Chart.Name $.service }} {{- $valuesPostgres := get .context.Values.postgres .key }} {{- $localSecretPass := "" }} {{- $secretData := (lookup "v1" "Secret" $.context.Release.Namespace (printf "%s-%s" $chartName "dbcreds")).data }} diff --git a/helm/dicom-server/templates/deployment.yaml b/helm/dicom-server/templates/deployment.yaml index 3a51512e8..d839f580b 100644 --- a/helm/dicom-server/templates/deployment.yaml +++ b/helm/dicom-server/templates/deployment.yaml @@ -51,19 +51,20 @@ spec: readinessProbe: httpGet: path: /system - port: 8042 + port: http initialDelaySeconds: 5 periodSeconds: 20 timeoutSeconds: 30 livenessProbe: httpGet: path: /system - port: 8042 + port: http initialDelaySeconds: 5 periodSeconds: 60 timeoutSeconds: 30 ports: - - containerPort: 8042 + - containerPort: {{ .Values.service.targetPort }} + name: http env: - name: PGHOST valueFrom: diff --git a/helm/etl/templates/etl-job.yaml b/helm/etl/templates/etl-job.yaml index 9c6199a72..3d2187ffa 100644 --- a/helm/etl/templates/etl-job.yaml +++ b/helm/etl/templates/etl-job.yaml @@ -36,6 +36,8 @@ spec: operator: In values: - ONDEMAND + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 12 }} volumes: {{- if .Values.legacySupport }} - name: config-volume @@ -111,6 +113,12 @@ spec: ports: - containerPort: 80 env: + {{- with .Values.env }} + {{- range $key, $value := . }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} - name: DB_HOST valueFrom: secretKeyRef: diff --git a/helm/etl/values.yaml b/helm/etl/values.yaml index cd20d7e80..027e052e1 100644 --- a/helm/etl/values.yaml +++ b/helm/etl/values.yaml @@ -47,6 +47,8 @@ resources: esEndpoint: gen3-elasticsearch-master +env: + etlMapping: mappings: - name: dev_case @@ -156,3 +158,17 @@ suspendCronjob: true legacySupport: false etlForced: "TRUE" + +# -- (map) Security context for the pod +podSecurityContext: {} + +# -- (map) Security context for the containers in the pod +securityContext: + {} + + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 diff --git a/helm/fence/templates/fence-delete-expired-clients-cron.yaml b/helm/fence/templates/fence-delete-expired-clients-cron.yaml index de3c214df..cb2fcdcbb 100644 --- a/helm/fence/templates/fence-delete-expired-clients-cron.yaml +++ b/helm/fence/templates/fence-delete-expired-clients-cron.yaml @@ -18,45 +18,44 @@ spec: labels: app: gen3job spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 12 }} volumes: - - name: yaml-merge - configMap: - name: "fence-yaml-merge" - - name: config-volume - secret: - secretName: "fence-config" + {{- toYaml .Values.volumes | nindent 12 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} containers: - name: fence image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: Always env: - - name: FENCE_PUBLIC_CONFIG - valueFrom: - configMapKeyRef: - name: manifest-fence - key: fence-config-public.yaml - optional: true - - name: slackWebHook - valueFrom: - secretKeyRef: - name: slack-webhook - key: slack_webhook - optional: true - {{- toYaml .Values.env | nindent 16 }} + {{- if .Values.env }} + {{- toYaml .Values.env | nindent 14 }} + {{- end }} + - name: FENCE_PUBLIC_CONFIG + valueFrom: + configMapKeyRef: + name: manifest-fence + key: fence-config-public.yaml + optional: true + - name: slackWebHook + valueFrom: + secretKeyRef: + name: slack-webhook + key: slack_webhook + optional: true + volumeMounts: - - name: "config-volume" - readOnly: true - mountPath: "/var/www/fence/fence-config-secret.yaml" - subPath: fence-config.yaml - - name: "yaml-merge" - readOnly: true - mountPath: "/var/www/fence/yaml_merge.py" - subPath: yaml_merge.py + {{- toYaml .Values.initVolumeMounts | nindent 14 }} + resources: + {{- toYaml .Values.resources | nindent 14 }} command: ["/bin/bash"] args: - "-c" - | - python /var/www/fence/yaml_merge.py /var/www/fence/fence-config-public.yaml /var/www/fence/fence-config-secret.yaml /var/www/fence/fence-config.yaml + python /var/www/fence/yaml_merge.py /var/www/fence/fence-config-secret.yaml /var/www/fence/fence-config-public.yaml /var/www/fence/fence-config.yaml if [[ "$slackWebHook" =~ ^http ]]; then fence-create client-delete-expired --slack-webhook $slackWebHook --warning-days 7 else diff --git a/helm/fence/templates/fence-deployment.yaml b/helm/fence/templates/fence-deployment.yaml index 4f77869bf..6f2b4ae29 100644 --- a/helm/fence/templates/fence-deployment.yaml +++ b/helm/fence/templates/fence-deployment.yaml @@ -42,15 +42,21 @@ spec: {{- end }} enableServiceLinks: false serviceAccountName: {{ include "fence.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: {{- toYaml .Values.volumes | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: fence image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP - name: https containerPort: 443 @@ -88,16 +94,6 @@ spec: - name: fence-init image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: 80 - protocol: TCP - - name: https - containerPort: 443 - protocol: TCP - - name: container - containerPort: 6567 - protocol: TCP resources: {{- toYaml .Values.resources | nindent 12 }} command: ["/bin/bash"] diff --git a/helm/fence/templates/jwt-keys.yaml b/helm/fence/templates/jwt-keys.yaml index 06d10f288..3724b85a7 100644 --- a/helm/fence/templates/jwt-keys.yaml +++ b/helm/fence/templates/jwt-keys.yaml @@ -1,9 +1,7 @@ {{- if or (not .Values.global.externalSecrets.deploy) (and .Values.global.externalSecrets.deploy .Values.externalSecrets.createK8sJwtKeysSecret) }} -apiVersion: v1 -kind: Secret -metadata: - name: fence-jwt-keys -type: Opaque -data: - jwt_private_key.pem: {{ include "getOrCreatePrivateKey" . }} +{{include "common.jwt-key-pair-secret" .}} +--- +{{include "common.jwt_public_key_setup_sa" .}} +--- +{{include "common.create_public_key_job" .}} {{- end }} \ No newline at end of file diff --git a/helm/fence/templates/presigned-url-fence.yaml b/helm/fence/templates/presigned-url-fence.yaml index 4244e3c4c..cef6b3139 100644 --- a/helm/fence/templates/presigned-url-fence.yaml +++ b/helm/fence/templates/presigned-url-fence.yaml @@ -38,15 +38,21 @@ spec: userhelper: "yes" spec: serviceAccountName: {{ include "fence.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: {{- toYaml .Values.volumes | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: presigned-url-fence image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: Always + imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP - name: https containerPort: 443 diff --git a/helm/fence/templates/useryaml-job.yaml b/helm/fence/templates/useryaml-job.yaml index c8a65b624..be11da813 100644 --- a/helm/fence/templates/useryaml-job.yaml +++ b/helm/fence/templates/useryaml-job.yaml @@ -25,10 +25,16 @@ spec: - name: useryaml configMap: name: useryaml + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: fence image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: Always + imagePullPolicy: {{ .Values.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} env: {{- toYaml .Values.env | nindent 10 }} volumeMounts: diff --git a/helm/fence/values.yaml b/helm/fence/values.yaml index 0e414666b..1c210eba9 100644 --- a/helm/fence/values.yaml +++ b/helm/fence/values.yaml @@ -268,8 +268,8 @@ serviceAccount: podAnnotations: {} # -- (map) Security context for the pod -podSecurityContext: - fsGroup: 101 +podSecurityContext: {} + # fsGroup: 101 # -- (map) Security context for the containers in the pod securityContext: @@ -287,6 +287,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Resource requests and limits for the containers in the pod resources: @@ -391,6 +392,7 @@ env: secretKeyRef: name: indexd-service-creds key: fence + optional: true - name: gen3Env valueFrom: configMapKeyRef: @@ -434,6 +436,12 @@ volumes: - name: yaml-merge configMap: name: "fence-yaml-merge" + - name: amanuensis-jwt-keys + secret: + secretName: "amanuensis-jwt-keys" + items: + - key: jwt_public_key.pem + path: jwt_public_key.pem optional: false - name: config-volume-public configMap: @@ -486,6 +494,10 @@ volumeMounts: readOnly: true mountPath: "/fence/keys/key/jwt_private_key.pem" subPath: "jwt_private_key.pem" + - name: "amanuensis-jwt-keys" + readOnly: true + mountPath: "/amanuensis/jwt_public_key.pem" + subPath: "jwt_public_key.pem" - name: "config-volume-public" readOnly: true mountPath: "/var/www/fence/fence-config-public.yaml" diff --git a/helm/gearbox-middleware/.helmignore b/helm/gearbox-middleware/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/gearbox-middleware/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/gearbox-middleware/Chart.yaml b/helm/gearbox-middleware/Chart.yaml new file mode 100644 index 000000000..400358bec --- /dev/null +++ b/helm/gearbox-middleware/Chart.yaml @@ -0,0 +1,32 @@ +apiVersion: v2 +name: gearbox-middleware +description: A Helm chart for Kubernetes + +# 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.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" + +dependencies: + - name: common + version: 0.1.28 + repository: file://../common + - name: gearbox + version: 0.1.0 + repository: file://../gearbox diff --git a/helm/gearbox-middleware/templates/NOTES.txt b/helm/gearbox-middleware/templates/NOTES.txt new file mode 100644 index 000000000..c1e7e1aef --- /dev/null +++ b/helm/gearbox-middleware/templates/NOTES.txt @@ -0,0 +1 @@ +{{ .Chart.Name }} has been deployed successfully. diff --git a/helm/gearbox-middleware/templates/_helpers.tpl b/helm/gearbox-middleware/templates/_helpers.tpl new file mode 100644 index 000000000..80f399786 --- /dev/null +++ b/helm/gearbox-middleware/templates/_helpers.tpl @@ -0,0 +1,67 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gearbox-middleware.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- 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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "gearbox-middleware.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "gearbox-middleware.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +Common labels +*/}} +{{- define "gearbox-middleware.labels" -}} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gearbox-middleware.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 "gearbox-middleware.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default "gearbox-middleware-sa" .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/gearbox-middleware/templates/aws-config.yaml b/helm/gearbox-middleware/templates/aws-config.yaml new file mode 100644 index 000000000..a49fb9f1b --- /dev/null +++ b/helm/gearbox-middleware/templates/aws-config.yaml @@ -0,0 +1,3 @@ +{{- if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) }} +{{ include "common.awsconfig" . }} +{{- end -}} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/crossplane.yaml b/helm/gearbox-middleware/templates/crossplane.yaml new file mode 100644 index 000000000..d8913be95 --- /dev/null +++ b/helm/gearbox-middleware/templates/crossplane.yaml @@ -0,0 +1,101 @@ +{{- if .Values.global.crossplane.enabled }} +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Role +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox-middleware.serviceAccountName" . }}" +spec: + providerConfigRef: + name: provider-aws + forProvider: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox-middleware.serviceAccountName" . }}" + description: "Role for gearbox-middleware service account for {{ .Values.global.environment }}" + assumeRolePolicyDocument: | + { + "Version":"2012-10-17", + "Statement":[ + { "Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole" }, + { + "Sid":"", + "Effect":"Allow", + "Principal":{"Federated":"arn:aws:iam::{{ .Values.global.crossplane.accountId }}:oidc-provider/{{ .Values.global.crossplane.oidcProviderUrl }}"}, + "Action":"sts:AssumeRoleWithWebIdentity", + "Condition":{ + "StringEquals":{ + "{{ .Values.global.crossplane.oidcProviderUrl }}:sub":"system:serviceaccount:{{ .Release.Namespace }}:{{ include "gearbox-middleware.serviceAccountName" . }}", + "{{ .Values.global.crossplane.oidcProviderUrl }}:aud":"sts.amazonaws.com" + } + } + } + ] + } +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Policy +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-role-policy" +spec: + providerConfigRef: + name: provider-aws + forProvider: + roleName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox-middleware.serviceAccountName" . }}" + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-role-policy" + document: | + { + "Version":"2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":["s3:List*","s3:Get*"], + "Resource":[ + "arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-bucket/*", + "arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-bucket" + ] + }, + { + "Effect":"Allow", + "Action":["s3:PutObject","s3:GetObject","s3:DeleteObject"], + "Resource":"arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-bucket/*" + } + ] + } + +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: RolePolicyAttachment +metadata: + name: "{{ include "gearbox-middleware.serviceAccountName" . }}-{{ .Release.Namespace }}-managed-policy-attachment" +spec: + providerConfigRef: + name: provider-aws + forProvider: + roleName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox-middleware.serviceAccountName" . }}" + policyArnRef: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-role-policy" +--- +apiVersion: s3.aws.crossplane.io/v1beta1 +kind: Bucket +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-bucket" +spec: + providerConfigRef: + name: provider-aws + forProvider: + bucketName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-middleware-bucket" + acl: private + forceDestroy: false + locationConstraint: {{ .Values.global.aws.region }} + tags: + Organization: gen3 + description: Created by crossplane + versioningConfiguration: + {{- if .Values.global.crossplane.s3.versioningEnabled }} + status: "Enabled" + {{- end }} + serverSideEncryptionConfiguration: + rules: + - applyServerSideEncryptionByDefault: + sseAlgorithm: aws:kms + {{- if .Values.global.crossplane.s3.kmsKeyId }} + kmsMasterKeyID: {{ .Values.global.crossplane.s3.kmsKeyId }} + {{- end }} +{{- end}} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/deployment.yaml b/helm/gearbox-middleware/templates/deployment.yaml new file mode 100644 index 000000000..a7e806b89 --- /dev/null +++ b/helm/gearbox-middleware/templates/deployment.yaml @@ -0,0 +1,117 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gearbox-middleware-deployment + labels: + {{- include "gearbox-middleware.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "gearbox-middleware.selectorLabels" . | nindent 6 }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + strategy: + {{- toYaml .Values.strategy | nindent 8 }} + template: + metadata: + labels: + public: "yes" + s3: "yes" + userhelper: "yes" + {{- include "gearbox-middleware.selectorLabels" . | nindent 8 }} + {{- include "common.extraLabels" . | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/gearbox-middleware-creds.yaml") . | sha256sum }} + {{- $metricsEnabled := .Values.metricsEnabled }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = .Values.global.metricsEnabled }} + {{- end }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = true }} + {{- end }} + + {{- if $metricsEnabled }} + {{- include "common.grafanaAnnotations" . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.topologySpread.enabled }} + {{- include "common.TopologySpread" . | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "gearbox-middleware.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken}} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds}} + containers: + - name: gearbox-middleware + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- toYaml .Values.env | nindent 12 }} + {{- if and .Values.gearboxMiddlewareG3auto.awsaccesskey .Values.gearboxMiddlewareG3auto.awssecretkey }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: gearbox-middleware-g3auto + key: aws_access_key_id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: gearbox-middleware-g3auto + key: aws_secret_access_key + {{- else if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) (.Values.global.aws.useLocalSecret.enabled) }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + {{- if .Values.global.aws.useLocalSecret.enabled }} + name: {{ .Values.global.aws.useLocalSecret.localSecretName }} + {{- else }} + name: {{.Chart.Name}}-aws-config + {{- end }} + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + {{- if .Values.global.aws.useLocalSecret.enabled }} + name: {{ .Values.global.aws.useLocalSecret.localSecretName }} + {{- else }} + name: {{.Chart.Name}}-aws-config + {{- end }} + key: secret-access-key + {{- end }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + livenessProbe: + httpGet: + path: /_status + port: http + initialDelaySeconds: 10 + periodSeconds: 60 + timeoutSeconds: 30 + readinessProbe: + httpGet: + path: /_status + port: http + initContainers: + - name: wait-for-gearbox + image: curlimages/curl:latest + command: ["/bin/sh","-c"] + args: ["while [ $(curl -sw '%{http_code}' http://gearbox-service/_status -o /dev/null) -ne 200 ]; do sleep 5; echo 'Waiting for gearbox...'; done"] \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/external-secret.yaml b/helm/gearbox-middleware/templates/external-secret.yaml new file mode 100644 index 000000000..4896a1df9 --- /dev/null +++ b/helm/gearbox-middleware/templates/external-secret.yaml @@ -0,0 +1,19 @@ +{{- if and (.Values.global.externalSecrets.deploy) (not .Values.externalSecrets.createK8sgearboxMiddlewareSecret) }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gearbox-middleware-g3auto +spec: + refreshInterval: 5m + secretStoreRef: + name: {{include "common.SecretStore" .}} + kind: SecretStore + target: + name: gearbox-middleware-g3auto + creationPolicy: Owner + data: + - secretKey: config.json + remoteRef: + #name of secret in secrets manager + key: {{include "gearbox-middleware-g3auto" .}} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/gearbox-middleware-creds.yaml b/helm/gearbox-middleware/templates/gearbox-middleware-creds.yaml new file mode 100644 index 000000000..d10b5f2b6 --- /dev/null +++ b/helm/gearbox-middleware/templates/gearbox-middleware-creds.yaml @@ -0,0 +1,29 @@ +{{- if or (not .Values.global.externalSecrets.deploy) (and .Values.global.externalSecrets.deploy .Values.externalSecrets.createK8sgearboxMiddlewareSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: gearbox-middleware-g3auto +type: Opaque +stringData: + {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "gearbox-middleware-g3auto" }} + {{- $randomPass := printf "%s%s" "gateway:" (randAlphaNum 32) }} + base64Authz.txt: {{ if and $existingSecret (index $existingSecret.data "base64Authz.txt") }}{{ (index $existingSecret.data "base64Authz.txt") | b64dec | quote }}{{ else }}{{ $randomPass | quote }}{{ end }} + gearbox-middleware.env: | + HOSTNAME={{ .Values.global.hostname }} + {{ if and .Values.gearboxMiddlewareG3auto.awsaccesskey .Values.gearboxMiddlewareG3auto.awssecretkey }} + aws_access_key_id={{ .Values.gearboxMiddlewareG3auto.awsaccesskey }} + aws_secret_access_key={{ .Values.gearboxMiddlewareG3auto.awssecretkey }} + {{ end }} + AWS_REGION={{ .Values.gearboxMiddlewareG3auto.awsRegion }} + TESTING={{ .Values.gearboxMiddlewareG3auto.testing }} + DEBUG={{ .Values.gearboxMiddlewareG3auto.debug }} + ALLOWED_ISSUERS={{ .Values.gearboxMiddlewareG3auto.allowedIssuers }} + USER_API={{ .Values.gearboxMiddlewareG3auto.userApi }} + FORCE_ISSUER={{ .Values.gearboxMiddlewareG3auto.forceIssuer }} + PRIVATE_KEY_PATH={{ .Values.gearboxMiddlewareG3auto.gearboxMiddlewarePrivateKeyPath }} +data: + {{- if and .Values.gearboxMiddlewareG3auto.awsaccesskey .Values.gearboxMiddlewareG3auto.awssecretkey }} + aws_access_key_id: {{ .Values.gearboxMiddlewareG3auto.awsaccesskey | b64enc | quote }} + aws_secret_access_key: {{ .Values.gearboxMiddlewareG3auto.awssecretkey | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/hpa.yaml b/helm/gearbox-middleware/templates/hpa.yaml new file mode 100644 index 000000000..c3dee2ad8 --- /dev/null +++ b/helm/gearbox-middleware/templates/hpa.yaml @@ -0,0 +1,3 @@ +{{- if default .Values.global.autoscaling.enabled .Values.autoscaling.enabled }} +{{ include "common.hpa" . }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/jwt-keys.yaml b/helm/gearbox-middleware/templates/jwt-keys.yaml new file mode 100644 index 000000000..322abbf5b --- /dev/null +++ b/helm/gearbox-middleware/templates/jwt-keys.yaml @@ -0,0 +1,5 @@ +{{include "common.jwt-key-pair-secret" .}} +--- +{{include "common.jwt_public_key_setup_sa" .}} +--- +{{include "common.create_public_key_job" .}} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/pdb.yaml b/helm/gearbox-middleware/templates/pdb.yaml new file mode 100644 index 000000000..2ef2de13d --- /dev/null +++ b/helm/gearbox-middleware/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/gearbox-middleware/templates/secret-store.yaml b/helm/gearbox-middleware/templates/secret-store.yaml new file mode 100644 index 000000000..771c7760d --- /dev/null +++ b/helm/gearbox-middleware/templates/secret-store.yaml @@ -0,0 +1,3 @@ +{{ if .Values.global.externalSecrets.separateSecretStore }} +{{ include "common.secretstore" . }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/service.yaml b/helm/gearbox-middleware/templates/service.yaml new file mode 100644 index 000000000..3b49b48d1 --- /dev/null +++ b/helm/gearbox-middleware/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: gearbox-middleware-service + labels: + {{- include "gearbox-middleware.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gearbox-middleware.selectorLabels" . | nindent 4 }} diff --git a/helm/gearbox-middleware/templates/serviceaccount.yaml b/helm/gearbox-middleware/templates/serviceaccount.yaml new file mode 100644 index 000000000..72ba1cf7c --- /dev/null +++ b/helm/gearbox-middleware/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gearbox-middleware.serviceAccountName" . }} + labels: + {{- include "gearbox-middleware.labels" . | nindent 4 }} + {{- if .Values.global.crossplane.enabled }} + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.global.crossplane.accountId }}:role/{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox-middleware.serviceAccountName" . }} + {{- else }} + {{ with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox-middleware/templates/tests/test-connection.yaml b/helm/gearbox-middleware/templates/tests/test-connection.yaml new file mode 100644 index 000000000..4637273af --- /dev/null +++ b/helm/gearbox-middleware/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "gearbox-middleware.fullname" . }}-test-connection" + labels: + {{- include "gearbox-middleware.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "gearbox-middleware.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/gearbox-middleware/values.yaml b/helm/gearbox-middleware/values.yaml new file mode 100644 index 000000000..81e86262b --- /dev/null +++ b/helm/gearbox-middleware/values.yaml @@ -0,0 +1,271 @@ +# Default values for gearbox-middleware. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Global configuration +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: + externalSecrets: + # -- (bool) Whether to use External Secrets for aws config. + enabled: false + # -- (String) Name of Secrets Manager secret. + externalSecretAwsCreds: + # -- (map) Local secret setting if using a pre-exising secret. + useLocalSecret: + # -- (bool) Set to true if you would like to use a secret that is already running on your cluster. + enabled: false + # -- (string) Name of the local secret. + localSecretName: + # -- (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) 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. + minAvailable: 1 + # -- (map) External Secrets settings. + externalSecrets: + # -- (bool) Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any gearbox-middleware secrets you have deployed. + deploy: false + # -- (string) Will deploy a separate External Secret Store for this service. + separateSecretStore: false + # -- (map) Kubernetes configuration + crossplane: + # -- (bool) Set to true if deploying to AWS and want to use crossplane for AWS resources. + enabled: false + # -- (string) The name of the crossplane provider config. + providerConfigName: provider-aws + # -- (string) OIDC provider URL. This is used for authentication of roles/service accounts. + oidcProviderUrl: oidc.eks.us-east-1.amazonaws.com/id/12345678901234567890 + # -- (string) The account ID of the AWS account. + accountId: 123456789012 + s3: + # -- (string) The kms key id for the s3 bucket. + kmsKeyId: + # -- (bool) Whether to use s3 bucket versioning. + versioningEnabled: false + # -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + averageCPUValue: 500m + averageMemoryValue: 500Mi + # -- (map) Karpenter topology spread configuration. + topologySpread: + # -- (bool) Whether to enable topology spread constraints for all subcharts that support it. + enabled: false + # -- (string) The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". + topologyKey: "topology.kubernetes.io/zone" + # -- (int) The maxSkew to use for topology spread constraints. Defaults to 1. + maxSkew: 1 + +# -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: {} + +# -- (bool) Whether Metrics are enabled. +metricsEnabled: + +# -- (map) External Secrets settings. +externalSecrets: + # -- (string) Will create the Helm "gearbox-middleware-g3auto" secret even if Secrets Manager is enabled. This is helpful if you are wanting to use External Secrets for some, but not all secrets. + createK8sgearboxMiddlewareSecret: false + # -- (string) Will override the name of the aws secrets manager secret. Default is "gearbox-middleware-g3auto" + gearboxMiddlewareG3auto: +# -- (map) Secret information for External Secrets. +secrets: + # -- (str) AWS access key ID. Overrides global key. + awsAccessKeyId: + # -- (str) AWS secret access key ID. Overrides global key. + awsSecretAccessKey: + +# -- (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/pcdc/gearbox-middleware + # -- (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: + # -- (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) 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 + +# -- (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: + - gearbox-middleware + # -- (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: "gearbox-middleware-g3auto" + - name: gearbox-middleware-jwt-keys + secret: + secretName: "gearbox-middleware-jwt-keys" + items: + - key: jwt_private_key.pem + path: jwt_private_key.pem + optional: false +# -- (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: GEN3_DEBUG + value: "False" + - name: DB_DATABASE + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: database + optional: false + - name: DB_HOST + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: host + optional: false + - name: DB_USER + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: username + optional: false + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: password + optional: false + - name: DBREADY + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: dbcreated + optional: false + +# -- (list) Volumes to mount to the container. +volumeMounts: + - name: "gearbox-middleware-jwt-keys" + readOnly: true + mountPath: "/gearbox-middleware/gearbox_middleware/keys/jwt_private_key.pem" + subPath: jwt_private_key.pem + - name: config-volume + readOnly: true + mountPath: /gearbox-middleware/.env + subPath: gearbox-middleware.env + +# -- (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 memory requested + memory: 12Mi + # -- (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: 512Mi + +# -- (map) Values for gearbox-middleware secret. If the variable you want to change is not listed here it can be added to +gearboxMiddlewareG3auto: + hostname: "localhost" + # -- (string) AWS access key. + awsaccesskey: "" + # -- (string) AWS secret access key. + awssecretkey: "" + # -- (string) region for AWS. + awsRegion: "us-east-1" + # -- (bool) Whether to set gearbox-middleware backend into testing mode. + testing: True + # -- (bool) Whether to run in debug mode. + debug: False + # -- (string) accepted issuers in fence tokens. + allowedIssuers: "http://fence-service/,https://localhost/user" + # -- (string) url for fence. + userApi: "http://fence-service/" + # -- (string) whether to use the userApi value when validating tokens. + forceIssuer: True + # -- (string) private key path for service to service requests with gearbox-middleware. + gearboxMiddlewarePrivateKeyPath: "/gearbox-middleware/gearbox_middleware/keys/jwt_private_key.pem" + +# 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: + +# -- (map) Security context for the pod +podSecurityContext: + {} + # fsGroup: 2000 + +# -- (map) Security context for the containers in the pod +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 \ No newline at end of file diff --git a/helm/gearbox/.helmignore b/helm/gearbox/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/gearbox/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/gearbox/Chart.yaml b/helm/gearbox/Chart.yaml new file mode 100644 index 000000000..318b0d7d2 --- /dev/null +++ b/helm/gearbox/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +name: gearbox +description: A Helm chart for Kubernetes + +# 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.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" + +dependencies: + - name: common + version: 0.1.28 + repository: file://../common + - name: postgresql + version: 11.9.13 + repository: "https://charts.bitnami.com/bitnami" + condition: postgres.separate diff --git a/helm/gearbox/templates/NOTES.txt b/helm/gearbox/templates/NOTES.txt new file mode 100644 index 000000000..304ff546f --- /dev/null +++ b/helm/gearbox/templates/NOTES.txt @@ -0,0 +1 @@ +{{ .Chart.Name }} has been deployed successfully. \ No newline at end of file diff --git a/helm/gearbox/templates/_helpers.tpl b/helm/gearbox/templates/_helpers.tpl new file mode 100644 index 000000000..cb841b8ca --- /dev/null +++ b/helm/gearbox/templates/_helpers.tpl @@ -0,0 +1,67 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gearbox.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- 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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "gearbox.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "gearbox.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +Common labels +*/}} +{{- define "gearbox.labels" -}} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gearbox.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 "gearbox.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default "gearbox-sa" .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/gearbox/templates/aws-config.yaml b/helm/gearbox/templates/aws-config.yaml new file mode 100644 index 000000000..a49fb9f1b --- /dev/null +++ b/helm/gearbox/templates/aws-config.yaml @@ -0,0 +1,3 @@ +{{- if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) }} +{{ include "common.awsconfig" . }} +{{- end -}} \ No newline at end of file diff --git a/helm/gearbox/templates/crossplane.yaml b/helm/gearbox/templates/crossplane.yaml new file mode 100644 index 000000000..97fbcec4b --- /dev/null +++ b/helm/gearbox/templates/crossplane.yaml @@ -0,0 +1,101 @@ +{{- if .Values.global.crossplane.enabled }} +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Role +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox.serviceAccountName" . }}" +spec: + providerConfigRef: + name: provider-aws + forProvider: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox.serviceAccountName" . }}" + description: "Role for gearbox service account for {{ .Values.global.environment }}" + assumeRolePolicyDocument: | + { + "Version":"2012-10-17", + "Statement":[ + { "Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole" }, + { + "Sid":"", + "Effect":"Allow", + "Principal":{"Federated":"arn:aws:iam::{{ .Values.global.crossplane.accountId }}:oidc-provider/{{ .Values.global.crossplane.oidcProviderUrl }}"}, + "Action":"sts:AssumeRoleWithWebIdentity", + "Condition":{ + "StringEquals":{ + "{{ .Values.global.crossplane.oidcProviderUrl }}:sub":"system:serviceaccount:{{ .Release.Namespace }}:{{ include "gearbox.serviceAccountName" . }}", + "{{ .Values.global.crossplane.oidcProviderUrl }}:aud":"sts.amazonaws.com" + } + } + } + ] + } +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: Policy +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-role-policy" +spec: + providerConfigRef: + name: provider-aws + forProvider: + roleName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox.serviceAccountName" . }}" + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-role-policy" + document: | + { + "Version":"2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":["s3:List*","s3:Get*"], + "Resource":[ + "arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-bucket/*", + "arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-bucket" + ] + }, + { + "Effect":"Allow", + "Action":["s3:PutObject","s3:GetObject","s3:DeleteObject"], + "Resource":"arn:aws:s3:::{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-bucket/*" + } + ] + } + +--- +apiVersion: iam.aws.crossplane.io/v1beta1 +kind: RolePolicyAttachment +metadata: + name: "{{ include "gearbox.serviceAccountName" . }}-{{ .Release.Namespace }}-managed-policy-attachment" +spec: + providerConfigRef: + name: provider-aws + forProvider: + roleName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox.serviceAccountName" . }}" + policyArnRef: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-role-policy" +--- +apiVersion: s3.aws.crossplane.io/v1beta1 +kind: Bucket +metadata: + name: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-bucket" +spec: + providerConfigRef: + name: provider-aws + forProvider: + bucketName: "{{ .Values.global.environment }}-{{ .Release.Namespace }}-gearbox-bucket" + acl: private + forceDestroy: false + locationConstraint: {{ .Values.global.aws.region }} + tags: + Organization: gen3 + description: Created by crossplane + versioningConfiguration: + {{- if .Values.global.crossplane.s3.versioningEnabled }} + status: "Enabled" + {{- end }} + serverSideEncryptionConfiguration: + rules: + - applyServerSideEncryptionByDefault: + sseAlgorithm: aws:kms + {{- if .Values.global.crossplane.s3.kmsKeyId }} + kmsMasterKeyID: {{ .Values.global.crossplane.s3.kmsKeyId }} + {{- end }} +{{- end}} \ No newline at end of file diff --git a/helm/gearbox/templates/db-init.yaml b/helm/gearbox/templates/db-init.yaml new file mode 100644 index 000000000..e43084729 --- /dev/null +++ b/helm/gearbox/templates/db-init.yaml @@ -0,0 +1,12 @@ +{{ include "common.db-secret" . }} +--- +{{ include "common.db_setup_job" . }} +--- +{{ include "common.db_setup_sa" . }} +--- +{{- if and $.Values.global.externalSecrets.deploy (or $.Values.global.externalSecrets.pushSecret .Values.externalSecrets.pushSecret) }} +--- +{{ include "common.db-push-secret" . }} +--- +{{ include "common.secret.db.bootstrap" . }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox/templates/deployment.yaml b/helm/gearbox/templates/deployment.yaml new file mode 100644 index 000000000..144ee04ec --- /dev/null +++ b/helm/gearbox/templates/deployment.yaml @@ -0,0 +1,128 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gearbox-deployment + labels: + {{- include "gearbox.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "gearbox.selectorLabels" . | nindent 6 }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + strategy: + {{- toYaml .Values.strategy | nindent 8 }} + template: + metadata: + labels: + public: "yes" + s3: "yes" + userhelper: "yes" + {{- include "gearbox.selectorLabels" . | nindent 8 }} + {{- include "common.extraLabels" . | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/gearbox-creds.yaml") . | sha256sum }} + {{- $metricsEnabled := .Values.metricsEnabled }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = .Values.global.metricsEnabled }} + {{- end }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = true }} + {{- end }} + + {{- if $metricsEnabled }} + {{- include "common.grafanaAnnotations" . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.topologySpread.enabled }} + {{- include "common.TopologySpread" . | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "gearbox.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken}} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds}} + containers: + - name: gearbox + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- toYaml .Values.env | nindent 12 }} + {{- if and .Values.gearboxG3auto.awsaccesskey .Values.gearboxG3auto.awssecretkey }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: gearbox-g3auto + key: aws_access_key_id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: gearbox-g3auto + key: aws_secret_access_key + {{- else if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) (.Values.global.aws.useLocalSecret.enabled) }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + {{- if .Values.global.aws.useLocalSecret.enabled }} + name: {{ .Values.global.aws.useLocalSecret.localSecretName }} + {{- else }} + name: {{.Chart.Name}}-aws-config + {{- end }} + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + {{- if .Values.global.aws.useLocalSecret.enabled }} + name: {{ .Values.global.aws.useLocalSecret.localSecretName }} + {{- else }} + name: {{.Chart.Name}}-aws-config + {{- end }} + key: secret-access-key + {{- end }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + livenessProbe: + httpGet: + path: /_status + port: http + initialDelaySeconds: 10 + periodSeconds: 60 + timeoutSeconds: 30 + readinessProbe: + httpGet: + path: /_status + port: http + initContainers: + - name: gearbox-db-migrate + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + {{- toYaml .Values.initVolumeMounts | nindent 12 }} + env: + {{- toYaml .Values.env | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + command: ["/bin/sh"] + args: + - "-c" + - | + poetry run alembic upgrade head + diff --git a/helm/gearbox/templates/external-secret.yaml b/helm/gearbox/templates/external-secret.yaml new file mode 100644 index 000000000..cb73179bd --- /dev/null +++ b/helm/gearbox/templates/external-secret.yaml @@ -0,0 +1,22 @@ +{{- if and (.Values.global.externalSecrets.deploy) (not .Values.externalSecrets.createK8sgearboxSecret) }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gearbox-g3auto +spec: + refreshInterval: 5m + secretStoreRef: + name: {{include "common.SecretStore" .}} + kind: SecretStore + target: + name: gearbox-g3auto + creationPolicy: Owner + data: + - secretKey: config.json + remoteRef: + #name of secret in secrets manager + key: {{include "gearbox-g3auto" .}} +{{- end }} +{{- if and .Values.global.externalSecrets.deploy (not .Values.global.externalSecrets.createLocalK8sSecret) }} +{{ include "common.externalSecret.db" . }} +{{- end}} \ No newline at end of file diff --git a/helm/gearbox/templates/gearbox-creds.yaml b/helm/gearbox/templates/gearbox-creds.yaml new file mode 100644 index 000000000..7496d70f1 --- /dev/null +++ b/helm/gearbox/templates/gearbox-creds.yaml @@ -0,0 +1,32 @@ +{{- if or (not .Values.global.externalSecrets.deploy) (and .Values.global.externalSecrets.deploy .Values.externalSecrets.createK8sgearboxSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: gearbox-g3auto +type: Opaque +stringData: + {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "gearbox-g3auto" }} + {{- $randomPass := printf "%s%s" "gateway:" (randAlphaNum 32) }} + # existing secret data are base64; decode them before placing in stringData + base64Authz.txt: {{ if and $existingSecret (index $existingSecret.data "base64Authz.txt") }}{{ (index $existingSecret.data "base64Authz.txt") | b64dec | quote }}{{ else }}{{ $randomPass | quote }}{{ end }} + gearbox.env: | + HOSTNAME={{ .Values.global.hostname }} + {{ if and .Values.gearboxG3auto.awsaccesskey .Values.gearboxG3auto.awssecretkey }} + S3_AWS_ACCESS_KEY_ID={{ .Values.gearboxG3auto.awsaccesskey }} + S3_AWS_SECRET_ACCESS_KEY={{ .Values.gearboxG3auto.awssecretkey }} + {{ end }} + AWS_REGION={{ .Values.gearboxG3auto.awsRegion }} + TESTING={{ .Values.gearboxG3auto.testing }} + DEBUG={{ .Values.gearboxG3auto.debug }} + ENABLE_PHI={{ .Values.gearboxG3auto.enablePHI }} + DUMMY_S3={{ .Values.gearboxG3auto.dummyS3 }} + ALLOWED_ISSUERS={{ .Values.gearboxG3auto.allowedIssuers }} + USER_API={{ .Values.gearboxG3auto.userApi }} + FORCE_ISSUER={{ .Values.gearboxG3auto.forceIssuer }} + GEARBOX_MIDDLEWARE_PUBLIC_KEY_PATH={{ .Values.gearboxG3auto.gearboxMiddlewarePublicKeyPath }} +data: + {{- if and .Values.gearboxG3auto.awsaccesskey .Values.gearboxG3auto.awssecretkey }} + aws_access_key_id: {{ .Values.gearboxG3auto.awsaccesskey | b64enc | quote }} + aws_secret_access_key: {{ .Values.gearboxG3auto.awssecretkey | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox/templates/hpa.yaml b/helm/gearbox/templates/hpa.yaml new file mode 100644 index 000000000..c3dee2ad8 --- /dev/null +++ b/helm/gearbox/templates/hpa.yaml @@ -0,0 +1,3 @@ +{{- if default .Values.global.autoscaling.enabled .Values.autoscaling.enabled }} +{{ include "common.hpa" . }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox/templates/netpolicy.yaml b/helm/gearbox/templates/netpolicy.yaml new file mode 100644 index 000000000..70a5c3b5d --- /dev/null +++ b/helm/gearbox/templates/netpolicy.yaml @@ -0,0 +1 @@ +{{ include "common.db_netpolicy" . }} \ No newline at end of file diff --git a/helm/gearbox/templates/pdb.yaml b/helm/gearbox/templates/pdb.yaml new file mode 100644 index 000000000..2ef2de13d --- /dev/null +++ b/helm/gearbox/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/gearbox/templates/secret-store.yaml b/helm/gearbox/templates/secret-store.yaml new file mode 100644 index 000000000..771c7760d --- /dev/null +++ b/helm/gearbox/templates/secret-store.yaml @@ -0,0 +1,3 @@ +{{ if .Values.global.externalSecrets.separateSecretStore }} +{{ include "common.secretstore" . }} +{{- end }} \ No newline at end of file diff --git a/helm/gearbox/templates/service.yaml b/helm/gearbox/templates/service.yaml new file mode 100644 index 000000000..92c7bd807 --- /dev/null +++ b/helm/gearbox/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: gearbox-service + labels: + {{- include "gearbox.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gearbox.selectorLabels" . | nindent 4 }} diff --git a/helm/gearbox/templates/serviceaccount.yaml b/helm/gearbox/templates/serviceaccount.yaml new file mode 100644 index 000000000..cb90113b6 --- /dev/null +++ b/helm/gearbox/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gearbox.serviceAccountName" . }} + labels: + {{- include "gearbox.labels" . | nindent 4 }} + {{- if .Values.global.crossplane.enabled }} + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.global.crossplane.accountId }}:role/{{ .Values.global.environment }}-{{ .Release.Namespace }}-{{ include "gearbox.serviceAccountName" . }} + {{- else }} + {{ with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/gearbox/values.yaml b/helm/gearbox/values.yaml new file mode 100644 index 000000000..9d83760bc --- /dev/null +++ b/helm/gearbox/values.yaml @@ -0,0 +1,346 @@ +# Default values for gearbox. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Global configuration +global: + # -- (map) External Secrets settings. + externalSecrets: + # -- (bool) Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override secrets you have deployed. + deploy: false + # -- (bool) Will create the databases and store the creds in Kubernetes Secrets even if externalSecrets is deployed. Useful if you want to use ExternalSecrets for other secrets besides db secrets. + createLocalK8sSecret: false + # -- (string) Will deploy a separate External Secret Store for this service. + separateSecretStore: false + # -- (string) Will use a manually deployed clusterSecretStore if defined. + clusterSecretStoreRef: "" + + # -- (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: + externalSecrets: + # -- (bool) Whether to use External Secrets for aws config. + enabled: false + # -- (String) Name of Secrets Manager secret. + externalSecretAwsCreds: + # -- (map) Local secret setting if using a pre-exising secret. + useLocalSecret: + # -- (bool) Set to true if you would like to use a secret that is already running on your cluster. + enabled: false + # -- (string) Name of the local secret. + localSecretName: + # -- (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) 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. + minAvailable: 1 + # -- (map) External Secrets settings. + externalSecrets: + # -- (bool) Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any gearbox secrets you have deployed. + deploy: false + # -- (string) Will deploy a separate External Secret Store for this service. + separateSecretStore: false + # -- (map) Kubernetes configuration + crossplane: + # -- (bool) Set to true if deploying to AWS and want to use crossplane for AWS resources. + enabled: false + # -- (string) The name of the crossplane provider config. + providerConfigName: provider-aws + # -- (string) OIDC provider URL. This is used for authentication of roles/service accounts. + oidcProviderUrl: oidc.eks.us-east-1.amazonaws.com/id/12345678901234567890 + # -- (string) The account ID of the AWS account. + accountId: 123456789012 + s3: + # -- (string) The kms key id for the s3 bucket. + kmsKeyId: + # -- (bool) Whether to use s3 bucket versioning. + versioningEnabled: false + # -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + averageCPUValue: 500m + averageMemoryValue: 500Mi + # -- (map) Karpenter topology spread configuration. + topologySpread: + # -- (bool) Whether to enable topology spread constraints for all subcharts that support it. + enabled: false + # -- (string) The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". + topologyKey: "topology.kubernetes.io/zone" + # -- (int) The maxSkew to use for topology spread constraints. Defaults to 1. + maxSkew: 1 + + # -- (map) Controls network policy settings + netPolicy: + enabled: false + + postgres: + # -- (bool) Whether the database should be created. + dbCreate: true + # -- (string) Name of external secret. Disabled if empty + externalSecret: "" + # -- (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" + +# -- (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: + # (bool) Whether the database should be restored from s3. Default to global.postgres.dbRestore + dbRestore: false + # -- (bool) Whether the database should be created. Default to global.postgres.dbCreate + dbCreate: + # -- (string) Hostname for postgres server. This is a service override, defaults to global.postgres.host + host: + # -- (string) Database name for postgres. This is a service override, defaults to - + database: + # -- (string) Username for postgres. This is a service override, defaults to - + username: + # -- (string) Port for 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) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: {} + +# -- (bool) Whether Metrics are enabled. +metricsEnabled: + +# -- (map) External Secrets settings. +externalSecrets: + # -- (string) Will create the Helm "gearbox-g3auto" secret even if Secrets Manager is enabled. This is helpful if you are wanting to use External Secrets for some, but not all secrets. + createK8sgearboxSecret: false + # -- (string) Will override the name of the aws secrets manager secret. Default is "gearbox-g3auto" + gearboxG3auto: +# -- (map) Secret information for External Secrets. +secrets: + # -- (str) AWS access key ID. Overrides global key. + awsAccessKeyId: + # -- (str) AWS secret access key ID. Overrides global key. + awsSecretAccessKey: + +# -- (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/gearbox + # -- (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 + targetPort: 80 + +# -- (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: "" + +# -- (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 + +# -- (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: + - gearbox + # -- (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: "gearbox-g3auto" + - name: gearbox-middleware-jwt-keys + secret: + secretName: "gearbox-middleware-jwt-keys" + items: + - key: jwt_public_key.pem + path: jwt_public_key.pem + optional: false +# -- (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: GEN3_DEBUG + value: "False" + - name: DB_DATABASE + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: database + optional: false + - name: DB_HOST + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: host + optional: false + - name: DB_USER + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: username + optional: false + - name: ADMIN_LOGINS + valueFrom: + secretKeyRef: + name: gearbox-g3auto + key: base64Authz.txt + optional: true + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: password + optional: false + - name: DBREADY + valueFrom: + secretKeyRef: + name: gearbox-dbcreds + key: dbcreated + optional: false + +# -- (list) Volumes to mount to the container. +volumeMounts: + - name: "gearbox-middleware-jwt-keys" + readOnly: true + mountPath: "/gearbox/src/gearbox/keys/jwt_public_key.pem" + subPath: jwt_public_key.pem + - name: config-volume + readOnly: true + mountPath: /gearbox/.env + subPath: gearbox.env + +initVolumeMounts: + - name: config-volume + readOnly: true + mountPath: /gearbox/.env + subPath: gearbox.env + +# -- (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 memory requested + memory: 12Mi + # -- (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: 512Mi + +# -- (map) Values for gearbox secret. If the variable you want to change is not listed here it can be added to +gearboxG3auto: + hostname: "localhost" + # -- (string) AWS access key. + awsaccesskey: "" + # -- (string) AWS secret access key. + awssecretkey: "" + # -- (string) region for AWS. + awsRegion: "us-east-1" + # -- (bool) Whether to set gearbox backend into testing mode. + testing: False + # -- (bool) Whether to run in debug mode. + debug: False + # -- (bool) Whether to allow for phi. + enablePhi: False + # -- (string) use a public dummy S3 bucket for testing presigned urls + dummyS3: True + # -- (string) accepted issuers in fence tokens. + allowedIssuers: "http://fence-service/,https://localhost/user" + # -- (string) url for fence. + userApi: "http://fence-service/" + # -- (string) whether to use the userApi value when validating tokens. + forceIssuer: True + # -- (string) public key path for service to service requests with gearbox. + gearboxMiddlewarePublicKeyPath: "/gearbox/src/gearbox/keys/jwt_public_key.pem" + +# 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: + +# -- (map) Security context for the pod +podSecurityContext: + {} + # fsGroup: 2000 + +# -- (map) Security context for the containers in the pod +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 diff --git a/helm/gen3-analysis/templates/deployment.yaml b/helm/gen3-analysis/templates/deployment.yaml index b4b55bda9..ac1a13cbf 100644 --- a/helm/gen3-analysis/templates/deployment.yaml +++ b/helm/gen3-analysis/templates/deployment.yaml @@ -60,16 +60,17 @@ spec: livenessProbe: httpGet: path: /_status - port: 8000 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 8000 + port: http ports: - - containerPort: 8000 + - containerPort: {{ .Values.service.targetPort }} + name: http {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/gen3-analysis/values.yaml b/helm/gen3-analysis/values.yaml index 4347e7d25..11cc09cb7 100644 --- a/helm/gen3-analysis/values.yaml +++ b/helm/gen3-analysis/values.yaml @@ -189,6 +189,7 @@ resources: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 8000 # -- (int) The port number that the service exposes. port: - protocol: TCP diff --git a/helm/gen3-user-data-library/templates/deployment.yaml b/helm/gen3-user-data-library/templates/deployment.yaml index 654b6a63f..caa26b6b5 100644 --- a/helm/gen3-user-data-library/templates/deployment.yaml +++ b/helm/gen3-user-data-library/templates/deployment.yaml @@ -90,19 +90,19 @@ spec: optional: false imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} name: http livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 80 + port: http {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/gen3-user-data-library/values.yaml b/helm/gen3-user-data-library/values.yaml index feee82b35..15d0b46b3 100644 --- a/helm/gen3-user-data-library/values.yaml +++ b/helm/gen3-user-data-library/values.yaml @@ -80,6 +80,7 @@ service: type: ClusterIP # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports port: 80 + targetPort: 80 # This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ ingress: diff --git a/helm/gen3/Chart.yaml b/helm/gen3/Chart.yaml index 6fdf16693..63ab91b6c 100644 --- a/helm/gen3/Chart.yaml +++ b/helm/gen3/Chart.yaml @@ -119,6 +119,23 @@ dependencies: version: 0.1.34 repository: "file://../wts" condition: wts.enabled + - name: pcdcanalysistools + version: "1.0.0" + repository: "file://../pcdcanalysistools" + condition: pcdcanalysistools.enabled + - name: amanuensis + version: "1.0.0" + repository: "file://../amanuensis" + condition: amanuensis.enabled + - name: gearbox + version: "0.1.0" + repository: "file://../gearbox" + condition: gearbox.enabled + - name: gearbox-middleware + version: "0.1.0" + repository: "file://../gearbox-middleware" + condition: gearbox-middleware.enabled + - name: gen3-network-policies version: 0.1.3 repository: "file://../gen3-network-policies" @@ -143,11 +160,11 @@ dependencies: - name: elasticsearch version: 7.10.2 repository: "https://helm.elastic.co" - condition: global.dev + condition: elasticsearch.enabled - name: postgresql - version: 11.9.13 + version: 14.3.3 repository: "https://charts.bitnami.com/bitnami" - condition: global.dev + condition: postgresql.enabled # (optional) NeuVector Kubernetes Security Policy templates to protect Gen3 # NeuVector must be installed separately. diff --git a/helm/gen3/confighelper/config_helper.py b/helm/gen3/confighelper/config_helper.py new file mode 100644 index 000000000..869ca25af --- /dev/null +++ b/helm/gen3/confighelper/config_helper.py @@ -0,0 +1,486 @@ +import json +import os +import copy +import argparse +import re +import types + +# +# make it easy to change this for testing +XDG_DATA_HOME = os.getenv("XDG_DATA_HOME", "/usr/share/") + + +def default_search_folders(app_name): + """ + Return the list of folders to search for configuration files + """ + return [ + "%s/cdis/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/cdis/%s" % app_name, + "%s/gen3/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/gen3/%s" % app_name, + "/var/www/%s" % app_name, + "/etc/gen3/%s" % app_name, + ] + + +def find_paths(file_name, app_name, search_folders=None): + """ + Search the given folders for file_name + search_folders defaults to default_search_folders if not specified + return the first path to file_name found + """ + search_folders = search_folders or default_search_folders(app_name) + possible_files = [os.path.join(folder, file_name) for folder in search_folders] + return [path for path in possible_files if os.path.exists(path)] + + +def load_json(file_name, app_name, search_folders=None): + """ + json.load(file_name) after finding file_name in search_folders + + return the loaded json data or None if file not found + """ + actual_files = find_paths(file_name, app_name, search_folders) + if not actual_files: + return None + with open(actual_files[0], "r") as reader: + return json.load(reader) + + +def inject_creds_into_fence_config(creds_file_path, config_file_path): + creds_file = open(creds_file_path, "r") + creds = json.load(creds_file) + creds_file.close() + + # get secret values from creds.json file + db_host = _get_nested_value(creds, "db_host") + db_username = _get_nested_value(creds, "db_username") + db_password = _get_nested_value(creds, "db_password") + db_database = _get_nested_value(creds, "db_database") + hostname = _get_nested_value(creds, "hostname") + indexd_password = _get_nested_value(creds, "indexd_password") + google_client_secret = _get_nested_value(creds, "google_client_secret") + google_client_id = _get_nested_value(creds, "google_client_id") + hmac_key = _get_nested_value(creds, "hmac_key") + db_path = "postgresql://{}:{}@{}:5432/{}".format( + db_username, db_password, db_host, db_database + ) + + config_file = open(config_file_path, "r").read() + + print(" DB injected with value(s) from creds.json") + config_file = _replace(config_file, "DB", db_path) + + print(" BASE_URL injected with value(s) from creds.json") + config_file = _replace(config_file, "BASE_URL", "https://{}/user".format(hostname)) + + print(" INDEXD_PASSWORD injected with value(s) from creds.json") + config_file = _replace(config_file, "INDEXD_PASSWORD", indexd_password) + config_file = _replace(config_file, "INDEXD_USERNAME", "fence") + + print(" ENCRYPTION_KEY injected with value(s) from creds.json") + config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) + + print( + " OPENID_CONNECT/google/client_secret injected with value(s) " + "from creds.json" + ) + config_file = _replace( + config_file, "OPENID_CONNECT/google/client_secret", google_client_secret + ) + + print(" OPENID_CONNECT/google/client_id injected with value(s) from creds.json") + config_file = _replace( + config_file, "OPENID_CONNECT/google/client_id", google_client_id + ) + + open(config_file_path, "w+").write(config_file) + +def inject_creds_into_amanuensis_config(creds_file_path, config_file_path): + creds_file = open(creds_file_path, "r") + creds = json.load(creds_file) + creds_file.close() + + # get secret values from creds.json file + db_host = _get_nested_value(creds, "db_host") + db_username = _get_nested_value(creds, "db_username") + db_password = _get_nested_value(creds, "db_password") + db_database = _get_nested_value(creds, "db_database") + hostname = _get_nested_value(creds, "hostname") + data_delivery_bucket = _get_nested_value(creds, "data_delivery_bucket") + data_delivery_bucket_aws_key_id = _get_nested_value(creds, "data_delivery_bucket_aws_key_id") + data_delivery_bucket_aws_access_key = _get_nested_value(creds, "data_delivery_bucket_aws_access_key") + csl_key = _get_nested_value(creds, "csl_key") + + db_path = "postgresql://{}:{}@{}:5432/{}".format( + db_username, db_password, db_host, db_database + ) + + config_file = open(config_file_path, "r").read() + + print(" DB injected with value(s) from creds.json") + config_file = _replace(config_file, "DB", db_path) + + print(" BASE_URL injected with value(s) from creds.json") + config_file = _replace(config_file, "BASE_URL", "https://{}/amanuensis".format(hostname)) + + print(" HOSTNAME injected with value(s) from creds.json") + config_file = _replace(config_file, "HOSTNAME", "{}".format(hostname)) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_access_key_id injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_access_key_id", data_delivery_bucket_aws_key_id + ) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_secret_access_key injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/aws_secret_access_key", data_delivery_bucket_aws_access_key + ) + + print(" AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/bucket_name injected with value(s) from creds.json") + config_file = _replace( + config_file, "AWS_CREDENTIALS/DATA_DELIVERY_S3_BUCKET/bucket_name", data_delivery_bucket + ) + + print(" CSL_KEY injected with value(s) from creds.json") + config_file = _replace( + config_file, "CSL_KEY", csl_key + ) + + # modify USER_API to http://user-service/ if hostname is localhost + + if hostname == "localhost": + print(" USER_API set to http://fence-service/") + config_file = _replace(config_file, "USER_API", "http://fence-service/") + # print(" ENCRYPTION_KEY injected with value(s) from creds.json") + # config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) + + + open(config_file_path, "w+").write(config_file) + + +def set_prod_defaults(config_file_path): + config_file = open(config_file_path, "r").read() + + print( + " CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS set as " + "var/www/fence/fence_google_app_creds_secret.json" + ) + config_file = _replace( + config_file, + "CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS", + "/var/www/fence/fence_google_app_creds_secret.json", + ) + + print( + " CIRRUS_CFG/GOOGLE_STORAGE_CREDS set as " + "var/www/fence/fence_google_storage_creds_secret.json" + ) + config_file = _replace( + config_file, + "CIRRUS_CFG/GOOGLE_STORAGE_CREDS", + "/var/www/fence/fence_google_storage_creds_secret.json", + ) + + print(" INDEXD set as http://indexd-service/") + config_file = _replace(config_file, "INDEXD", "http://indexd-service/") + + print(" ARBORIST set as http://arborist-service/") + config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") + + print(" HTTP_PROXY/host set as cloud-proxy.internal.io") + config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") + + print(" HTTP_PROXY/port set as 3128") + config_file = _replace(config_file, "HTTP_PROXY/port", 3128) + + print(" DEBUG set to false") + config_file = _replace(config_file, "DEBUG", False) + + print(" MOCK_AUTH set to false") + config_file = _replace(config_file, "MOCK_AUTH", False) + + print(" MOCK_GOOGLE_AUTH set to false") + config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) + + print(" AUTHLIB_INSECURE_TRANSPORT set to true") + config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) + + print(" SESSION_COOKIE_SECURE set to true") + config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) + + print(" ENABLE_CSRF_PROTECTION set to true") + config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) + + open(config_file_path, "w+").write(config_file) + +def set_prod_defaults_amanuensis(config_file_path): + config_file = open(config_file_path, "r").read() + + print(" INDEXD set as http://indexd-service/") + config_file = _replace(config_file, "INDEXD", "http://indexd-service/") + + print(" ARBORIST set as http://arborist-service/") + config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") + + print(" HTTP_PROXY/host set as cloud-proxy.internal.io") + config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") + + print(" HTTP_PROXY/port set as 3128") + config_file = _replace(config_file, "HTTP_PROXY/port", 3128) + + print(" DEBUG set to false") + config_file = _replace(config_file, "DEBUG", False) + + print(" MOCK_AUTH set to false") + config_file = _replace(config_file, "MOCK_AUTH", False) + + print(" MOCK_GOOGLE_AUTH set to false") + config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) + + print(" AUTHLIB_INSECURE_TRANSPORT set to true") + config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) + + print(" SESSION_COOKIE_SECURE set to true") + config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) + + print(" ENABLE_CSRF_PROTECTION set to true") + config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) + + open(config_file_path, "w+").write(config_file) + +def inject_other_files_into_fence_config(other_files, config_file_path): + additional_cfgs = _get_all_additional_configs(other_files) + + config_file = open(config_file_path, "r").read() + + for key, value in additional_cfgs.iteritems(): + print(" {} set to {}".format(key, value)) + config_file = _nested_replace(config_file, key, value) + + open(config_file_path, "w+").write(config_file) + + +def _get_all_additional_configs(other_files): + """ + Attempt to parse given list of files and extract configuration variables and values + """ + additional_configs = dict() + for file_path in other_files: + try: + file_ext = file_path.strip().split(".")[-1] + if file_ext == "json": + json_file = open(file_path, "r") + configs = json.load(json_file) + json_file.close() + elif file_ext == "py": + configs = from_pyfile(file_path) + else: + print( + "Cannot load config vars from a file with extention: {}".format( + file_ext + ) + ) + except Exception as exc: + # if there's any issue reading the file, exit + print( + "Error reading {}. Cannot get configuration. Skipping this file. " + "Details: {}".format(other_files, str(exc)) + ) + continue + + if configs: + additional_configs.update(configs) + + return additional_configs + + +def _nested_replace(config_file, key, value, replacement_path=None): + replacement_path = replacement_path or key + try: + for inner_key, inner_value in value.iteritems(): + temp_path = replacement_path + temp_path = temp_path + "/" + inner_key + config_file = _nested_replace( + config_file, inner_key, inner_value, temp_path + ) + except AttributeError: + # not a dict so replace + if value is not None: + config_file = _replace(config_file, replacement_path, value) + + return config_file + + +def _replace(yaml_config, path_to_key, replacement_value, start=0, nested_level=0, key_only=False): + """ + Replace a nested value in a YAML file string with the given value without + losing comments. Uses a regex to do the replacement. + + Args: + yaml_config (str): a string representing a full configuration file + path_to_key (str): nested/path/to/key. The value of this key will be + replaced + replacement_value (str): Replacement value for the key from + path_to_key + """ + nested_path_to_replace = path_to_key.split("/") + + # our regex looks for a specific number of spaces to ensure correct + # level of nesting. It matches to the end of the line + search_string = ( + " " * nested_level + ".*" + nested_path_to_replace[0] + "(')?(\")?:.*\n" + ) + matches = re.search(search_string, yaml_config[start:]) + + # early return if we haven't found anything + if not matches: + return yaml_config + + # if we're on the last item in the path, we need to get the value and + # replace it in the original file + if len(nested_path_to_replace) == 1: + # replace the current key:value with the new replacement value + match_start = start + matches.start(0) + len(" " * nested_level) + match_end = start + matches.end(0) + if not key_only: + yaml_config = ( + yaml_config[:match_start] + + "{}: {}\n".format( + nested_path_to_replace[0], + _get_yaml_replacement_value(replacement_value, nested_level), + ) + + yaml_config[match_end:] + ) + else: + yaml_config = ( + yaml_config[:match_start] + + "{}:\n".format( + _get_yaml_replacement_value(replacement_value, nested_level), + ) + + yaml_config[match_end:] + ) + + return yaml_config + + # set new start point to past current match and move on to next match + start = start + matches.end(0) + nested_level += 1 + del nested_path_to_replace[0] + + return _replace( + yaml_config, + "/".join(nested_path_to_replace), + replacement_value, + start, + nested_level, + key_only=key_only, + ) + + +def from_pyfile(filename, silent=False): + """ + Modeled after flask's ability to load in python files: + https://github.com/pallets/flask/blob/master/flask/config.py + + Some alterations were made but logic is essentially the same + """ + filename = os.path.abspath(filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except IOError as e: + print("Unable to load configuration file ({})".format(e.strerror)) + if silent: + return False + raise + return _from_object(d) + + +def _from_object(obj): + configs = {} + for key in dir(obj): + if key.isupper(): + configs[key] = getattr(obj, key) + return configs + + +def _get_yaml_replacement_value(value, nested_level=0): + if isinstance(value, str): + return "'" + value + "'" + elif isinstance(value, bool): + return str(value).lower() + elif isinstance(value, list) or isinstance(value, set): + output = "" + for item in value: + # spaces for nested level then spaces and hyphen for each list item + output += ( + "\n" + + " " * nested_level + + " - " + + _get_yaml_replacement_value(item) + + "" + ) + return output + else: + return value + + +def _get_nested_value(dictionary, nested_path): + """ + Return a value from a dictionary given a path-like nesting of keys. + + Will default to an empty string if value cannot be found. + + Args: + dictionary (dict): a dictionary + nested_path (str): nested/path/to/key + + Returns: + ?: Value from dict + """ + replacement_value_path = nested_path.split("/") + replacement_value = copy.deepcopy(dictionary) + + for item in replacement_value_path: + replacement_value = replacement_value.get(item, {}) + + if replacement_value == {}: + replacement_value = "" + + return replacement_value + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", + "--creds_file_to_inject", + default="creds.json", + help="creds file to inject into the configuration yaml", + ) + parser.add_argument( + "--other_files_to_inject", + nargs="+", + help="fence_credentials.json, local_settings.py, fence_settings.py file(s) to " + "inject into the configuration yaml", + ) + parser.add_argument( + "-c", "--config_file", default="config.yaml", help="configuration yaml" + ) + args = parser.parse_args() + + if args.config_file == "new-amanuensis-config.yaml": + inject_creds_into_amanuensis_config(args.creds_file_to_inject, args.config_file) + set_prod_defaults_amanuensis(args.config_file) + else: + inject_creds_into_fence_config(args.creds_file_to_inject, args.config_file) + set_prod_defaults(args.config_file) + + if args.other_files_to_inject: + inject_other_files_into_fence_config( + args.other_files_to_inject, args.config_file + ) diff --git a/helm/gen3/templates/cleanup-helm-hooks-job.yaml b/helm/gen3/templates/cleanup-helm-hooks-job.yaml new file mode 100644 index 000000000..7723d71c0 --- /dev/null +++ b/helm/gen3/templates/cleanup-helm-hooks-job.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gen3.fullname" . }}-cleanup + namespace: {{ .Release.Namespace }} + labels: + {{- include "gen3.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-20" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +automountServiceAccountToken: true + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "gen3.fullname" . }}-cleanup-role + namespace: {{ .Release.Namespace }} + labels: + {{- include "gen3.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-20" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +rules: +- apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["get", "list", "delete"] +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "delete"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "delete"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "gen3.fullname" . }}-cleanup-rolebinding + namespace: {{ .Release.Namespace }} + labels: + {{- include "gen3.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-20" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +subjects: +- kind: ServiceAccount + name: {{ include "gen3.fullname" . }}-cleanup + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ include "gen3.fullname" . }}-cleanup-role + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "gen3.fullname" . }}-cleanup-{{ randAlphaNum 8 | lower }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "-10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + ttlSecondsAfterFinished: 60 + template: + spec: + restartPolicy: Never + serviceAccountName: {{ include "gen3.fullname" . }}-cleanup + containers: + - name: cleanup + image: bitnamisecure/kubectl:latest + imagePullPolicy: IfNotPresent + command: + - /bin/bash + - -c + - | + echo "Cleaning up hook resources for release: {{ .Release.Name }}" + + # Clean up jobs created by hooks + kubectl delete jobs -l app=gen3-created-by-hook + + # Clean up cronjobs created by hooks + kubectl delete cronjobs -l app=gen3-created-by-hook + + # Clean up secrets created by hooks (if any) + kubectl delete secrets -l app=gen3-created-by-hook + echo "Cleanup completed" + resources: + requests: + memory: 12Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m \ No newline at end of file diff --git a/helm/gen3/templates/config-helper.yaml b/helm/gen3/templates/config-helper.yaml new file mode 100644 index 000000000..fe20be2f2 --- /dev/null +++ b/helm/gen3/templates/config-helper.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-helper +data: +{{ (.Files.Glob "confighelper/*").AsConfig | indent 2 }} \ No newline at end of file diff --git a/helm/gen3/templates/global-manifest.yaml b/helm/gen3/templates/global-manifest.yaml index b7b6838e2..4d2daf386 100644 --- a/helm/gen3/templates/global-manifest.yaml +++ b/helm/gen3/templates/global-manifest.yaml @@ -13,6 +13,8 @@ data: "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 }} + "authz_entity_name": {{.Values.global.authz_entity_name | quote }} "frontend_root": {{ .Values.global.frontendRoot | quote }} "logout_inactive_users": {{ .Values.global.logoutInactiveUsers | quote }} "workspace_timeout_in_minutes": {{ .Values.global.workspaceTimeoutInMinutes | quote }} diff --git a/helm/gen3/templates/nginx-config.yaml b/helm/gen3/templates/nginx-config.yaml new file mode 100644 index 000000000..4698aaa28 --- /dev/null +++ b/helm/gen3/templates/nginx-config.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config +data: + nginx.conf: | + user gen3; + worker_processes auto; + error_log /var/log/nginx/error.log notice; + pid /var/lib/nginx/nginx.pid; + + # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. + include /usr/share/nginx/modules/*.conf; + + events { + worker_connections 1024; + } + + http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + + # Suppress logging for known health checks + map $http_user_agent $loggable { + default 1; + "ELB-HealthChecker/2.0" 0; + ~^Uptime-Kuma 0; + ~^kube-probe 0; + ~GoogleStackdriverMonitoring 0; + } + + access_log /var/log/nginx/access.log main if=$loggable; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + types_hash_max_size 4096; + + # increase max from default 1m + client_max_body_size 200m; + + + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + server { + + listen 8080; + server_name localhost; + proxy_read_timeout 400; + proxy_send_timeout 400; + proxy_connect_timeout 400; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } + } \ No newline at end of file diff --git a/helm/gen3/templates/postgres-es-pdb.yaml b/helm/gen3/templates/postgres-es-pdb.yaml index 528f51b08..b17928319 100644 --- a/helm/gen3/templates/postgres-es-pdb.yaml +++ b/helm/gen3/templates/postgres-es-pdb.yaml @@ -1,4 +1,5 @@ {{- if and .Values.global.pdb (.Values.global.dev) }} +{{ if .Values.postgresql.enabled }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: @@ -8,7 +9,9 @@ spec: selector: matchLabels: app.kubernetes.io/name: "postgresql" +{{- end }} --- +{{ if .Values.elasticsearch.enabled }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: @@ -19,3 +22,4 @@ spec: matchLabels: app: "gen3-elasticsearch-master" {{- end }} +{{- end }} diff --git a/helm/gen3/templates/tests/service-account.yaml b/helm/gen3/templates/tests/service-account.yaml deleted file mode 100644 index 95b67cfdf..000000000 --- a/helm/gen3/templates/tests/service-account.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kubectl-access - namespace: {{ .Release.Namespace }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: kubectl-access-role - namespace: {{ .Release.Namespace }} -rules: - - apiGroups: [""] - resources: ["pods", "pods/exec", "configmaps", "deployments"] - verbs: ["get", "list", "create"] - - apiGroups: ["batch"] - resources: ["cronjobs", "jobs"] - verbs: ["get", "list", "create", "delete", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: kubectl-access-binding - namespace: {{ .Release.Namespace }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: kubectl-access-role -subjects: - - kind: ServiceAccount - name: kubectl-access - namespace: {{ .Release.Namespace }} diff --git a/helm/gen3/values.yaml b/helm/gen3/values.yaml index 2d388ede0..7dc169713 100644 --- a/helm/gen3/values.yaml +++ b/helm/gen3/values.yaml @@ -244,7 +244,6 @@ gen3-workflow: # -- (bool) Whether to deploy the gen3-workflow subchart. enabled: false - # -- (map) Configurations for guppy chart. guppy: # -- (bool) Whether to deploy the guppy subchart. @@ -402,6 +401,7 @@ access-backend: # -- (map) To configure postgresql subchart # Disable persistence by default so we can spin up and down ephemeral environments postgresql: + enabled: true image: repository: bitnamilegacy/postgresql primary: @@ -456,6 +456,14 @@ neuvector: # hostname/service name for our ElasitcSearch instance, used to allow egress from containers ES_HOST: gen3-elasticsearch-master +pcdcanalysistools: + # -- (bool) Whether to deploy the pcdcanalysistools subchart. + enabled: true + +amanuensis: + # -- (bool) Whether to deploy the amanuensis subchart. + enabled: true + # -- (map) Secret information for External Secrets and DB Secrets. secrets: # -- (str) AWS access key ID. Overrides global key. diff --git a/helm/guppy/templates/deployment.yaml b/helm/guppy/templates/deployment.yaml index 96ca5f352..bd33c3474 100644 --- a/helm/guppy/templates/deployment.yaml +++ b/helm/guppy/templates/deployment.yaml @@ -46,6 +46,8 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} {{- with .Values.volumes}} volumes: {{- toYaml . | nindent 8}} @@ -53,10 +55,12 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} livenessProbe: httpGet: path: /_status - port: 8000 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 @@ -64,9 +68,10 @@ spec: readinessProbe: httpGet: path: /_status - port: 8000 + port: http ports: - - containerPort: 8000 + - containerPort: {{ .Values.service.targetPort }} + name: http env: - name: GUPPY_PORT value: "8000" @@ -84,6 +89,8 @@ spec: value: {{ .Values.global.tierAccessLimit | quote }} - name: NODE_ENV value: production + - name: PUBLIC_KEY_PATH + value: /guppy/jwt_public_key.pem {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/guppy/values.yaml b/helm/guppy/values.yaml index 57ef6a536..2c31c0b6d 100644 --- a/helm/guppy/values.yaml +++ b/helm/guppy/values.yaml @@ -141,6 +141,12 @@ volumes: items: - key: guppy_config.json path: guppy_config.json + - name: pcdcanalysistools-jwt-keys + secret: + secretName: "pcdcanalysistools-jwt-keys" + items: + - key: jwt_public_key.pem + path: jwt_public_key.pem # -- (bool) Automount the default service account token automountServiceAccountToken: false @@ -166,6 +172,10 @@ volumeMounts: readOnly: true mountPath: /guppy/guppy_config.json subPath: guppy_config.json + - name: "pcdcanalysistools-jwt-keys" + readOnly: true + mountPath: "/guppy/jwt_public_key.pem" + subPath: "jwt_public_key.pem" # -- (map) Resource requests and limits for the containers in the pod resources: @@ -182,6 +192,7 @@ resources: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 8000 # -- (int) The port number that the service exposes. port: - protocol: TCP @@ -219,3 +230,18 @@ partOf: "Explorer-Tab" selectorLabels: # -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl commonLabels: + + +# -- (map) Security context for the pod +podSecurityContext: {} + +# -- (map) Security context for the containers in the pod +securityContext: + {} + + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 diff --git a/helm/hatchery/README.md b/helm/hatchery/README.md index 5f9130839..86ca8072e 100644 --- a/helm/hatchery/README.md +++ b/helm/hatchery/README.md @@ -112,7 +112,7 @@ A Helm chart for gen3 Hatchery | resources.requests | map | `{"memory":"12Mi"}` | The amount of resources that the container requests | | 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 | map | `{"port":80,"targetPort":8000,"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":"hatchery-sa"}` | Service account to use or create. | diff --git a/helm/hatchery/templates/deployment.yaml b/helm/hatchery/templates/deployment.yaml index e9fcf8811..0e077bf40 100644 --- a/helm/hatchery/templates/deployment.yaml +++ b/helm/hatchery/templates/deployment.yaml @@ -56,7 +56,7 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http - containerPort: 8000 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: diff --git a/helm/hatchery/values.yaml b/helm/hatchery/values.yaml index 4878cb084..fe848e41c 100644 --- a/helm/hatchery/values.yaml +++ b/helm/hatchery/values.yaml @@ -148,6 +148,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 8000 # -- (map) Resource requests and limits for the containers in the pod resources: diff --git a/helm/indexd/templates/deployment.yaml b/helm/indexd/templates/deployment.yaml index a9e1bc90c..b4de39f0a 100644 --- a/helm/indexd/templates/deployment.yaml +++ b/helm/indexd/templates/deployment.yaml @@ -95,7 +95,7 @@ spec: {{- end }} ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: diff --git a/helm/indexd/templates/pre-install.yaml b/helm/indexd/templates/pre-install.yaml index 8e18baf1c..bbbb7dae6 100644 --- a/helm/indexd/templates/pre-install.yaml +++ b/helm/indexd/templates/pre-install.yaml @@ -15,6 +15,8 @@ spec: app: gen3job spec: automountServiceAccountToken: false + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} {{- with .Values.volumes }} volumes: {{- toYaml . | nindent 8 }} diff --git a/helm/indexd/values.yaml b/helm/indexd/values.yaml index 6a7cb5a64..00809b72f 100644 --- a/helm/indexd/values.yaml +++ b/helm/indexd/values.yaml @@ -223,6 +223,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Resource requests and limits for the containers in the pod resources: diff --git a/helm/manifestservice/templates/deployment.yaml b/helm/manifestservice/templates/deployment.yaml index b36cd8811..631d3e591 100644 --- a/helm/manifestservice/templates/deployment.yaml +++ b/helm/manifestservice/templates/deployment.yaml @@ -40,6 +40,8 @@ spec: {{- include "common.TopologySpread" . | nindent 6 }} {{- end }} serviceAccountName: {{ include "manifestservice.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} @@ -90,15 +92,16 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 10 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 80 + port: http diff --git a/helm/manifestservice/templates/service.yaml b/helm/manifestservice/templates/service.yaml index 173ba48c2..41e3b0e28 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: 80 + targetPort: http protocol: TCP name: http selector: diff --git a/helm/manifestservice/values.yaml b/helm/manifestservice/values.yaml index a99d9cf4d..45e846c8e 100644 --- a/helm/manifestservice/values.yaml +++ b/helm/manifestservice/values.yaml @@ -107,6 +107,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Service account to use or create. serviceAccount: @@ -208,3 +209,19 @@ partOf: "Workspace-tab" selectorLabels: # -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl commonLabels: + + +# -- (map) Security context for the pod +podSecurityContext: + {} + # fsGroup: 2000 + +# -- (map) Security context for the containers in the pod +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 \ No newline at end of file diff --git a/helm/metadata/templates/deployment.yaml b/helm/metadata/templates/deployment.yaml index ee870e740..b6afffc3d 100644 --- a/helm/metadata/templates/deployment.yaml +++ b/helm/metadata/templates/deployment.yaml @@ -46,10 +46,12 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: - - name: config-volume-g3auto - secret: - secretName: metadata-g3auto + # - name: config-volume-g3auto + # secret: + # secretName: metadata-g3auto - name: config-volume configMap: name: agg-mds-config @@ -106,16 +108,17 @@ spec: livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 80 + port: http ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} @@ -128,7 +131,6 @@ spec: - name: {{ .Values.initContainerName }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.initVolumeMounts }} env: - name: DB_HOST valueFrom: @@ -160,6 +162,7 @@ spec: name: metadata-dbcreds key: dbcreated optional: false + {{- with .Values.initVolumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} {{- end }} diff --git a/helm/metadata/templates/secrets.yaml b/helm/metadata/templates/secrets.yaml index 0bd639d73..e69de29bb 100644 --- a/helm/metadata/templates/secrets.yaml +++ b/helm/metadata/templates/secrets.yaml @@ -1,16 +0,0 @@ -{{- if or (not .Values.global.externalSecrets.deploy) (and .Values.global.externalSecrets.deploy .Values.externalSecrets.createK8sMetadataSecret) }} -apiVersion: v1 -kind: Secret -metadata: - name: metadata-g3auto -stringData: - {{- $randomPass := printf "%s%s" "gateway:" (randAlphaNum 32) }} - base64Authz.txt: {{ $randomPass | quote | b64enc }} - metadata.env: | - DEBUG={{ .Values.debug}} - DB_HOST={{ .Values.postgres.host }} - DB_USER={{ .Values.postgres.user }} - DB_PASSWORD={{ include "metadata.postgres.password" . }} - DB_DATABASE={{ .Values.postgres.dbname }} - ADMIN_LOGINS={{ $randomPass }} -{{- end }} \ No newline at end of file diff --git a/helm/metadata/values.yaml b/helm/metadata/values.yaml index fc3fe81b4..6de3b4f64 100644 --- a/helm/metadata/values.yaml +++ b/helm/metadata/values.yaml @@ -179,7 +179,7 @@ image: # -- (string) Docker pull policy. pullPolicy: Always # -- (string) Overrides the image tag whose default is the chart appVersion. - tag: "feat_es-7" + tag: "master" debug: false @@ -264,15 +264,15 @@ aggMdsConfig: | # -- (list) Volumes to mount to the container. volumeMounts: - - name: config-volume-g3auto - readOnly: true - mountPath: /src/.env - subPath: metadata.env + # - name: config-volume-g3auto + # readOnly: true + # mountPath: /src/.env + # subPath: metadata.env # Added an additional volume mount for new images using the / directory, while retaining the 'src' mount for backward compatibility. - - name: config-volume-g3auto - readOnly: true - mountPath: /mds/.env - subPath: metadata.env + # - name: config-volume-g3auto + # readOnly: true + # mountPath: /mds/.env + # subPath: metadata.env - name: config-volume readOnly: true mountPath: /aggregate_config.json @@ -294,15 +294,15 @@ resources: 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 - # Added an additional volume mount for new images using the / directory, while retaining the 'src' mount for backward compatibility. - - name: config-volume-g3auto - readOnly: true - mountPath: /mds/.env - subPath: metadata.env + # - name: config-volume-g3auto + # readOnly: true + # mountPath: /src/.env + # subPath: metadata.env + # # Added an additional volume mount for new images using the / directory, while retaining the 'src' mount for backward compatibility. + # - name: config-volume-g3auto + # readOnly: true + # mountPath: /mds/.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 @@ -333,6 +333,7 @@ serviceAnnotations: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 80 # -- (int) The port number that the service exposes. port: - protocol: TCP diff --git a/helm/ohif-viewer/templates/deployment.yaml b/helm/ohif-viewer/templates/deployment.yaml index 4cca4d133..e5c14864f 100644 --- a/helm/ohif-viewer/templates/deployment.yaml +++ b/helm/ohif-viewer/templates/deployment.yaml @@ -50,19 +50,20 @@ spec: readinessProbe: httpGet: path: / - port: 8080 + port: http initialDelaySeconds: 5 periodSeconds: 20 timeoutSeconds: 30 livenessProbe: httpGet: path: / - port: 8080 + port: http initialDelaySeconds: 5 periodSeconds: 60 timeoutSeconds: 30 ports: - - containerPort: 8080 + - containerPort: {{ .Values.service.targetPort }} + name: http env: - name: PORT value: "8080" diff --git a/helm/orthanc/templates/deployment.yaml b/helm/orthanc/templates/deployment.yaml index eb090458b..12d330b55 100644 --- a/helm/orthanc/templates/deployment.yaml +++ b/helm/orthanc/templates/deployment.yaml @@ -70,7 +70,8 @@ spec: periodSeconds: 60 timeoutSeconds: 30 ports: - - containerPort: 8042 + - containerPort: {{ .Values.service.targetPort }} + name: http env: - name: PGHOST valueFrom: diff --git a/helm/pcdcanalysistools/.helmignore b/helm/pcdcanalysistools/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/pcdcanalysistools/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/pcdcanalysistools/Chart.yaml b/helm/pcdcanalysistools/Chart.yaml new file mode 100644 index 000000000..e2f860bae --- /dev/null +++ b/helm/pcdcanalysistools/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +name: pcdcanalysistools +description: A Helm chart for gen3 pcdcanalysistools Service + +# 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: 1.0.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: "master" + +dependencies: + - name: common + version: 0.1.28 + repository: file://../common diff --git a/helm/pcdcanalysistools/pcdcanalysistools-secret/confighelper.py b/helm/pcdcanalysistools/pcdcanalysistools-secret/confighelper.py new file mode 100644 index 000000000..05c929e2d --- /dev/null +++ b/helm/pcdcanalysistools/pcdcanalysistools-secret/confighelper.py @@ -0,0 +1,54 @@ +""" +Originally copied from `cloud-automation/apis_configs/config_helper.py` +(renamed `confighelper.py` so it isn't overwritten by the file that cloud-automation +still mounts for backwards compatibility). + +TODO: once everyone has this independent version of PcdcAnalysisTools, remove `wsgi.py` and +`config_helper.py` here: +https://github.com/uc-cdis/cloud-automation/blob/afb750d/kube/services/PcdcAnalysisTools/PcdcAnalysisTools-deploy.yaml#L166-L177 +""" + +import json +import os + +# +# make it easy to change this for testing +XDG_DATA_HOME = os.getenv("XDG_DATA_HOME", "/usr/share/") + + +def default_search_folders(app_name): + """ + Return the list of folders to search for configuration files + """ + return [ + "%s/cdis/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/cdis/%s" % app_name, + "%s/gen3/%s" % (XDG_DATA_HOME, app_name), + "/usr/share/gen3/%s" % app_name, + "/var/www/%s" % app_name, + "/etc/gen3/%s" % app_name, + ] + + +def find_paths(file_name, app_name, search_folders=None): + """ + Search the given folders for file_name + search_folders defaults to default_search_folders if not specified + return the first path to file_name found + """ + search_folders = search_folders or default_search_folders(app_name) + possible_files = [os.path.join(folder, file_name) for folder in search_folders] + return [path for path in possible_files if os.path.exists(path)] + + +def load_json(file_name, app_name, search_folders=None): + """ + json.load(file_name) after finding file_name in search_folders + + return the loaded json data or None if file not found + """ + actual_files = find_paths(file_name, app_name, search_folders) + if not actual_files: + return {} + with open(actual_files[0], "r") as reader: + return json.load(reader) \ No newline at end of file diff --git a/helm/pcdcanalysistools/pcdcanalysistools-secret/settings.py b/helm/pcdcanalysistools/pcdcanalysistools-secret/settings.py new file mode 100644 index 000000000..7d8d57293 --- /dev/null +++ b/helm/pcdcanalysistools/pcdcanalysistools-secret/settings.py @@ -0,0 +1,148 @@ +from PcdcAnalysisTools.api import app, app_init +from os import environ +import bin.confighelper as confighelper +from pcdcutils.environment import is_env_enabled + +APP_NAME='PcdcAnalysisTools' +def load_json(file_name): + return confighelper.load_json(file_name, APP_NAME) + + +conf_data = load_json("creds.json") +config = app.config + +# ARBORIST deprecated, replaced by ARBORIST_URL +# ARBORIST_URL is initialized in app_init() directly +config["ARBORIST"] = "http://arborist-service/" + +config["INDEX_CLIENT"] = { + "host": environ.get("INDEX_CLIENT_HOST") or "http://indexd-service", + "version": "v0", + # The user should be "sheepdog", but for legacy reasons, we use "gdcapi" instead + "auth": ( + ( + environ.get("INDEXD_USER", "gdcapi"), + environ.get("INDEXD_PASS") + or conf_data.get("indexd_password", "{{indexd_password}}"), + ) + ), +} + +config["PSQLGRAPH"] = { + "host": conf_data.get("db_host", environ.get("PGHOST", "localhost")), + "user": conf_data.get("db_username", environ.get("PGUSER", "sheepdog")), + "password": conf_data.get("db_password", environ.get("PGPASSWORD", "sheepdog")), + "database": conf_data.get("db_database", environ.get("PGDB", "sheepdog")), +} + +config["FLASK_SECRET_KEY"] = conf_data.get("gdcapi_secret_key", "{{gdcapi_secret_key}}") +fence_username = conf_data.get( + "fence_username", environ.get("FENCE_DB_USER", "fence") +) +fence_password = conf_data.get( + "fence_password", environ.get("FENCE_DB_PASS", "fence") +) +fence_host = conf_data.get("fence_host", environ.get("FENCE_DB_HOST", "localhost")) +fence_database = conf_data.get( + "fence_database", environ.get("FENCE_DB_DATABASE", "fence") +) +config["PSQL_USER_DB_CONNECTION"] = "postgresql://%s:%s@%s:5432/%s" % ( + fence_username, + fence_password, + fence_host, + fence_database, +) + +hostname = conf_data.get( + "hostname", environ.get("CONF_HOSTNAME", "localhost") +) # for use by authutils +config["OIDC_ISSUER"] = "https://%s/user" % hostname +if hostname == "localhost": + config["USER_API"] = "http://fence-service/" +else: + config["USER_API"] = "https://%s/user" % hostname # for use by authutils + +# config['USER_API'] = 'http://fence-service/' +# option to force authutils to prioritize USER_API setting over the issuer from +# token when redirecting, used during local docker compose setup when the +# services are on different containers but the hostname is still localhost +config['FORCE_ISSUER'] = True + +config["DICTIONARY_URL"] = environ.get( + "DICTIONARY_URL", + "https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json", +) + +# trailing slash intentionally omitted +config['GUPPY_API'] = 'http://guppy-service' +config["FENCE"] = 'http://fence-service' +config['SERVICE_NAME'] = 'pcdcanalysistools' +config['PRIVATE_KEY_PATH'] = "/var/www/PcdcAnalysisTools/jwt_private_key.pem" +config["AUTH"] = "https://auth.service.consul:5000/v3/" +config["AUTH_ADMIN_CREDS"] = None +config["INTERNAL_AUTH"] = None +config["FAKE_AUTH"] = False +config["HMAC_ENCRYPTION_KEY"] = conf_data.get("hmac_key", environ.get("HMAC_ENCRYPTION_KEY")) + +config['SURVIVAL'] = { + 'consortium': ["INSTRuCT", "INRG", "MaGIC", "NODAL"], + 'excluded_variables': [ + { + 'label': 'Data Contributor', + 'field': 'data_contributor_id', + }, + { + 'label': 'Study', + 'field': 'studies.study_id', + }, + { + 'label': 'Treatment Arm', + 'field': 'studies.treatment_arm', + } + ], + + 'result': { + 'risktable': True, + 'survival': True + } +} + +config['TABLE_ONE'] = { + 'consortium': ["INSTRuCT", "INRG", "MaGIC", "NODAL"], + 'excluded_variables': [ + { + 'label': 'Data Contributor', + 'field': 'data_contributor_id', + }, + { + 'label': 'Study', + 'field': 'studies.study_id', + }, + { + 'label': 'Treatment Arm', + 'field': 'studies.treatment_arm', + } + ], + "enabled": True +} + +config['EXTERNAL'] = { + 'commons': [ + { + 'label': 'Genomic Data Commons', + 'value': 'gdc' + }, + { + 'label': 'Gabriella Miller Kids First', + 'value': 'gmkf' + } + ], + "commons_dict": { + "gdc": "TARGET - GDC", + "gmkf": "GMKF" + } +} + +app_init(app) +application = app +application.debug = (is_env_enabled('GEN3_DEBUG')) \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/NOTES.txt b/helm/pcdcanalysistools/templates/NOTES.txt new file mode 100644 index 000000000..c1e7e1aef --- /dev/null +++ b/helm/pcdcanalysistools/templates/NOTES.txt @@ -0,0 +1 @@ +{{ .Chart.Name }} has been deployed successfully. diff --git a/helm/pcdcanalysistools/templates/_helpers.tpl b/helm/pcdcanalysistools/templates/_helpers.tpl new file mode 100644 index 000000000..7413ac498 --- /dev/null +++ b/helm/pcdcanalysistools/templates/_helpers.tpl @@ -0,0 +1,92 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "pcdcanalysistools.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- 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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "pcdcanalysistools.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "pcdcanalysistools.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "pcdcanalysistools.labels" -}} +{{- if .Values.commonLabels }} + {{- with .Values.commonLabels }} + {{- toYaml . }} + {{- end }} +{{- else }} + {{- (include "common.commonLabels" .)}} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "pcdcanalysistools.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 "pcdcanalysistools.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "pcdcanalysistools.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + + +{{/* + Postgres Password lookup Fence +*/}} +{{- define "fence.postgres.password" -}} +{{- $localpass := (lookup "v1" "Secret" "postgres" "postgres-postgresql" ) -}} +{{- if $localpass }} +{{- default (index $localpass.data "postgres-password" | b64dec) }} +{{- else }} +{{- default .Values.secrets.fence.password }} +{{- end }} +{{- end }} + +# {{/* +# Define dictionaryUrl +# */}} +# {{- define "pcdcanalysistools.dictionaryUrl" -}} +# {{- if .Values.global }} +# {{- .Values.global.dictionaryUrl }} +# {{- else}} +# {{- .Values.dictionaryUrl }} +# {{- end }} +# {{- end }} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/aws-config.yaml b/helm/pcdcanalysistools/templates/aws-config.yaml new file mode 100644 index 000000000..a49fb9f1b --- /dev/null +++ b/helm/pcdcanalysistools/templates/aws-config.yaml @@ -0,0 +1,3 @@ +{{- if or (.Values.secrets.awsSecretAccessKey) (.Values.global.aws.awsSecretAccessKey) (.Values.global.aws.externalSecrets.enabled) }} +{{ include "common.awsconfig" . }} +{{- end -}} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/deployment.yaml b/helm/pcdcanalysistools/templates/deployment.yaml new file mode 100644 index 000000000..80174261b --- /dev/null +++ b/helm/pcdcanalysistools/templates/deployment.yaml @@ -0,0 +1,115 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pcdcanalysistools-deployment + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "pcdcanalysistools.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "pcdcanalysistools.selectorLabels" . | nindent 6 }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + {{- with .Values.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + template: + metadata: + labels: + # gen3 networkpolicy labels + public: "yes" + netnolimit: "yes" + s3: "yes" + {{- include "pcdcanalysistools.selectorLabels" . | nindent 8 }} + {{- include "common.extraLabels" . | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/pcdcanalysistools-secret.yaml") . | sha256sum }} + {{- $metricsEnabled := .Values.metricsEnabled }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = .Values.global.metricsEnabled }} + {{- end }} + {{- if eq $metricsEnabled nil }} + {{- $metricsEnabled = true }} + {{- end }} + + {{- if $metricsEnabled }} + {{- include "common.grafanaAnnotations" . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.topologySpread.enabled }} + {{- include "common.TopologySpread" . | nindent 6 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 10 }} + {{- end }} + containers: + - name: pcdcanalysistools + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 80 + - containerPort: 443 + livenessProbe: + httpGet: + path: /_status?timeout=20 + port: 80 + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 30 + readinessProbe: + initialDelaySeconds: 30 + httpGet: + path: /_status?timeout=2 + port: 80 + env: + - name: CONF_HOSTNAME + value: {{ .Values.global.hostname }} + - name: DICTIONARY_URL + value: {{ default "https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json" .Values.global.dictionaryUrl | quote }} + - name: INDEX_CLIENT_HOST + value: {{ default "http://indexd-service" .Values.global.indexdURL | quote }} + {{- if eq .Values.global.dev false }} + - name: FENCE_URL + value: https://{{ .Values.global.hostname }}/user + {{- else }} + - name: FENCE_URL + value: {{ default "http://fence-service" .Values.global.fenceURL | quote }} + {{- end }} + - name: ARBORIST_URL + value: {{ default "http://arborist-service" .Values.global.arboristURL | quote }} + {{- with .Values.authNamespace }} + - name: AUTH_NAMESPACE + value: {{ . }} + {{- end }} + # - name: REQUESTS_CA_BUNDLE + # # + # # override python 'requests' SSL certificate bundle + # # to use system trusted certs + # # which includes our private certificate authority + # # + # value: /etc/ssl/certs/ca-certificates.crt + - name: GEN3_DEBUG + value: "True" + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 10 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + # command: ["/bin/bash" ] + # args: + # - "-c" + # - "sleep infinity" \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/hpa.yaml b/helm/pcdcanalysistools/templates/hpa.yaml new file mode 100644 index 000000000..94f298246 --- /dev/null +++ b/helm/pcdcanalysistools/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if default .Values.global.autoscaling.enabled .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: pcdcanalysistools-deployment + labels: + {{- include "pcdcanalysistools.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: pcdcanalysistools-deployment + minReplicas: {{ default .Values.global.autoscaling.minReplicas .Values.autoscaling.minReplicas }} + maxReplicas: {{ default .Values.global.autoscaling.maxReplicas .Values.autoscaling.maxReplicas }} + metrics: + {{- if default .Values.global.autoscaling.targetCPUUtilizationPercentage .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ default .Values.global.autoscaling.targetCPUUtilizationPercentage .Values.autoscaling.targetCPUUtilizationPercentage}} + {{- end }} + {{- if default .Values.global.autoscaling.targetMemoryUtilizationPercentage .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + averageUtilization: {{ default .Values.global.autoscaling.targetMemoryUtilizationPercentage .Values.autoscaling.targetMemoryUtilizationPercentage }} + type: Utilization + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/jwt-keys.yaml b/helm/pcdcanalysistools/templates/jwt-keys.yaml new file mode 100644 index 000000000..322abbf5b --- /dev/null +++ b/helm/pcdcanalysistools/templates/jwt-keys.yaml @@ -0,0 +1,5 @@ +{{include "common.jwt-key-pair-secret" .}} +--- +{{include "common.jwt_public_key_setup_sa" .}} +--- +{{include "common.create_public_key_job" .}} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/netpolicy.yaml b/helm/pcdcanalysistools/templates/netpolicy.yaml new file mode 100644 index 000000000..2fb372551 --- /dev/null +++ b/helm/pcdcanalysistools/templates/netpolicy.yaml @@ -0,0 +1,2 @@ +#I believe this allows a service to connect to a DB but not sure if pcdcanalysistools needs to be able to connect to the DB +{{ include "common.db_netpolicy" . }} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/pcdcanalysistools-secret.yaml b/helm/pcdcanalysistools/templates/pcdcanalysistools-secret.yaml new file mode 100644 index 000000000..be51e06c0 --- /dev/null +++ b/helm/pcdcanalysistools/templates/pcdcanalysistools-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: pcdcanalysistools-secret +type: Opaque +data: +{{ (.Files.Glob "pcdcanalysistools-secret/*").AsSecrets | indent 2 }} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/pdb.yaml b/helm/pcdcanalysistools/templates/pdb.yaml new file mode 100644 index 000000000..2ef2de13d --- /dev/null +++ b/helm/pcdcanalysistools/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/pcdcanalysistools/templates/secret-store.yaml b/helm/pcdcanalysistools/templates/secret-store.yaml new file mode 100644 index 000000000..771c7760d --- /dev/null +++ b/helm/pcdcanalysistools/templates/secret-store.yaml @@ -0,0 +1,3 @@ +{{ if .Values.global.externalSecrets.separateSecretStore }} +{{ include "common.secretstore" . }} +{{- end }} \ No newline at end of file diff --git a/helm/pcdcanalysistools/templates/service.yaml b/helm/pcdcanalysistools/templates/service.yaml new file mode 100644 index 000000000..678d878fb --- /dev/null +++ b/helm/pcdcanalysistools/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: "pcdcanalysistools-service" + labels: + {{- include "pcdcanalysistools.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + protocol: TCP + name: http + selector: + {{- include "pcdcanalysistools.selectorLabels" . | nindent 4 }} diff --git a/helm/pcdcanalysistools/values.yaml b/helm/pcdcanalysistools/values.yaml new file mode 100644 index 000000000..4ab7a1545 --- /dev/null +++ b/helm/pcdcanalysistools/values.yaml @@ -0,0 +1,256 @@ +# Default values for pcdcanalysistools. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# Global configuration +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: + externalSecrets: + # -- (bool) Whether to use External Secrets for aws config. + enabled: false + # -- (String) Name of Secrets Manager secret. + externalSecretAwsCreds: + # -- (bool) Whether the deployment is for development purposes. + dev: true + postgres: + # -- (bool) Whether the database should be created. + dbCreate: true + # -- (string) Name of external secret. Disabled if empty + externalSecret: "" + # -- (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 + # -- (map) Controls network policy settings + netPolicy: + enabled: false + # -- (int) Number of dispatcher jobs. + dispatcherJobNum: "10" + # -- (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. + minAvailable: 1 + # -- (map) External Secrets settings. + externalSecrets: + # -- (bool) Will use ExternalSecret resources to pull secrets from Secrets Manager instead of creating them locally. Be cautious as this will override any pcdcanalysistools secrets you have deployed. + deploy: false + # -- (string) Will deploy a separate External Secret Store for this service. + separateSecretStore: false + # -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + averageCPUValue: 500m + averageMemoryValue: 500Mi + # -- (map) Karpenter topology spread configuration. + topologySpread: + # -- (bool) Whether to enable topology spread constraints for all subcharts that support it. + enabled: false + # -- (string) The topology key to use for spreading. Defaults to "topology.kubernetes.io/zone". + topologyKey: "topology.kubernetes.io/zone" + # -- (int) The maxSkew to use for topology spread constraints. Defaults to 1. + maxSkew: 1 +# -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: {} + +# -- (bool) Whether Metrics are enabled. +metricsEnabled: + +# -- (map) External Secrets settings. +externalSecrets: + # -- (bool) Whether to create the database and Secrets Manager secrets via PushSecret. + pushSecret: false + # -- (string) Will override the name of the aws secrets manager secret. Default is "Values.global.environment-.Chart.Name-creds" + dbcreds: + +# -- (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: + # -- (bool) Whether the database should be restored from s3. Default to global.postgres.dbRestore + dbRestore: false + # -- (bool) Whether the database should be created. Default to global.postgres.dbCreate + dbCreate: + # -- (string) Hostname for postgres server. This is a service override, defaults to global.postgres.host + host: + # -- (string) Database name for postgres. This is a service override, defaults to - + database: + # -- (string) Username for postgres. This is a service override, defaults to - + username: + # -- (string) Port for 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": "pcdcanalysistools" } + +# -- (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: + - pcdcanalysistools + # -- (string) Value for topology key label. + topologyKey: "kubernetes.io/hostname" + +# -- (bool) Automount the default service account token +automountServiceAccountToken: false + +# -- (int) pcdcanalysistools transactions take forever - try to let the complete before termination +terminationGracePeriodSeconds: 50 + +# -- (map) Docker image information. +image: + # -- (string) Docker repository. + repository: quay.io/pcdc/pcdcanalysistools + # -- (string) Docker pull policy. + pullPolicy: IfNotPresent + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: 1.8.4 + +# Environment Variables +authNamespace: "" + +# -- (list) of files to become volumes in the container +volumes: + - name: config-volume + secret: + secretName: "pcdcanalysistools-secret" + - name: pcdcanalysistools-jwt-keys + secret: + secretName: "pcdcanalysistools-jwt-keys" + items: + - key: jwt_private_key.pem + path: jwt_private_key.pem + +# -- (list) Volumes to mount to the container. +volumeMounts: + - name: "config-volume" + readOnly: true + mountPath: "/var/www/PcdcAnalysisTools/wsgi.py" + subPath: "settings.py" + - name: "config-volume" + readOnly: true + mountPath: "PcdcAnalysisTools/bin/settings.py" + subPath: "settings.py" + - name: "config-volume" + readOnly: true + mountPath: "PcdcAnalysisTools/bin/confighelper.py" + subPath: "confighelper.py" + - name: "pcdcanalysistools-jwt-keys" + readOnly: true + mountPath: "/var/www/PcdcAnalysisTools/jwt_private_key.pem" + subPath: "jwt_private_key.pem" + +# -- (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 memory requested + memory: 12Mi + # -- (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: 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 pcdcanalysistools secret. +secrets: + # -- (str) AWS access key ID to access the db restore job S3 bucket. Overrides global key. + awsAccessKeyId: + # -- (str) AWS secret access key ID to access the db restore job S3 bucket. Overrides global 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: "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: diff --git a/helm/peregrine/peregrine-secret/config_helper.py b/helm/peregrine/peregrine-secret/config_helper.py index 6b303beac..6bf5f592c 100644 --- a/helm/peregrine/peregrine-secret/config_helper.py +++ b/helm/peregrine/peregrine-secret/config_helper.py @@ -1,9 +1,17 @@ +""" +Originally copied from `cloud-automation/apis_configs/config_helper.py` +(renamed `confighelper.py` so it isn't overwritten by the file that cloud-automation +still mounts for backwards compatibility). + +TODO: once everyone has this independent version of sheepdog, remove `wsgi.py` and +`config_helper.py` here: +https://github.com/uc-cdis/cloud-automation/blob/afb750d/kube/services/peregrine/peregrine-deploy.yaml#L159-L170 +and update this: +https://github.com/uc-cdis/cloud-automation/blob/afb750d752f1324c2884da1efaef3cec8f9476b9/gen3/bin/kube-setup-peregrine.sh#L16 +""" + import json import os -import copy -import argparse -import re -import types # # make it easy to change this for testing @@ -43,334 +51,6 @@ def load_json(file_name, app_name, search_folders=None): """ actual_files = find_paths(file_name, app_name, search_folders) if not actual_files: - return None + return {} with open(actual_files[0], "r") as reader: - return json.load(reader) - - -def inject_creds_into_fence_config(creds_file_path, config_file_path): - creds_file = open(creds_file_path, "r") - creds = json.load(creds_file) - creds_file.close() - - # get secret values from creds.json file - db_host = _get_nested_value(creds, "db_host") - db_username = _get_nested_value(creds, "db_username") - db_password = _get_nested_value(creds, "db_password") - db_database = _get_nested_value(creds, "db_database") - hostname = _get_nested_value(creds, "hostname") - indexd_password = _get_nested_value(creds, "indexd_password") - google_client_secret = _get_nested_value(creds, "google_client_secret") - google_client_id = _get_nested_value(creds, "google_client_id") - hmac_key = _get_nested_value(creds, "hmac_key") - db_path = "postgresql://{}:{}@{}:5432/{}".format( - db_username, db_password, db_host, db_database - ) - - config_file = open(config_file_path, "r").read() - - print(" DB injected with value(s) from creds.json") - config_file = _replace(config_file, "DB", db_path) - - print(" BASE_URL injected with value(s) from creds.json") - config_file = _replace(config_file, "BASE_URL", "https://{}/user".format(hostname)) - - print(" INDEXD_PASSWORD injected with value(s) from creds.json") - config_file = _replace(config_file, "INDEXD_PASSWORD", indexd_password) - config_file = _replace(config_file, "INDEXD_USERNAME", "fence") - - print(" ENCRYPTION_KEY injected with value(s) from creds.json") - config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) - - print( - " OPENID_CONNECT/google/client_secret injected with value(s) " - "from creds.json" - ) - config_file = _replace( - config_file, "OPENID_CONNECT/google/client_secret", google_client_secret - ) - - print(" OPENID_CONNECT/google/client_id injected with value(s) from creds.json") - config_file = _replace( - config_file, "OPENID_CONNECT/google/client_id", google_client_id - ) - - open(config_file_path, "w+").write(config_file) - - -def set_prod_defaults(config_file_path): - config_file = open(config_file_path, "r").read() - - print( - " CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS set as " - "var/www/fence/fence_google_app_creds_secret.json" - ) - config_file = _replace( - config_file, - "CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS", - "/var/www/fence/fence_google_app_creds_secret.json", - ) - - print( - " CIRRUS_CFG/GOOGLE_STORAGE_CREDS set as " - "var/www/fence/fence_google_storage_creds_secret.json" - ) - config_file = _replace( - config_file, - "CIRRUS_CFG/GOOGLE_STORAGE_CREDS", - "/var/www/fence/fence_google_storage_creds_secret.json", - ) - - print(" INDEXD set as http://indexd-service/") - config_file = _replace(config_file, "INDEXD", "http://indexd-service/") - - print(" ARBORIST set as http://arborist-service/") - config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") - - print(" HTTP_PROXY/host set as cloud-proxy.internal.io") - config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") - - print(" HTTP_PROXY/port set as 3128") - config_file = _replace(config_file, "HTTP_PROXY/port", 3128) - - print(" DEBUG set to false") - config_file = _replace(config_file, "DEBUG", False) - - print(" MOCK_AUTH set to false") - config_file = _replace(config_file, "MOCK_AUTH", False) - - print(" MOCK_GOOGLE_AUTH set to false") - config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) - - print(" AUTHLIB_INSECURE_TRANSPORT set to true") - config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) - - print(" SESSION_COOKIE_SECURE set to true") - config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) - - print(" ENABLE_CSRF_PROTECTION set to true") - config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) - - open(config_file_path, "w+").write(config_file) - - -def inject_other_files_into_fence_config(other_files, config_file_path): - additional_cfgs = _get_all_additional_configs(other_files) - - config_file = open(config_file_path, "r").read() - - for key, value in additional_cfgs.iteritems(): - print(" {} set to {}".format(key, value)) - config_file = _nested_replace(config_file, key, value) - - open(config_file_path, "w+").write(config_file) - - -def _get_all_additional_configs(other_files): - """ - Attempt to parse given list of files and extract configuration variables and values - """ - additional_configs = dict() - for file_path in other_files: - try: - file_ext = file_path.strip().split(".")[-1] - if file_ext == "json": - json_file = open(file_path, "r") - configs = json.load(json_file) - json_file.close() - elif file_ext == "py": - configs = from_pyfile(file_path) - else: - print( - "Cannot load config vars from a file with extention: {}".format( - file_ext - ) - ) - except Exception as exc: - # if there's any issue reading the file, exit - print( - "Error reading {}. Cannot get configuration. Skipping this file. " - "Details: {}".format(other_files, str(exc)) - ) - continue - - if configs: - additional_configs.update(configs) - - return additional_configs - - -def _nested_replace(config_file, key, value, replacement_path=None): - replacement_path = replacement_path or key - try: - for inner_key, inner_value in value.iteritems(): - temp_path = replacement_path - temp_path = temp_path + "/" + inner_key - config_file = _nested_replace( - config_file, inner_key, inner_value, temp_path - ) - except AttributeError: - # not a dict so replace - if value is not None: - config_file = _replace(config_file, replacement_path, value) - - return config_file - - -def _replace(yaml_config, path_to_key, replacement_value, start=0, nested_level=0): - """ - Replace a nested value in a YAML file string with the given value without - losing comments. Uses a regex to do the replacement. - - Args: - yaml_config (str): a string representing a full configuration file - path_to_key (str): nested/path/to/key. The value of this key will be - replaced - replacement_value (str): Replacement value for the key from - path_to_key - """ - nested_path_to_replace = path_to_key.split("/") - - # our regex looks for a specific number of spaces to ensure correct - # level of nesting. It matches to the end of the line - search_string = ( - " " * nested_level + ".*" + nested_path_to_replace[0] + "(')?(\")?:.*\n" - ) - matches = re.search(search_string, yaml_config[start:]) - - # early return if we haven't found anything - if not matches: - return yaml_config - - # if we're on the last item in the path, we need to get the value and - # replace it in the original file - if len(nested_path_to_replace) == 1: - # replace the current key:value with the new replacement value - match_start = start + matches.start(0) + len(" " * nested_level) - match_end = start + matches.end(0) - yaml_config = ( - yaml_config[:match_start] - + "{}: {}\n".format( - nested_path_to_replace[0], - _get_yaml_replacement_value(replacement_value, nested_level), - ) - + yaml_config[match_end:] - ) - - return yaml_config - - # set new start point to past current match and move on to next match - start = matches.end(0) - nested_level += 1 - del nested_path_to_replace[0] - - return _replace( - yaml_config, - "/".join(nested_path_to_replace), - replacement_value, - start, - nested_level, - ) - - -def from_pyfile(filename, silent=False): - """ - Modeled after flask's ability to load in python files: - https://github.com/pallets/flask/blob/master/flask/config.py - - Some alterations were made but logic is essentially the same - """ - filename = os.path.abspath(filename) - d = types.ModuleType("config") - d.__file__ = filename - try: - with open(filename, mode="rb") as config_file: - exec(compile(config_file.read(), filename, "exec"), d.__dict__) - except IOError as e: - print("Unable to load configuration file ({})".format(e.strerror)) - if silent: - return False - raise - return _from_object(d) - - -def _from_object(obj): - configs = {} - for key in dir(obj): - if key.isupper(): - configs[key] = getattr(obj, key) - return configs - - -def _get_yaml_replacement_value(value, nested_level=0): - if isinstance(value, str): - return "'" + value + "'" - elif isinstance(value, bool): - return str(value).lower() - elif isinstance(value, list) or isinstance(value, set): - output = "" - for item in value: - # spaces for nested level then spaces and hyphen for each list item - output += ( - "\n" - + " " * nested_level - + " - " - + _get_yaml_replacement_value(item) - + "" - ) - return output - else: - return value - - -def _get_nested_value(dictionary, nested_path): - """ - Return a value from a dictionary given a path-like nesting of keys. - - Will default to an empty string if value cannot be found. - - Args: - dictionary (dict): a dictionary - nested_path (str): nested/path/to/key - - Returns: - ?: Value from dict - """ - replacement_value_path = nested_path.split("/") - replacement_value = copy.deepcopy(dictionary) - - for item in replacement_value_path: - replacement_value = replacement_value.get(item, {}) - - if replacement_value == {}: - replacement_value = "" - - return replacement_value - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-i", - "--creds_file_to_inject", - default="creds.json", - help="creds file to inject into the configuration yaml", - ) - parser.add_argument( - "--other_files_to_inject", - nargs="+", - help="fence_credentials.json, local_settings.py, fence_settings.py file(s) to " - "inject into the configuration yaml", - ) - parser.add_argument( - "-c", "--config_file", default="config.yaml", help="configuration yaml" - ) - args = parser.parse_args() - - inject_creds_into_fence_config(args.creds_file_to_inject, args.config_file) - set_prod_defaults(args.config_file) - - if args.other_files_to_inject: - inject_other_files_into_fence_config( - args.other_files_to_inject, args.config_file - ) + return json.load(reader) \ No newline at end of file diff --git a/helm/peregrine/peregrine-secret/settings.py b/helm/peregrine/peregrine-secret/settings.py index 1a623a907..75afb2428 100644 --- a/helm/peregrine/peregrine-secret/settings.py +++ b/helm/peregrine/peregrine-secret/settings.py @@ -1,87 +1,80 @@ -##################################################### -# DO NOT CHANGE THIS FILE # -# config updates should be done in the service code # -##################################################### - from peregrine.api import app, app_init from os import environ -# import config_helper +import bin.confighelper as confighelper + +APP_NAME = "peregrine" + -APP_NAME='peregrine' -# def load_json(file_name): -# return config_helper.load_json(file_name, APP_NAME) +def load_json(file_name): + return confighelper.load_json(file_name, APP_NAME) -# conf_data = load_json('creds.json') + +conf_data = load_json("creds.json") config = app.config -# config["AUTH"] = 'https://auth.service.consul:5000/v3/' -# config["AUTH_ADMIN_CREDS"] = None -# config["INTERNAL_AUTH"] = None # ARBORIST deprecated, replaced by ARBORIST_URL # ARBORIST_URL is initialized in app_init() directly -# config["ARBORIST"] = "http://arborist-service/" +config["ARBORIST"] = "http://arborist-service/" + -config['INDEX_CLIENT'] = { - 'host': environ.get('INDEX_CLIENT_HOST') or 'http://indexd-service', - 'version': 'v0', - 'auth': ('gdcapi', environ.get( "PGHOST") ), +config["INDEX_CLIENT"] = { + "host": environ.get("INDEX_CLIENT_HOST") or "http://indexd-service", + "version": "v0", + # The user should be "sheepdog", but for legacy reasons, we use "gdcapi" instead + "auth": ( + ( + environ.get("INDEXD_USER", "gdcapi"), + environ.get("INDEXD_PASS") + or conf_data.get("indexd_password", "{{indexd_password}}"), + ) + ), } -# config["FAKE_AUTH"] = environ.get( "FAKE_AUTH", False) + config["PSQLGRAPH"] = { - 'host': environ.get( "PGHOST"), - 'user': environ.get( "PGUSER"), - 'password': environ.get( "PGPASSWORD"), - 'database': environ.get( "PGDB"), + "host": environ.get("PGHOST") or conf_data.get("db_host", "{{db_host}}"), + "user": environ.get("PGUSER") or conf_data.get("db_username", "{{db_username}}"), + "password": environ.get("PGPASSWORD") + or conf_data.get("db_password", "{{db_password}}"), + "database": environ.get("PGDB") or conf_data.get("db_database", "{{db_database}}"), } -config['HMAC_ENCRYPTION_KEY'] = environ.get( "HMAC_ENCRYPTION_KEY") -config['FLASK_SECRET_KEY'] = environ.get( "FLASK_SECRET_KEY") - -fence_username = environ.get( "FENCE_DB_USER") -fence_password = environ.get( "FENCE_DB_PASS") -fence_host = environ.get( "FENCE_DB_HOST") -fence_database = environ.get( "FENCE_DB_DBNAME") -config['PSQL_USER_DB_CONNECTION'] = 'postgresql://%s:%s@%s:5432/%s' % (fence_username, fence_password, fence_host, fence_database) - -config['DICTIONARY_URL'] = environ.get('DICTIONARY_URL','https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json') +fence_username = environ.get("FENCE_DB_USER") or conf_data.get( + "fence_username", "{{fence_username}}" +) +fence_password = environ.get("FENCE_DB_PASS") or conf_data.get( + "fence_password", "{{fence_password}}" +) +fence_host = environ.get("FENCE_DB_HOST") or conf_data.get( + "fence_host", "{{fence_host}}" +) +fence_database = environ.get("FENCE_DB_DBNAME") or conf_data.get( + "fence_database", "{{fence_database}}" +) +config["PSQL_USER_DB_CONNECTION"] = "postgresql://%s:%s@%s:5432/%s" % ( + fence_username, + fence_password, + fence_host, + fence_database, +) -# config['SUBMISSION'] = { -# 'bucket': conf_data.get( 'bagit_bucket', '' ) -# } -# config['STORAGE'] = { -# "s3": -# { -# "access_key": conf_data.get( 's3_access', '' ), -# 'secret_key': conf_data.get( 's3_secret', '' ) -# } -# } +config["DICTIONARY_URL"] = environ.get( + "DICTIONARY_URL", + "https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json", +) -hostname = environ.get("CONF_HOSTNAME") -config['OIDC_ISSUER'] = 'https://%s/user' % hostname - -config['OAUTH2'] = { - 'client_id': "conf_data.get('oauth2_client_id', '{{oauth2_client_id}}')", - 'client_secret': "conf_data.get('oauth2_client_secret', '{{oauth2_client_secret}}')", - 'api_base_url': 'https://%s/user/' % hostname, - 'authorize_url': 'https://%s/user/oauth2/authorize' % hostname, - 'access_token_url': 'https://%s/user/oauth2/token' % hostname, - 'refresh_token_url': 'https://%s/user/oauth2/token' % hostname, - 'client_kwargs': { - 'redirect_uri': 'https://%s/api/v0/oauth2/authorize' % hostname, - 'scope': 'openid data user', - }, - # deprecated key values, should be removed after all commons use new oidc - 'internal_oauth_provider': 'http://fence-service/oauth2/', - 'oauth_provider': 'https://%s/user/oauth2/' % hostname, - 'redirect_uri': 'https://%s/api/v0/oauth2/authorize' % hostname -} +hostname = conf_data.get( + "hostname", environ.get("CONF_HOSTNAME", "localhost") +) # for use by authutils +config["OIDC_ISSUER"] = "https://%s/user" % hostname +if hostname == "localhost": + config["USER_API"] = "http://fence-service/" +else: + config["USER_API"] = "https://%s/user" % hostname # for use by authutils -config['USER_API'] = environ.get('FENCE_URL') or 'http://fence-service/' # use the USER_API URL instead of the public issuer URL to accquire JWT keys -config['FORCE_ISSUER'] = True -print(config) +config["FORCE_ISSUER"] = True app_init(app) application = app -application.debug = (environ.get('GEN3_DEBUG') == "True") +application.debug = environ.get("GEN3_DEBUG") == "True" \ No newline at end of file diff --git a/helm/peregrine/templates/deployment.yaml b/helm/peregrine/templates/deployment.yaml index 646a44034..d79921128 100644 --- a/helm/peregrine/templates/deployment.yaml +++ b/helm/peregrine/templates/deployment.yaml @@ -96,7 +96,7 @@ spec: secretKeyRef: name: indexd-service-creds key: sheepdog - optional: false + optional: true - name: PGHOST valueFrom: secretKeyRef: @@ -158,11 +158,11 @@ spec: value: {{ .Values.global.hostname | quote }} {{- with .Values.volumeMounts }} volumeMounts: - {{- toYaml . | nindent 10 }} + {{- toYaml . | nindent 12 }} {{- end }} ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: diff --git a/helm/peregrine/values.yaml b/helm/peregrine/values.yaml index 29bb346b8..f8480e1ad 100644 --- a/helm/peregrine/values.yaml +++ b/helm/peregrine/values.yaml @@ -183,6 +183,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Configuration for network policies created by this chart. Only relevant if "global.netPolicy.enabled" is set to true netPolicy: @@ -235,6 +236,11 @@ volumeMounts: readOnly: true mountPath: "peregrine/bin/settings.py" subPath: "settings.py" + - name: "config-volume" + readOnly: true + mountPath: "peregrine/bin/confighelper.py" + subPath: "config_helper.py" + # 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". diff --git a/helm/portal/defaults/gitops.json b/helm/portal/defaults/gitops.json index dfcd0dcb3..54408911c 100644 --- a/helm/portal/defaults/gitops.json +++ b/helm/portal/defaults/gitops.json @@ -348,4 +348,4 @@ }] } } -} +} \ No newline at end of file diff --git a/helm/portal/templates/deployment.yaml b/helm/portal/templates/deployment.yaml index 291b67463..55d15babe 100644 --- a/helm/portal/templates/deployment.yaml +++ b/helm/portal/templates/deployment.yaml @@ -42,6 +42,8 @@ spec: {{- include "common.TopologySpread" . | nindent 6 }} {{- end }} serviceAccountName: {{ include "portal.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} @@ -67,12 +69,19 @@ spec: name: "privacy-policy" optional: true {{- end }} + - name: nginx-config + configMap: + name: portal-nginx - name: extra-images-config configMap: name: portal-extra-images optional: true - name: extra-images emptyDir: {} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} initContainers: {{- if .Values.extraImages }} - name: init @@ -133,6 +142,8 @@ spec: containers: - name: portal image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} imagePullPolicy: {{ .Values.image.pullPolicy }} # livenessProbe: # httpGet: @@ -149,14 +160,15 @@ spec: {{- else }} path: / {{- end }} - port: 80 - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 30 + port: http + initialDelaySeconds: 30 + periodSeconds: 60 + timeoutSeconds: 60 resources: {{- toYaml .Values.resources | nindent 12 }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http - containerPort: 443 # command: # - /bin/bash @@ -170,6 +182,8 @@ spec: value: "false" - name: NODE_ENV value: "dev" + - name: DICTIONARY_URL + value: {{ .Values.global.dictionaryUrl }} - name: APP value: {{ .Values.global.portalApp | quote }} {{- if .Values.gitops.gen3Bundle }} @@ -248,6 +262,10 @@ spec: - name: DATA_UPLOAD_BUCKET value: {{ .Values.global.dataUploadBucket }} {{- end }} + {{- with .Values.gearboxS3Bucket }} + - name: GEARBOX_S3_BUCKET + value: {{ . }} + {{- end }} volumeMounts: {{- if .Values.extraImages }} - name: extra-images @@ -261,8 +279,14 @@ spec: mountPath: /data-portal/custom/ {{- else }} - name: "config-volume" - mountPath: "/data-portal/data/config/gitops.json" + mountPath: "/data-portal/data/config/pcdc.json" subPath: "gitops.json" + # - name: "nginx-config" + # mountPath: "/etc/nginx/conf.d/nginx.conf" + # subPath: "nginx.conf" + # - name: "nginx-config" + # mountPath: "/etc/nginx/nginx.conf" + # subPath: "main" - name: "config-volume" mountPath: "/data-portal/custom/logo/gitops-logo.png" subPath: "gitops-logo.png" diff --git a/helm/portal/templates/job.yaml b/helm/portal/templates/job.yaml index bd78d8005..2af65deb9 100644 --- a/helm/portal/templates/job.yaml +++ b/helm/portal/templates/job.yaml @@ -102,14 +102,15 @@ spec: {{- else }} path: / {{- end }} - port: 80 + port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 30 resources: {{- toYaml .Values.resources | nindent 12 }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http - containerPort: 443 command: - /bin/bash diff --git a/helm/portal/templates/nginx-conf.yaml b/helm/portal/templates/nginx-conf.yaml new file mode 100644 index 000000000..143272c5b --- /dev/null +++ b/helm/portal/templates/nginx-conf.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: portal-nginx +data: + main: | + # For more information on configuration, see: + # * Official English Documentation: http://nginx.org/en/docs/ + # * Official Russian Documentation: http://nginx.org/ru/docs/ + + user nginx; + worker_processes auto; + error_log /var/log/nginx/error.log notice; + pid /run/nginx.pid; + + # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. + include /usr/share/nginx/modules/*.conf; + + events { + worker_connections 1024; + } + + http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + types_hash_max_size 4096; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + server { + listen 8000; + listen [::]:8000; + server_name _; + root /usr/share/nginx/html; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + error_page 404 /404.html; + location = /404.html { + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + } + } + + # Settings for a TLS enabled server. + # + # server { + # listen 443 ssl; + # listen [::]:443 ssl; + # http2 on; + # server_name _; + # root /usr/share/nginx/html; + # + # ssl_certificate "/etc/pki/nginx/server.crt"; + # ssl_certificate_key "/etc/pki/nginx/private/server.key"; + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 10m; + # ssl_ciphers PROFILE=SYSTEM; + # ssl_prefer_server_ciphers on; + # + # # Load configuration files for the default server block. + # include /etc/nginx/default.d/*.conf; + # + # error_page 404 /404.html; + # location = /404.html { + # } + # + # error_page 500 502 503 504 /50x.html; + # location = /50x.html { + # } + # } + } + nginx.conf: | + ## + # Note that this file actually winds up at + # /etc/nginx/conf.d/nginx.conf + # , and is loaded by /etc/nginx/nginx.conf in an http{} block + ## + + ## + # Logging Settings + # The http_x_* headers are set by the gen3 reverse proxy: + # kube/services/revproxy/ + ## + log_format json '{"gen3log": "nginx", ' + '"date_access": "$time_iso8601", ' + '"user_id": "$http_x_userid", ' + '"request_id": "$http_x_reqid", ' + '"session_id": "$http_x_sessionid", ' + '"visitor_id": "$http_x_visitorid", ' + '"network_client_ip": "$http_x_forwarded_for", ' + '"network_bytes_write": $body_bytes_sent, ' + '"http_response_time": "$request_time", ' + '"http_status_code": $status, ' + '"http_request": "$request_uri", ' + '"http_verb": "$request_method", ' + '"http_referer": "$http_referer", ' + '"http_useragent": "$http_user_agent", ' + '"message": "$request"}'; + + log_format aws '$http_x_forwarded_for - $http_x_userid [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + + access_log /dev/stdout json; + + server { + listen 8080 default_server; + ssl_certificate /mnt/ssl/nginx.crt; + ssl_certificate_key /mnt/ssl/nginx.key; + server_tokens off; + + root /data-portal; + index index.html index.htm; + + # dev.html signals dev mode - for developer testing + rewrite ^(\/\w+)?\/dev.html.+$ $1/dev.html; + + # Block all access to things like .git or .htaccess + location ~ /\. { + deny all; + } + + # Block all access to package and config files + # Note if WAF is deployed this should already be handled by WAF + location ~ package.json$ { + deny all; + } + location ~ package-lock.json$ { + deny all; + } + location ^~ /npm-debug.log { + deny all; + } + location ^~ /tsconfig.json { + deny all; + } + location ^~ /webpack.config.js { + deny all; + } + location ^~ /yarn.lock { + deny all; + } + location ^~ /nginx.conf { + deny all; + } + + location ~* \.(?:manifest|appcache|html?|xml|json)$ { + expires -1; + # access_log logs/static.log; # I don't usually include a static log + } + + location ~* \.(?:css|js)$ { + try_files $uri =404; + expires 1y; + access_log off; + add_header Cache-Control "public"; + } + + # Any route that doesn't have a file extension (e.g. /devices) + location / { + try_files $uri /index.html; + } + } \ No newline at end of file diff --git a/helm/portal/templates/service.yaml b/helm/portal/templates/service.yaml index 971503f49..182b26638 100644 --- a/helm/portal/templates/service.yaml +++ b/helm/portal/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: 80 + targetPort: http protocol: TCP name: http selector: diff --git a/helm/portal/values.yaml b/helm/portal/values.yaml index 3ac273375..e5301cd1c 100644 --- a/helm/portal/values.yaml +++ b/helm/portal/values.yaml @@ -156,6 +156,7 @@ service: type: ClusterIP # -- (int) The port number that the service exposes. port: 80 + targetPort: 80 # -- (map) Node selector to apply to the pod nodeSelector: {} diff --git a/helm/requestor/templates/deployment.yaml b/helm/requestor/templates/deployment.yaml index 7aedbaeee..0c5346080 100644 --- a/helm/requestor/templates/deployment.yaml +++ b/helm/requestor/templates/deployment.yaml @@ -96,16 +96,17 @@ spec: livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 80 + port: http ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http {{- with .Values.volumeMounts }} volumeMounts: {{- toYaml . | nindent 10 }} diff --git a/helm/requestor/values.yaml b/helm/requestor/values.yaml index 9a24d751b..edb3ab203 100644 --- a/helm/requestor/values.yaml +++ b/helm/requestor/values.yaml @@ -240,6 +240,7 @@ args: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 80 # -- (int) The port number that the service exposes. port: - protocol: TCP diff --git a/helm/revproxy/README.md b/helm/revproxy/README.md index 227d9c686..b6528536c 100644 --- a/helm/revproxy/README.md +++ b/helm/revproxy/README.md @@ -77,6 +77,8 @@ A Helm chart for gen3 revproxy | netPolicy | map | `{"egressApps":["portal","sowerjob"],"ingressApps":["portal","sowerjob"]}` | Configuration for network policies created by this chart. Only relevant if "global.netPolicy.enabled" is set to true | | netPolicy.egressApps | array | `["portal","sowerjob"]` | List of apps that this app requires egress to | | netPolicy.ingressApps | array | `["portal","sowerjob"]` | List of app labels that require ingress to this service | +| nginx.resolver | string | `"kube-dns.kube-system.svc.cluster.local"` | | +| nginx.user | string | `"nginx"` | | | 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. | @@ -99,7 +101,7 @@ A Helm chart for gen3 revproxy | 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 | map | `{"port":80,"targetPort":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. | diff --git a/helm/revproxy/gen3.nginx.conf/amanuensis-service.conf b/helm/revproxy/gen3.nginx.conf/amanuensis-service.conf new file mode 100644 index 000000000..e9fffae2c --- /dev/null +++ b/helm/revproxy/gen3.nginx.conf/amanuensis-service.conf @@ -0,0 +1,24 @@ +location /amanuensis/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + + proxy_next_upstream off; + proxy_set_header Host $host; + proxy_set_header Authorization "$access_token"; + proxy_set_header X-Forwarded-For "$realip"; + proxy_set_header X-UserId "$userid"; + proxy_set_header X-SessionId "$session_id"; + proxy_set_header X-VisitorId "$visitor_id"; + + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + send_timeout 300; + + set $proxy_service "amanuensis"; + # set $upstream http://amanuensis-service.$namespace.svc.cluster.local; + set $upstream http://amanuensis-service$des_domain; + rewrite ^/amanuensis/(.*) /$1 break; + proxy_pass $upstream; +} \ No newline at end of file diff --git a/helm/revproxy/gen3.nginx.conf/gearbox-middleware-service.conf b/helm/revproxy/gen3.nginx.conf/gearbox-middleware-service.conf new file mode 100644 index 000000000..28c6e8f60 --- /dev/null +++ b/helm/revproxy/gen3.nginx.conf/gearbox-middleware-service.conf @@ -0,0 +1,41 @@ + location /gearbox-middleware/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + + set $proxy_service "gearbox-middleware-service"; + set $upstream http://gearbox-middleware-service$des_domain; + rewrite ^/gearbox-middleware/(.*) /$1 break; + proxy_pass $upstream; + proxy_redirect http://$host/ https://$host/gearbox-middleware/; + client_max_body_size 0; + } + + location /gearbox-middleware-admin/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + set $authz_resource "/gearbox_gateway"; + set $authz_method "access"; + set $authz_service "gearbox_gateway"; + # be careful - sub-request runs in same context as this request + auth_request /gen3-authz; + + set $gearbox_middleware_password "Basic ${gearbox_middleware_b64}"; + + # For testing: + #add_header Set-Cookie "X-Frickjack=${gearbox_middleware_password};Path=/;Max-Age=600"; + set $proxy_service "gearbox-middleware-service"; + set $upstream http://gearbox-middleware-service$des_domain; + rewrite ^/gearbox-admin/(.*) /$1 break; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For "$realip"; + proxy_set_header X-UserId "$userid"; + proxy_set_header X-SessionId "$session_id"; + proxy_set_header X-VisitorId "$visitor_id"; + proxy_set_header Authorization "$gearbox_middleware_password"; + + proxy_pass $upstream; + proxy_redirect http://$host/ https://$host/gearbox-middleware-admin/; + client_max_body_size 0; + } diff --git a/helm/revproxy/gen3.nginx.conf/gearbox-service.conf b/helm/revproxy/gen3.nginx.conf/gearbox-service.conf new file mode 100644 index 000000000..e31d55b96 --- /dev/null +++ b/helm/revproxy/gen3.nginx.conf/gearbox-service.conf @@ -0,0 +1,41 @@ + location /gearbox/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + + set $proxy_service "gearbox-service"; + set $upstream http://gearbox-service$des_domain; + rewrite ^/gearbox/(.*) /$1 break; + proxy_pass $upstream; + proxy_redirect http://$host/ https://$host/gearbox/; + client_max_body_size 0; + } + + location /gearbox-admin/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + set $authz_resource "/gearbox_gateway"; + set $authz_method "access"; + set $authz_service "gearbox_gateway"; + # be careful - sub-request runs in same context as this request + auth_request /gen3-authz; + + set $gearbox_password "Basic ${gearbox_b64}"; + + # For testing: + #add_header Set-Cookie "X-Frickjack=${gearbox_password};Path=/;Max-Age=600"; + set $proxy_service "gearbox-service"; + set $upstream http://gearbox-service$des_domain; + rewrite ^/gearbox-admin/(.*) /$1 break; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For "$realip"; + proxy_set_header X-UserId "$userid"; + proxy_set_header X-SessionId "$session_id"; + proxy_set_header X-VisitorId "$visitor_id"; + proxy_set_header Authorization "$gearbox_password"; + + proxy_pass $upstream; + proxy_redirect http://$host/ https://$host/gearbox-admin/; + client_max_body_size 0; + } diff --git a/helm/revproxy/gen3.nginx.conf/pcdcanalysistools-service.conf b/helm/revproxy/gen3.nginx.conf/pcdcanalysistools-service.conf new file mode 100644 index 000000000..1244faf21 --- /dev/null +++ b/helm/revproxy/gen3.nginx.conf/pcdcanalysistools-service.conf @@ -0,0 +1,26 @@ +location /analysis/ { + if ($csrf_check !~ ^ok-\S.+$) { + return 403 "failed csrf check"; + } + + proxy_next_upstream off; + # Forward the host and set Subdir header so api + # knows the original request path for hmac signing + proxy_set_header Host $host; + proxy_set_header Subdir /api; + proxy_set_header Authorization "$access_token"; + proxy_set_header X-Forwarded-For "$realip"; + proxy_set_header X-UserId "$userid"; + proxy_set_header X-SessionId "$session_id"; + proxy_set_header X-VisitorId "$visitor_id"; + + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + send_timeout 300; + + set $proxy_service "pcdcanalysistools"; + set $upstream http://pcdcanalysistools-service.default.svc.cluster.local; + rewrite ^/analysis/(.*) /$1 break; + proxy_pass $upstream; +} \ No newline at end of file diff --git a/helm/revproxy/nginx/helpers.js b/helm/revproxy/nginx/helpers.js index eaee1b1df..586ea3a46 100644 --- a/helm/revproxy/nginx/helpers.js +++ b/helm/revproxy/nginx/helpers.js @@ -1,13 +1,13 @@ /** * This is a helper script used in the reverse proxy * Note that this is not technically javascript, but nginscript (or njs) - * See here for info: + * See here for info: * - http://nginx.org/en/docs/njs/ * - https://www.nginx.com/blog/introduction-nginscript/ */ /** global supporting atob polyfill below */ -var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; // default threshold for assigning a service to production // e.g. weight of 0 would mean all services are assigned to production var DEFAULT_WEIGHT = 0; @@ -17,20 +17,23 @@ var DEFAULT_WEIGHT = 0; * https://github.com/davidchambers/Base64.js/blob/master/base64.js */ function atob(input) { - var str = String(input).replace(/[=]+$/, ''); // #31: ExtendScript bad parse of /= + var str = String(input).replace(/[=]+$/, ""); // #31: ExtendScript bad parse of /= if (str.length % 4 == 1) { return input; } for ( // initialize result and counters - var bc = 0, bs, buffer, idx = 0, output = ''; + var bc = 0, bs, buffer, idx = 0, output = ""; // get next character - buffer = str.charAt(idx++); + (buffer = str.charAt(idx++)); // character found in table? initialize bit storage and add its ascii value; - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - // and if not first of each 4 characters, - // convert the first 8 bits to one ascii character - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ~buffer && + ((bs = bc % 4 ? bs * 64 + buffer : buffer), + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) + ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))) + : 0 ) { // try to find character in table (0-63, not found => -1) buffer = chars.indexOf(buffer); @@ -53,7 +56,9 @@ function userid(req, res) { if (token) { // note - raw token is secret, so do not expose in userid - var raw = atob((token.split('.')[1] || "").replace('-', '+').replace('_', '/')); + var raw = atob( + (token.split(".")[1] || "").replace("-", "+").replace("_", "/") + ); if (raw) { try { var data = JSON.parse(raw); @@ -73,7 +78,7 @@ function userid(req, res) { */ function MathAbs(x) { x = +x; - return (x > 0) ? x : 0 - x; + return x > 0 ? x : 0 - x; } /** @@ -83,9 +88,10 @@ function MathAbs(x) { * @param s - string to hash */ function simpleHash(s) { - var i, hash = 0; + var i, + hash = 0; for (i = 0; i < s.length; i++) { - hash += (s[i].charCodeAt() * (i+1)); + hash += s[i].charCodeAt() * (i + 1); } // mod 100 b/c we want a percentage range (ie 0-99) return MathAbs(hash) % 100; @@ -103,36 +109,36 @@ function simpleHash(s) { function selectRelease(hash_res, w) { // determine release by comparing hash val to service weight if (hash_res < parseInt(w)) { - return 'canary'; + return "canary"; } - return 'production'; + return "production"; } function getWeight(service, weights) { - if (typeof weights[service] === 'undefined') { - return weights['default']; + if (typeof weights[service] === "undefined") { + return weights["default"]; } return weights[service]; } function releasesObjToString(releases) { - var res = ''; + var res = ""; for (var service in releases) { if (releases.hasOwnProperty(service)) { - res = res + service + '.' + releases[service] + '&'; + res = res + service + "." + releases[service] + "&"; } } return res; } /** - * Checks cookie (dev_canaries or service_releases) + * Checks cookie (dev_canaries or service_releases) * for service release versions and assigns * release versions for services not in the cookie based * on hash value and the percent weight of the canary. * If the weight for a service is 0, it ignores the cookie * and sets the release to production. - * + * * @param req - nginx request object * @return a string of service assignments. E.g: * "fence.canary&sheepdog.production&" @@ -143,19 +149,27 @@ function getServiceReleases(req) { // developer override can force canary even when canary has // been deployed for general users by setting the canary weights to zero // - var devOverride= !!req.variables['cookie_dev_canaries']; - var release_cookie = req.variables['cookie_dev_canaries'] || req.variables['cookie_service_releases'] || ''; + var devOverride = !!req.variables["cookie_dev_canaries"]; + var release_cookie = + req.variables["cookie_dev_canaries"] || + req.variables["cookie_service_releases"] || + ""; // services to assign to a service (edit this if adding a new canary service) - var services = ['fence', 'fenceshib', 'sheepdog', 'indexd', 'peregrine']; + var services = ["fence", "fenceshib", "sheepdog", "indexd", "peregrine"]; // weights for services - if given a default weight, use it; else use the default weight from this file - var canary_weights = JSON.parse(req.variables['canary_percent_json']); - if (typeof canary_weights['default'] === 'undefined') { - canary_weights['default'] = DEFAULT_WEIGHT + var canary_weights = JSON.parse(req.variables["canary_percent_json"]); + if (typeof canary_weights["default"] === "undefined") { + canary_weights["default"] = DEFAULT_WEIGHT; } else { - canary_weights['default'] = parseInt(canary_weights['default']) + canary_weights["default"] = parseInt(canary_weights["default"]); } // the string to be hashed - var hash_str = ['app', req.variables['realip'], req.variables['http_user_agent'], req.variables['date_gmt']].join(); + var hash_str = [ + "app", + req.variables["realip"], + req.variables["http_user_agent"], + req.variables["date_gmt"], + ].join(); var hash_res = -1; // for each service: @@ -163,17 +177,20 @@ function getServiceReleases(req) { // else if it's in the cookie, use that release // else select one by hashing and comparing to weight var updated_releases = {}; - for (var i=0; i < services.length; i++) { + for (var i = 0; i < services.length; i++) { var service = services[i]; - var parsed_release = release_cookie.match(service+'\.(production|canary)'); - if ((!devOverride) && getWeight(service, canary_weights) === 0) { - updated_releases[service] = 'production'; + var parsed_release = release_cookie.match(service + ".(production|canary)"); + if (!devOverride && getWeight(service, canary_weights) === 0) { + updated_releases[service] = "production"; } else if (!parsed_release) { // if we haven't yet generated a hash value, do that now if (hash_res < 0) { hash_res = simpleHash(hash_str); } - updated_releases[service] = selectRelease(hash_res, getWeight(service, canary_weights)); + updated_releases[service] = selectRelease( + hash_res, + getWeight(service, canary_weights) + ); } else { // append the matched values from the cookie updated_releases[service] = parsed_release[1]; @@ -204,24 +221,27 @@ function getServiceReleases(req) { * to not include this header */ function isCredentialsAllowed(req) { - if (!!req.variables['http_origin']) { - var origins = JSON.parse(req.variables['origins_allow_credentials'] || '[]') || []; + if (!!req.variables["http_origin"]) { + var origins = + JSON.parse(req.variables["origins_allow_credentials"] || "[]") || []; for (var i = 0; i < origins.length; i++) { // cannot use === to compare byte strings, whose "typeof" is also confusingly "string" - if (origins[i].fromUTF8().toLowerCase().trim() === - req.variables['http_origin'].fromUTF8().toLowerCase().trim()) { - return 'true'; + if ( + origins[i].fromUTF8().toLowerCase().trim() === + req.variables["http_origin"].fromUTF8().toLowerCase().trim() + ) { + return "true"; } } } - return ''; + return ""; } /** * Test whether the given ipAddrStr is in the global blackListStr. * Currently does not support CIDR format - just list of IP's - * - * @param {string} ipAddrStr + * + * @param {string} ipAddrStr * @param {string} blackListStr comma separated black list - defaults to globalBlackListStr (see below) * @return {boolean} true if ipAddrStr is in the black list */ @@ -232,19 +252,19 @@ function isOnBlackList(ipAddrStr, blackListStr) { /** * Call via nginx.conf js_set after setting the blackListStr and * ipAddrStr variables via set: - * + * * set blackListStr="whatever" * set ipAddrStr="whatever" * js_set blackListCheck checkBlackList - * + * * Note: kube-setup-revproxy generates gen3-blacklist.conf - which * gets sucked into the nginx.conf config - * - * @param {Request} req - * @param {Response} res + * + * @param {Request} req + * @param {Response} res * @return "ok" or "block" - fail to "ok" in ambiguous situation */ -function checkBlackList(req,res) { +function checkBlackList(req, res) { var ipAddrStr = req.variables["ip_addr_str"]; var blackListStr = req.variables["black_list_str"]; @@ -254,30 +274,34 @@ function checkBlackList(req,res) { return "ok"; // + "-" + ipAddrStr + "-" + blackListStr; } - /** * Handle the js_content callout from /workspace-authorize. * Basically - redirect to a subdomain /wts/authorize endpoint * based on the state=SUBDOMAIN-... query parameter with * some guards to stop attacks. - * - * @param {*} req - * @param {*} res + * + * @param {*} req + * @param {*} res */ function gen3_workspace_authorize_handler(req) { - var subdomain = ''; + var subdomain = ""; var query = req.variables["args"] || ""; var matchGroups = null; - if (matchGroups = query.match(/(^state=|&state=)(\w+)-/)) { + if ((matchGroups = query.match(/(^state=|&state=)(\w+)-/))) { subdomain = matchGroups[2]; - var location = "https://" + subdomain + "." + req.variables["host"] + - "/wts/oauth2/authorize?" + query; + var location = + "https://" + + subdomain + + "." + + req.variables["host"] + + "/wts/oauth2/authorize?" + + query; req.return(302, location); } else { - req.headersOut["Content-Type"] = "application/json" + req.headersOut["Content-Type"] = "application/json"; req.return(400, '{ "status": "redirect failed validation" }'); } } -export default {userid, isCredentialsAllowed}; +export default { userid, isCredentialsAllowed }; diff --git a/helm/revproxy/nginx/nginx.conf b/helm/revproxy/nginx/nginx.conf index c38743d93..27bd7c9a3 100644 --- a/helm/revproxy/nginx/nginx.conf +++ b/helm/revproxy/nginx/nginx.conf @@ -1,6 +1,6 @@ -user nginx; +user {{ .Values.nginx.user }}; worker_processes 4; -pid /var/run/nginx.pid; +pid {{ .Values.nginx.pidFile }}; load_module modules/ngx_http_js_module.so; load_module modules/ngx_http_perl_module.so; @@ -19,6 +19,8 @@ env DES_NAMESPACE; env MAINTENANCE_MODE; env INDEXD_AUTHZ; env MDS_AUTHZ; +env GEARBOX_AUTHZ; +env GEARBOX_MIDDLEWARE_AUTHZ; env FRONTEND_ROOT; env DOCUMENT_URL; @@ -38,6 +40,13 @@ http { port_in_redirect off; server_tokens off; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # For websockets map $http_upgrade $connection_upgrade { default upgrade; @@ -152,7 +161,10 @@ map $http_user_agent $loggable { perl_set $indexd_b64 'sub { $_ = $ENV{"INDEXD_AUTHZ"}; chomp; return "$_"; }'; # # metadata service password for admin endpoint perl_set $mds_b64 'sub { $_ = $ENV{"MDS_AUTHZ"}; chomp; return "$_"; }'; - + # gearbox service password for admin endpoint + perl_set $gearbox_b64 'sub { $_ = $ENV{"GEARBOX_AUTHZ"}; chomp; return "$_"; }'; + #gearbox-middleware service password for admin endpoint + perl_set $gearbox_middleware_b64 'sub { $_ = $ENV{"GEARBOX_MIDDLEWARE_AUTHZ"}; chomp; return "$_"; }'; server { listen 6567; @@ -169,7 +181,12 @@ map $http_user_agent $loggable { } server { - listen 80; + listen {{ .Values.service.targetPort }}; + + # this is here for gearbox I believe + location /login { + try_files $uri /index.html; + } server_tokens off; proxy_hide_header server; @@ -214,7 +231,7 @@ map $http_user_agent $loggable { # see https://www.nginx.com/blog/dns-service-discovery-nginx-plus/ # https://distinctplace.com/2017/04/19/nginx-resolver-explained/ # - resolver kube-dns.kube-system.svc.cluster.local ipv6=off; + resolver {{ .Values.nginx.resolver }} ipv6=off; set $access_token ""; set $csrf_check "ok-tokenauth"; @@ -344,4 +361,4 @@ map $http_user_agent $loggable { return 200 'You are running the Helm version of this commons'; } } -} +} \ No newline at end of file diff --git a/helm/revproxy/templates/configMaps.yaml b/helm/revproxy/templates/configMaps.yaml index eb0d5655e..ff7b802bb 100644 --- a/helm/revproxy/templates/configMaps.yaml +++ b/helm/revproxy/templates/configMaps.yaml @@ -40,5 +40,5 @@ metadata: data: {{- range $path, $bytes := .Files.Glob "nginx/*" }} {{ ($a := split "/" $path)._1 }}: | - {{- $bytes | toString | nindent 4 }} + {{- tpl ($bytes | toString) $ | nindent 4 }} {{- end}} diff --git a/helm/revproxy/templates/deployment.yaml b/helm/revproxy/templates/deployment.yaml index d7968cbd3..a5e4e31dd 100644 --- a/helm/revproxy/templates/deployment.yaml +++ b/helm/revproxy/templates/deployment.yaml @@ -60,6 +60,8 @@ spec: topologyKey: "kubernetes.io/hostname" automountServiceAccountToken: false volumes: + - emptyDir: {} + name: nginx-logs - name: revproxy-conf configMap: name: revproxy-nginx-conf @@ -77,19 +79,20 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http - containerPort: 443 - containerPort: 6567 livenessProbe: httpGet: path: /_status - port: 80 + port: http initialDelaySeconds: 5 periodSeconds: 3000 readinessProbe: httpGet: path: /_status - port: 80 + port: http resources: {{- toYaml .Values.resources | nindent 12 }} env: @@ -125,7 +128,21 @@ spec: name: metadata-g3auto key: base64Authz.txt optional: true + - name: GEARBOX_AUTHZ + valueFrom: + secretKeyRef: + name: gearbox-g3auto + key: base64Authz.txt + optional: true + - name: GEARBOX_MIDDLEWARE_AUTHZ + valueFrom: + secretKeyRef: + name: gearbox-middleware-g3auto + key: base64Authz.txt + optional: true volumeMounts: + - mountPath: /var/log/nginx + name: nginx-logs - name: "revproxy-conf" readOnly: true mountPath: "/etc/nginx/nginx.conf" diff --git a/helm/revproxy/templates/ingress_dev.yaml b/helm/revproxy/templates/ingress_dev.yaml index df2ea60c8..6ecaa8d67 100644 --- a/helm/revproxy/templates/ingress_dev.yaml +++ b/helm/revproxy/templates/ingress_dev.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.dev true) (eq .Values.global.aws.enabled false) }} +{{- if and (eq .Values.global.dev true) (eq .Values.global.aws.enabled false) (eq .Values.openshiftRoute.enabled false) }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/helm/revproxy/templates/openshift_route.yaml b/helm/revproxy/templates/openshift_route.yaml new file mode 100644 index 000000000..ec6a05116 --- /dev/null +++ b/helm/revproxy/templates/openshift_route.yaml @@ -0,0 +1,27 @@ +{{- if .Values.openshiftRoute.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "revproxy.fullname" . }} + labels: + {{- include "revproxy.labels" . | nindent 4 }} + {{- if .Values.openshiftRoute.annotations }} + annotations: + {{- toYaml .Values.openshiftRoute.annotations | nindent 4 }} + {{- end }} +spec: + {{- if .Values.openshiftRoute.host }} + host: {{ .Values.openshiftRoute.host }} + {{- end }} + path: {{ .Values.openshiftRoute.path | default "/" }} + to: + kind: Service + name: revproxy-service + weight: 100 + port: + targetPort: {{ .Values.openshiftRoute.targetPort | default "http" }} + tls: + termination: {{ .Values.openshiftRoute.tls.termination | default "edge" }} + insecureEdgeTerminationPolicy: {{ .Values.openshiftRoute.tls.insecureEdgeTerminationPolicy | default "Redirect" }} + wildcardPolicy: {{ .Values.openshiftRoute.wildcardPolicy | default "None" }} +{{- end }} \ No newline at end of file diff --git a/helm/revproxy/templates/service.yaml b/helm/revproxy/templates/service.yaml index c752de6b8..71878a5e7 100644 --- a/helm/revproxy/templates/service.yaml +++ b/helm/revproxy/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: 80 + targetPort: http protocol: TCP name: http selector: diff --git a/helm/revproxy/values.yaml b/helm/revproxy/values.yaml index 2bd6f5307..94e13ae3e 100644 --- a/helm/revproxy/values.yaml +++ b/helm/revproxy/values.yaml @@ -86,7 +86,6 @@ global: # -- (int) The maxSkew to use for topology spread constraints. Defaults to 1. maxSkew: 1 - # -- (map) This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ autoscaling: {} @@ -163,6 +162,7 @@ securityContext: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: NodePort + targetPort: 80 # -- (int) The port number that the service exposes. port: 80 @@ -204,6 +204,27 @@ ingress: # hosts: # - chart-example.local +# -- (map) Configuration for OpenShift Route. +openshiftRoute: + # -- (bool) Whether to create an OpenShift Route + enabled: false + # -- (map) Annotations to add to the Route. + annotations: {} + # -- (string) Hostname for the Route. Leave empty to let OpenShift auto-generate + host: "" + # -- (string) Path for the Route. + path: "/" + # -- (string) Target port for the Route. + targetPort: "http" + # -- (map) TLS configuration for the Route. + tls: + # -- (string) Termination type for the Route. Valid options are "edge", "passthrough", and "reencrypt". + termination: "edge" + # -- (string) Insecure edge termination policy. Valid options are "None", "Allow", and "Redirect". + insecureEdgeTerminationPolicy: "Redirect" + # -- (string) Wildcard policy for the Route. Valid options are "None" and "Subdomain". + wildcardPolicy: "None" + # -- (map) Resource requests and limits for the containers in the pod resources: # -- (map) The amount of resources that the container requests @@ -263,3 +284,8 @@ extraServices: # - name: "protein-paint" # path: /protein-paint # serviceName: protein-paint + +nginx: + user: nginx + resolver: kube-dns.kube-system.svc.cluster.local + pidFile: /var/run/nginx.pid diff --git a/helm/sheepdog/sheepdog-secret/config_helper.py b/helm/sheepdog/sheepdog-secret/config_helper.py index ab1805496..e53844eaa 100644 --- a/helm/sheepdog/sheepdog-secret/config_helper.py +++ b/helm/sheepdog/sheepdog-secret/config_helper.py @@ -1,9 +1,15 @@ +""" +Originally copied from `cloud-automation/apis_configs/config_helper.py` +(renamed `confighelper.py` so it isn't overwritten by the file that cloud-automation +still mounts for backwards compatibility). + +TODO: once everyone has this independent version of sheepdog, remove `wsgi.py` and +`config_helper.py` here: +https://github.com/uc-cdis/cloud-automation/blob/afb750d/kube/services/sheepdog/sheepdog-deploy.yaml#L166-L177 +""" + import json import os -import copy -import argparse -import re -import types # # make it easy to change this for testing @@ -43,334 +49,6 @@ def load_json(file_name, app_name, search_folders=None): """ actual_files = find_paths(file_name, app_name, search_folders) if not actual_files: - return None + return {} with open(actual_files[0], "r") as reader: - return json.load(reader) - - -def inject_creds_into_fence_config(creds_file_path, config_file_path): - creds_file = open(creds_file_path, "r") - creds = json.load(creds_file) - creds_file.close() - - # get secret values from creds.json file - db_host = _get_nested_value(creds, "db_host") - db_username = _get_nested_value(creds, "db_username") - db_password = _get_nested_value(creds, "db_password") - db_database = _get_nested_value(creds, "db_database") - hostname = _get_nested_value(creds, "hostname") - indexd_password = environ.get('INDEXD_PASS') - google_client_secret = _get_nested_value(creds, "google_client_secret") - google_client_id = _get_nested_value(creds, "google_client_id") - hmac_key = _get_nested_value(creds, "hmac_key") - db_path = "postgresql://{}:{}@{}:5432/{}".format( - db_username, db_password, db_host, db_database - ) - - config_file = open(config_file_path, "r").read() - - print(" DB injected with value(s) from creds.json") - config_file = _replace(config_file, "DB", db_path) - - print(" BASE_URL injected with value(s) from creds.json") - config_file = _replace(config_file, "BASE_URL", "https://{}/user".format(hostname)) - - print(" INDEXD_PASSWORD injected with value(s) from creds.json") - config_file = _replace(config_file, "INDEXD_PASSWORD", indexd_password) - config_file = _replace(config_file, "INDEXD_USERNAME", "fence") - - print(" ENCRYPTION_KEY injected with value(s) from creds.json") - config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key) - - print( - " OPENID_CONNECT/google/client_secret injected with value(s) " - "from creds.json" - ) - config_file = _replace( - config_file, "OPENID_CONNECT/google/client_secret", google_client_secret - ) - - print(" OPENID_CONNECT/google/client_id injected with value(s) from creds.json") - config_file = _replace( - config_file, "OPENID_CONNECT/google/client_id", google_client_id - ) - - open(config_file_path, "w+").write(config_file) - - -def set_prod_defaults(config_file_path): - config_file = open(config_file_path, "r").read() - - print( - " CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS set as " - "var/www/fence/fence_google_app_creds_secret.json" - ) - config_file = _replace( - config_file, - "CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS", - "/var/www/fence/fence_google_app_creds_secret.json", - ) - - print( - " CIRRUS_CFG/GOOGLE_STORAGE_CREDS set as " - "var/www/fence/fence_google_storage_creds_secret.json" - ) - config_file = _replace( - config_file, - "CIRRUS_CFG/GOOGLE_STORAGE_CREDS", - "/var/www/fence/fence_google_storage_creds_secret.json", - ) - - print(" INDEXD set as http://indexd-service/") - config_file = _replace(config_file, "INDEXD", "http://indexd-service/") - - print(" ARBORIST set as http://arborist-service/") - config_file = _replace(config_file, "ARBORIST", "http://arborist-service/") - - print(" HTTP_PROXY/host set as cloud-proxy.internal.io") - config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io") - - print(" HTTP_PROXY/port set as 3128") - config_file = _replace(config_file, "HTTP_PROXY/port", 3128) - - print(" DEBUG set to false") - config_file = _replace(config_file, "DEBUG", False) - - print(" MOCK_AUTH set to false") - config_file = _replace(config_file, "MOCK_AUTH", False) - - print(" MOCK_GOOGLE_AUTH set to false") - config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False) - - print(" AUTHLIB_INSECURE_TRANSPORT set to true") - config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True) - - print(" SESSION_COOKIE_SECURE set to true") - config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True) - - print(" ENABLE_CSRF_PROTECTION set to true") - config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True) - - open(config_file_path, "w+").write(config_file) - - -def inject_other_files_into_fence_config(other_files, config_file_path): - additional_cfgs = _get_all_additional_configs(other_files) - - config_file = open(config_file_path, "r").read() - - for key, value in additional_cfgs.iteritems(): - print(" {} set to {}".format(key, value)) - config_file = _nested_replace(config_file, key, value) - - open(config_file_path, "w+").write(config_file) - - -def _get_all_additional_configs(other_files): - """ - Attempt to parse given list of files and extract configuration variables and values - """ - additional_configs = dict() - for file_path in other_files: - try: - file_ext = file_path.strip().split(".")[-1] - if file_ext == "json": - json_file = open(file_path, "r") - configs = json.load(json_file) - json_file.close() - elif file_ext == "py": - configs = from_pyfile(file_path) - else: - print( - "Cannot load config vars from a file with extention: {}".format( - file_ext - ) - ) - except Exception as exc: - # if there's any issue reading the file, exit - print( - "Error reading {}. Cannot get configuration. Skipping this file. " - "Details: {}".format(other_files, str(exc)) - ) - continue - - if configs: - additional_configs.update(configs) - - return additional_configs - - -def _nested_replace(config_file, key, value, replacement_path=None): - replacement_path = replacement_path or key - try: - for inner_key, inner_value in value.iteritems(): - temp_path = replacement_path - temp_path = temp_path + "/" + inner_key - config_file = _nested_replace( - config_file, inner_key, inner_value, temp_path - ) - except AttributeError: - # not a dict so replace - if value is not None: - config_file = _replace(config_file, replacement_path, value) - - return config_file - - -def _replace(yaml_config, path_to_key, replacement_value, start=0, nested_level=0): - """ - Replace a nested value in a YAML file string with the given value without - losing comments. Uses a regex to do the replacement. - - Args: - yaml_config (str): a string representing a full configuration file - path_to_key (str): nested/path/to/key. The value of this key will be - replaced - replacement_value (str): Replacement value for the key from - path_to_key - """ - nested_path_to_replace = path_to_key.split("/") - - # our regex looks for a specific number of spaces to ensure correct - # level of nesting. It matches to the end of the line - search_string = ( - " " * nested_level + ".*" + nested_path_to_replace[0] + "(')?(\")?:.*\n" - ) - matches = re.search(search_string, yaml_config[start:]) - - # early return if we haven't found anything - if not matches: - return yaml_config - - # if we're on the last item in the path, we need to get the value and - # replace it in the original file - if len(nested_path_to_replace) == 1: - # replace the current key:value with the new replacement value - match_start = start + matches.start(0) + len(" " * nested_level) - match_end = start + matches.end(0) - yaml_config = ( - yaml_config[:match_start] - + "{}: {}\n".format( - nested_path_to_replace[0], - _get_yaml_replacement_value(replacement_value, nested_level), - ) - + yaml_config[match_end:] - ) - - return yaml_config - - # set new start point to past current match and move on to next match - start = matches.end(0) - nested_level += 1 - del nested_path_to_replace[0] - - return _replace( - yaml_config, - "/".join(nested_path_to_replace), - replacement_value, - start, - nested_level, - ) - - -def from_pyfile(filename, silent=False): - """ - Modeled after flask's ability to load in python files: - https://github.com/pallets/flask/blob/master/flask/config.py - - Some alterations were made but logic is essentially the same - """ - filename = os.path.abspath(filename) - d = types.ModuleType("config") - d.__file__ = filename - try: - with open(filename, mode="rb") as config_file: - exec(compile(config_file.read(), filename, "exec"), d.__dict__) - except IOError as e: - print("Unable to load configuration file ({})".format(e.strerror)) - if silent: - return False - raise - return _from_object(d) - - -def _from_object(obj): - configs = {} - for key in dir(obj): - if key.isupper(): - configs[key] = getattr(obj, key) - return configs - - -def _get_yaml_replacement_value(value, nested_level=0): - if isinstance(value, str): - return "'" + value + "'" - elif isinstance(value, bool): - return str(value).lower() - elif isinstance(value, list) or isinstance(value, set): - output = "" - for item in value: - # spaces for nested level then spaces and hyphen for each list item - output += ( - "\n" - + " " * nested_level - + " - " - + _get_yaml_replacement_value(item) - + "" - ) - return output - else: - return value - - -def _get_nested_value(dictionary, nested_path): - """ - Return a value from a dictionary given a path-like nesting of keys. - - Will default to an empty string if value cannot be found. - - Args: - dictionary (dict): a dictionary - nested_path (str): nested/path/to/key - - Returns: - ?: Value from dict - """ - replacement_value_path = nested_path.split("/") - replacement_value = copy.deepcopy(dictionary) - - for item in replacement_value_path: - replacement_value = replacement_value.get(item, {}) - - if replacement_value == {}: - replacement_value = "" - - return replacement_value - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-i", - "--creds_file_to_inject", - default="creds.json", - help="creds file to inject into the configuration yaml", - ) - parser.add_argument( - "--other_files_to_inject", - nargs="+", - help="fence_credentials.json, local_settings.py, fence_settings.py file(s) to " - "inject into the configuration yaml", - ) - parser.add_argument( - "-c", "--config_file", default="config.yaml", help="configuration yaml" - ) - args = parser.parse_args() - - inject_creds_into_fence_config(args.creds_file_to_inject, args.config_file) - set_prod_defaults(args.config_file) - - if args.other_files_to_inject: - inject_other_files_into_fence_config( - args.other_files_to_inject, args.config_file - ) \ No newline at end of file + return json.load(reader) \ No newline at end of file diff --git a/helm/sheepdog/sheepdog-secret/settings.py b/helm/sheepdog/sheepdog-secret/settings.py index ac896e523..0700c13f3 100644 --- a/helm/sheepdog/sheepdog-secret/settings.py +++ b/helm/sheepdog/sheepdog-secret/settings.py @@ -1,81 +1,75 @@ -##################################################### -# DO NOT CHANGE THIS FILE # -# config updates should be done in the service code # -##################################################### - from sheepdog.api import app, app_init from os import environ -# import config_helper - -APP_NAME='sheepdog' -# def load_json(file_name): -# return config_helper.load_json(file_name, APP_NAME) - -# conf_data = load_json('creds.json') -config = app.config - - -config['INDEX_CLIENT'] = { - 'host': environ.get('INDEX_CLIENT_HOST') or 'http://indexd-service', - 'version': 'v0', - 'auth': (environ.get( "INDEXD_USER", 'sheepdog'), environ.get( "INDEXD_PASS") ), -} - -config["PSQLGRAPH"] = { - 'host': environ.get( "PGHOST"), - 'user': environ.get( "PGUSER"), - 'password': environ.get( "PGPASSWORD"), - 'database': environ.get( "PGDB"), -} - -config['HMAC_ENCRYPTION_KEY'] = environ.get( "HMAC_ENCRYPTION_KEY") -config['FLASK_SECRET_KEY'] = environ.get( "FLASK_SECRET_KEY") - -fence_username = environ.get( "FENCE_DB_USER") -fence_password = environ.get( "FENCE_DB_PASS") -fence_host = environ.get( "FENCE_DB_HOST") -fence_database = environ.get( "FENCE_DB_DBNAME") -config['PSQL_USER_DB_CONNECTION'] = 'postgresql://%s:%s@%s:5432/%s' % (fence_username, fence_password, fence_host, fence_database) +import os +import bin.confighelper as confighelper -config['DICTIONARY_URL'] = environ.get('DICTIONARY_URL','https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json') +APP_NAME = "sheepdog" -# config['SUBMISSION'] = { -# 'bucket': conf_data.get( 'bagit_bucket', '{{bagit_bucket}}' ) -# } +def load_json(file_name): + return confighelper.load_json(file_name, APP_NAME) -# config['STORAGE'] = { -# "s3": -# { -# "access_key": conf_data.get( 's3_access', '{{s3_access}}' ), -# 'secret_key': conf_data.get( 's3_secret', '{{s3_secret}}' ) -# } -# } -hostname = environ.get("CONF_HOSTNAME", "localhost") +conf_data = load_json("creds.json") +config = app.config -config['OIDC_ISSUER'] = 'https://%s/user' % hostname +# ARBORIST deprecated, replaced by ARBORIST_URL +# ARBORIST_URL is initialized in app_init() directly +config["ARBORIST"] = "http://arborist-service/" + +config["INDEX_CLIENT"] = { + "host": os.environ.get("INDEX_CLIENT_HOST") or "http://indexd-service", + "version": "v0", + # The user should be "sheepdog", but for legacy reasons, we use "gdcapi" instead + "auth": ( + ( + environ.get("INDEXD_USER", "gdcapi"), + environ.get("INDEXD_PASS") + or conf_data.get("indexd_password", "{{indexd_password}}"), + ) + ), +} -config['OAUTH2'] = { - 'client_id': "conf_data.get('oauth2_client_id', '{{oauth2_client_id}}')", - 'client_secret': "conf_data.get('oauth2_client_secret', '{{oauth2_client_secret}}')", - 'api_base_url': 'https://%s/user/' % hostname, - 'authorize_url': 'https://%s/user/oauth2/authorize' % hostname, - 'access_token_url': 'https://%s/user/oauth2/token' % hostname, - 'refresh_token_url': 'https://%s/user/oauth2/token' % hostname, - 'client_kwargs': { - 'redirect_uri': 'https://%s/api/v0/oauth2/authorize' % hostname, - 'scope': 'openid data user', - }, - # deprecated key values, should be removed after all commons use new oidc - 'internal_oauth_provider': 'http://fence-service/oauth2/', - 'oauth_provider': 'https://%s/user/oauth2/' % hostname, - 'redirect_uri': 'https://%s/api/v0/oauth2/authorize' % hostname +config["PSQLGRAPH"] = { + "host": conf_data.get("db_host", os.environ.get("PGHOST", "localhost")), + "user": conf_data.get("db_username", os.environ.get("PGUSER", "sheepdog")), + "password": conf_data.get("db_password", os.environ.get("PGPASSWORD", "sheepdog")), + "database": conf_data.get("db_database", os.environ.get("PGDB", "sheepdog")), } -config['USER_API'] = environ.get('FENCE_URL') or 'http://fence-service/' +config["FLASK_SECRET_KEY"] = conf_data.get("gdcapi_secret_key", "{{gdcapi_secret_key}}") +fence_username = conf_data.get( + "fence_username", os.environ.get("FENCE_DB_USER", "fence") +) +fence_password = conf_data.get( + "fence_password", os.environ.get("FENCE_DB_PASS", "fence") +) +fence_host = conf_data.get("fence_host", os.environ.get("FENCE_DB_HOST", "localhost")) +fence_database = conf_data.get( + "fence_database", os.environ.get("FENCE_DB_DATABASE", "fence") +) +config["PSQL_USER_DB_CONNECTION"] = "postgresql://%s:%s@%s:5432/%s" % ( + fence_username, + fence_password, + fence_host, + fence_database, +) + +hostname = conf_data.get( + "hostname", os.environ.get("CONF_HOSTNAME", "localhost") +) # for use by authutils +config["OIDC_ISSUER"] = "https://%s/user" % hostname +if hostname == "localhost": + config["USER_API"] = "http://fence-service/" +else: + config["USER_API"] = "https://%s/user" % hostname # for use by authutils # use the USER_API URL instead of the public issuer URL to accquire JWT keys -config['FORCE_ISSUER'] = True +config["FORCE_ISSUER"] = True +config["DICTIONARY_URL"] = os.environ.get( + "DICTIONARY_URL", + "https://s3.amazonaws.com/dictionary-artifacts/datadictionary/develop/schema.json", +) + app_init(app) application = app -application.debug = (environ.get('GEN3_DEBUG') == "True") +application.debug = os.environ.get("GEN3_DEBUG") == "True" \ No newline at end of file diff --git a/helm/sheepdog/templates/deployment.yaml b/helm/sheepdog/templates/deployment.yaml index ddba5efb2..f92968121 100644 --- a/helm/sheepdog/templates/deployment.yaml +++ b/helm/sheepdog/templates/deployment.yaml @@ -51,6 +51,8 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} volumes: - name: config-volume secret: @@ -122,12 +124,13 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 80 + - containerPort: {{ .Values.service.targetPort }} + name: http - containerPort: 443 livenessProbe: httpGet: path: /_status?timeout=20 - port: 80 + port: http initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 30 @@ -135,7 +138,7 @@ spec: initialDelaySeconds: 30 httpGet: path: /_status?timeout=2 - port: 80 + port: http # command: ["/bin/bash" ] # args: # - "-c" @@ -216,7 +219,12 @@ spec: secretKeyRef: name: indexd-service-creds key: sheepdog - optional: false + optional: true + - name: AUTHZ_ENTITY_NAME + valueFrom: + configMapKeyRef: + name: manifest-global + key: authz_entity_name - name: GEN3_UWSGI_TIMEOUT value: "600" - name: DICTIONARY_URL diff --git a/helm/sheepdog/templates/service.yaml b/helm/sheepdog/templates/service.yaml index eff84f425..accebdecc 100644 --- a/helm/sheepdog/templates/service.yaml +++ b/helm/sheepdog/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} protocol: TCP name: http selector: diff --git a/helm/sheepdog/values.yaml b/helm/sheepdog/values.yaml index 862c1ee52..0ce28a43c 100644 --- a/helm/sheepdog/values.yaml +++ b/helm/sheepdog/values.yaml @@ -215,6 +215,7 @@ resources: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 80 # -- (int) The port number that the service exposes. port: 80 @@ -237,3 +238,19 @@ partOf: "Core-Service" selectorLabels: # -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl commonLabels: + + +# -- (map) Security context for the pod +podSecurityContext: + {} + # fsGroup: 2000 + +# -- (map) Security context for the containers in the pod +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 \ No newline at end of file diff --git a/helm/sower/templates/deployment.yaml b/helm/sower/templates/deployment.yaml index f036a49ef..903bb5747 100644 --- a/helm/sower/templates/deployment.yaml +++ b/helm/sower/templates/deployment.yaml @@ -64,19 +64,19 @@ spec: value: {{ .Values.global.hostname }} ports: - name: http - containerPort: 8000 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: path: /_status - port: 8000 + port: http initialDelaySeconds: 5 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 8000 + port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} diff --git a/helm/sower/values.yaml b/helm/sower/values.yaml index a1aa6da2f..b7fc09e48 100644 --- a/helm/sower/values.yaml +++ b/helm/sower/values.yaml @@ -156,6 +156,7 @@ securityContext: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 8000 # -- (int) The port number that the service exposes. port: 80 diff --git a/helm/ssjdispatcher/README.md b/helm/ssjdispatcher/README.md index d65a02e61..3686e3e54 100644 --- a/helm/ssjdispatcher/README.md +++ b/helm/ssjdispatcher/README.md @@ -101,7 +101,7 @@ A Helm chart for gen3 ssjdispatcher | 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 | map | `{"port":80,"targetPort":8000,"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-sa"}` | Service account to use or create. | diff --git a/helm/ssjdispatcher/templates/deployment.yaml b/helm/ssjdispatcher/templates/deployment.yaml index eed1f5581..61ff2136d 100644 --- a/helm/ssjdispatcher/templates/deployment.yaml +++ b/helm/ssjdispatcher/templates/deployment.yaml @@ -70,19 +70,19 @@ spec: key: job_images ports: - name: http - containerPort: 8000 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: path: /_status - port: 8000 + port: http initialDelaySeconds: 5 periodSeconds: 60 timeoutSeconds: 30 readinessProbe: httpGet: path: /_status - port: 8000 + port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} diff --git a/helm/ssjdispatcher/values.yaml b/helm/ssjdispatcher/values.yaml index 1c8a964c1..170915dfb 100644 --- a/helm/ssjdispatcher/values.yaml +++ b/helm/ssjdispatcher/values.yaml @@ -134,6 +134,7 @@ securityContext: {} service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 8000 # -- (int) The port number that the service exposes. port: 80 diff --git a/helm/wts/templates/deployment.yaml b/helm/wts/templates/deployment.yaml index 88d3b00c3..50e486a3c 100644 --- a/helm/wts/templates/deployment.yaml +++ b/helm/wts/templates/deployment.yaml @@ -82,18 +82,18 @@ spec: subPath: appcreds.json ports: - name: http - containerPort: 80 + containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: path: /_status - port: 80 + port: http failureThreshold: 10 initialDelaySeconds: 5 readinessProbe: httpGet: path: /_status - port: 80 + port: http env: - name: OIDC_CLIENT_ID valueFrom: diff --git a/helm/wts/templates/wts-oidc.yaml b/helm/wts/templates/wts-oidc.yaml index c9b1aeeb9..6e80f4ee4 100644 --- a/helm/wts/templates/wts-oidc.yaml +++ b/helm/wts/templates/wts-oidc.yaml @@ -28,9 +28,8 @@ spec: args: ["while [ $(curl -sw '%{http_code}' http://fence-service -o /dev/null) -ne 200 ]; do sleep 5; echo 'Waiting for fence...'; done"] containers: - name: fence-client - # TODO: Make this configurable - image: "quay.io/cdis/fence:master" - imagePullPolicy: {{ .Values.image.pullPolicy }} + image: "{{ .Values.fenceImage.repository }}:{{ .Values.fenceImage.tag }}" + imagePullPolicy: {{ .Values.fenceImage.pullPolicy }} # TODO: ADD RESOURCES # resources: command: ["/bin/bash"] diff --git a/helm/wts/values.yaml b/helm/wts/values.yaml index c151480be..cd819c030 100644 --- a/helm/wts/values.yaml +++ b/helm/wts/values.yaml @@ -194,6 +194,7 @@ securityContext: service: # -- (string) Type of service. Valid values are "ClusterIP", "NodePort", "LoadBalancer", "ExternalName". type: ClusterIP + targetPort: 80 # -- (int) Port on which the service is exposed httpPort: 80 # -- (int) Secure port on which the service is exposed @@ -268,3 +269,12 @@ partOf: "Authentication" selectorLabels: # -- (map) Will completely override the commonLabels defined in the common chart's _label_setup.tpl commonLabels: + +# -- (map) Fence docker image information. +fenceImage: + # -- (string) Docker repository. + repository: quay.io/pcdc/fence + # -- (string) Docker pull policy. + pullPolicy: Always + # -- (string) Overrides the image tag whose default is the chart appVersion. + tag: "master" diff --git a/pcdc_data/external/external_reference.json b/pcdc_data/external/external_reference.json new file mode 100644 index 000000000..3da4e2daa --- /dev/null +++ b/pcdc_data/external/external_reference.json @@ -0,0 +1,66 @@ +[ + { + "external_links": "TARGET - GDC|https://pcdc-external-resource-files.s3.amazonaws.com/NHI_GDC_DataPortal-logo.23e6ca47.svg|https://portal.gdc.cancer.gov/cases/4e824cfb-d887-57b3-bff2-95e2e7b4d410", + "external_resource_icon_path": "https://pcdc-external-resource-files.s3.amazonaws.com/NHI_GDC_DataPortal-logo.23e6ca47.svg", + "external_resource_id": "1", + "external_resource_name": "TARGET - GDC", + "external_subject_id": "4e824cfb-d887-57b3-bff2-95e2e7b4d410", + "external_subject_submitter_id": "subject_regraduate_Tremandra", + "external_subject_url": "https://portal.gdc.cancer.gov/cases/4e824cfb-d887-57b3-bff2-95e2e7b4d410", + "subjects": [ + { + "submitter_id": "subject_Oreodoxa_hendness" + } + ], + "submitter_id": "external_reference_isomaltose_unmingling", + "type": "external_reference" + }, + { + "external_links": "TARGET - GDC|https://pcdc-external-resource-files.s3.amazonaws.com/NHI_GDC_DataPortal-logo.23e6ca47.svg|https://portal.gdc.cancer.gov/cases/448a7c70-73e8-5b2f-b226-83e4065dc6ef", + "external_resource_icon_path": "https://pcdc-external-resource-files.s3.amazonaws.com/NHI_GDC_DataPortal-logo.23e6ca47.svg", + "external_resource_id": "1", + "external_resource_name": "TARGET - GDC", + "external_subject_id": "448a7c70-73e8-5b2f-b226-83e4065dc6ef", + "external_subject_submitter_id": "subject_amenable_thermotropism", + "external_subject_url": "https://portal.gdc.cancer.gov/cases/448a7c70-73e8-5b2f-b226-83e4065dc6ef", + "subjects": [ + { + "submitter_id": "subject_unadventurously_oturia" + } + ], + "submitter_id": "external_reference_homemaking_antivibrator", + "type": "external_reference" + }, + { + "external_links": "GMKF|https://pcdc-external-resource-files.s3.us-east-1.amazonaws.com/Kids_First_Graphic_Horizontal_OL_FINAL.DRC-01-scaled.png|https://portal.kidsfirstdrc.org/participant/PT_72AZK0JR", + "external_resource_icon_path": "https://pcdc-external-resource-files.s3.us-east-1.amazonaws.com/Kids_First_Graphic_Horizontal_OL_FINAL.DRC-01-scaled.png", + "external_resource_id": "2", + "external_resource_name": "GMKF", + "external_subject_id": "5554823-73e8-5b2f-b226-83e4065dc6ef", + "external_subject_submitter_id": "subject_Archidiskodon_somnambulance", + "external_subject_url": "https://portal.kidsfirstdrc.org/participant/PT_72AZK0JR", + "subjects": [ + { + "submitter_id": "subject_springly_quartet" + } + ], + "submitter_id": "external_reference_irreverendly_subtrifid", + "type": "external_reference" + }, + { + "external_links": "GMKF|https://pcdc-external-resource-files.s3.us-east-1.amazonaws.com/Kids_First_Graphic_Horizontal_OL_FINAL.DRC-01-scaled.png|https://portal.kidsfirstdrc.org/participant/PT_N5N59J9M", + "external_resource_icon_path": "https://pcdc-external-resource-files.s3.us-east-1.amazonaws.com/Kids_First_Graphic_Horizontal_OL_FINAL.DRC-01-scaled.png", + "external_resource_id": "2", + "external_resource_name": "GMKF", + "external_subject_id": "5554823-73e8-5b2f-777b-83e4065dc6ef", + "external_subject_submitter_id": "subject_coincidence_strom", + "external_subject_url": "https://portal.kidsfirstdrc.org/participant/PT_N5N59J9M", + "subjects": [ + { + "submitter_id": "subject_autoschediastically_contraflexure" + } + ], + "submitter_id": "external_reference_communicative_syntactics", + "type": "external_reference" + } +] \ No newline at end of file diff --git a/pcdc_data/external/update_external_references.py b/pcdc_data/external/update_external_references.py new file mode 100644 index 000000000..61bbcd7d7 --- /dev/null +++ b/pcdc_data/external/update_external_references.py @@ -0,0 +1,90 @@ +import json +import sys +from pathlib import Path + + +def ensure_subjects_list(ref): + """ + This function ensures we are looking at a list. + """ + subjects = ref.get("subjects") + + # If it's a dict (one subject), wrap it in a list + if isinstance(subjects, dict): + return [subjects] + + # If it's already a list, just return it + elif isinstance(subjects, list): + return subjects + + # If it's missing or an unexpected format, return a list with a blank dict + else: + return [{"submitter_id": ""}] + + +def update_external_refs(subject_path, external_ref_path, output_path=None): + """ + This function reads a list of subjects and external references, + then updates each external reference's subjects[0]['submitter_id'] + with the matching subject's submitter_id (by index). + """ + + # Load the subject file (expects a list of subject objects) + with open(subject_path) as f: + subjects = json.load(f) + + # Extract just the top-level 'submitter_id' from each subject + subject_ids = [s["submitter_id"] for s in subjects] + + # Load the external_reference file (expects a list of reference objects) + with open(external_ref_path) as f: + external_refs = json.load(f) + + # Flatten outer list if needed + if isinstance(external_refs, list) and isinstance(external_refs[0], list): + external_refs = [item for sublist in external_refs for item in sublist] + + # Loop over each external reference + for i, ref in enumerate(external_refs): + # If there are more external references than subjects, warn and skip + if i >= len(subject_ids): + print(f"Warning: Not enough subject IDs for external reference {i}") + continue + + # Get the corresponding subject submitter_id + subject_id = subject_ids[i] + + # Make sure the 'subjects' field is a list with one dict + ref["subjects"] = ensure_subjects_list(ref) + + # Update the first subject's submitter_id with the new one + ref["subjects"][0]["submitter_id"] = subject_id + + # Use the provided output path, or overwrite the original file + output_path = output_path or external_ref_path + + # Save the updated external references back to a JSON file + with open(output_path, "w") as f: + json.dump(external_refs, f, indent=4) + + print(f"Updated file written to {output_path}") + + +# This block only runs if the script is called directly, not if imported +if __name__ == "__main__": + # Expect at least 2 arguments: subject.json and external_reference.json + if len(sys.argv) < 3: + print( + "Usage: python update_external_references.py [output.json]" + ) + sys.exit(1) # Exit with error if not enough arguments are provided + + # Read the input file paths from the command-line arguments + subject_file = Path(sys.argv[1]) + external_ref_file = Path(sys.argv[2]) + + # Optional third argument: a custom output file path + output_file = Path(sys.argv[3]) if len(sys.argv) > 3 else None + + # Call the main function + update_external_refs(subject_file, external_ref_file, output_file) diff --git a/pcdc_data/generate_data.sh b/pcdc_data/generate_data.sh new file mode 100755 index 000000000..83ce4c970 --- /dev/null +++ b/pcdc_data/generate_data.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Define the file path +generate_file="./gen3_etl/graph/generate.sh" + +# Check if the file exists +if [ ! -f "$generate_file" ]; then + echo "Error: $generate_file not found." + exit 1 +fi + +chmod +x "$generate_file" + +# Use sed to replace the line +sed -i '' 's/GEN3_SCRIPTS_REPO_BRANCH="origin\/pcdc_dev"/GEN3_SCRIPTS_REPO_BRANCH="origin\/pyyaml-patch"/' "$generate_file" + +echo "data-simulator branch changed to pyyaml-patch change when PR is completed" + +# Run the generate_file script +cd ./gen3_etl/graph +mkdir ./fake_data +./generate.sh + +# run our update script +SUBJECT_JSON="./fake_data/data-simulator/subject.json" +EXTERNAL_JSON="../../external/external_reference.json" +UPDATE_SCRIPT="../../external/update_external_references.py" + +# Grab a version of the external_reference, as well as the subject, and connect them together via submitter_id +python3 "$UPDATE_SCRIPT" "$SUBJECT_JSON" "$EXTERNAL_JSON" + +# copy our external_refernce.json to where it belongs +cp ../../external/external_reference.json ./fake_data/data-simulator/ + +cd ../../ \ No newline at end of file diff --git a/pcdc_data/load_elasticsearch.sh b/pcdc_data/load_elasticsearch.sh new file mode 100755 index 000000000..ffddacb15 --- /dev/null +++ b/pcdc_data/load_elasticsearch.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +source ../.env + +pcdc clear_elasticsearch + +cd ./gen3_etl/elasticsearch + +# Check if the 'env' directory exists +if [ ! -d "env" ]; then + # If 'env' directory doesn't exist, create a virtual environment + echo "Creating virtual environment..." + python -m venv env +else + echo "Virtual environment 'env' already exists." +fi + +# Activate the virtual environment +source env/bin/activate + +poetry install + +curr_dir=$(pwd) +auth_file_path="$curr_dir/env/lib/python3.9/site-packages/gen3/auth.py" +submission_file_path="$curr_dir/env/lib/python3.9/site-packages/gen3/submission.py" + +if [ ! -f "$auth_file_path" ]; then + echo "Error: File not found: $auth_file_path" + exit 1 +fi + +if [ ! -f "$submission_file_path" ]; then + echo "Error: File not found: $submission_file_path" + exit 1 +fi +# Find the line number where the text to be replaced is located +line_number=$(grep -n "resp = requests.post(auth_url, json=api_key)" "$auth_file_path" | cut -d ":" -f 1) + +# Edit the specified line +sed -i "" "${line_number}s/resp = requests.post(auth_url, json=api_key)/resp = requests.post(auth_url, json=api_key, verify=False)/" "$auth_file_path" + +echo "AUTH file edited successfully." + +sed -i "" -E 's/(requests\..*)\)/\1, verify=False)/' "$submission_file_path" + +echo "submission file edited successfully." + +mkdir -p files + +cd etl + +python etl.py et + + +python etl.py l + +cd ../../../.. + +INDEX=$(grep 'INDEX_NAME' .env | cut -d '=' -f 2-) +INDEX=$(echo "$INDEX" | sed "s/'//g") + + +cat << EOF > temp.yaml +guppy: + indices: + - index: "$INDEX" + type: "subject" + configIndex: "$INDEX-array-config" + authFilterField: "auth_resource_path" +EOF + +yq eval '. * load("./temp.yaml")' secret-values.yaml > updated-secret-values.yaml && mv updated-secret-values.yaml secret-values.yaml + + +pcdc roll revproxy guppy pcdcanalysistools + + +rm ./temp.yaml \ No newline at end of file diff --git a/pcdc_data/load_gen3_etl.sh b/pcdc_data/load_gen3_etl.sh new file mode 100755 index 000000000..adbeeb443 --- /dev/null +++ b/pcdc_data/load_gen3_etl.sh @@ -0,0 +1,34 @@ +GEN3_SCRIPTS_REPO="https://github.com/chicagopcdc/gen3_etl.git" +GEN3_SCRIPTS_REPO_BRANCH="origin/update-requirments" +ENV_FILE="../.env" +CREDENTIALS_FILE="../credentials.json" + +#------------------------------------------------------ +# Clean up +#------------------------------------------------------ +rm -rf ./gen3_etl +echo "removed old folder" + +#------------------------------------------------------ +# Clone or Update chicagopcdc/data-simulator repo +#------------------------------------------------------ +echo "Clone or Update chicagopcdc/gen3_etl repo from github" + +# Does the repo exist? If not, go get it! +if [ ! -d "./gen3_etl" ]; then + git clone $GEN3_SCRIPTS_REPO + + cd ./gen3_etl + + git checkout -t $GEN3_SCRIPTS_REPO_BRANCH + git pull + + cd .. +fi + +#here I will set the defaults for the .env file if left blank + + + +#load in files to gen3_load and es_etl_patch +cp $ENV_FILE ./gen3_etl/elasticsearch && cp $ENV_FILE ./gen3_etl/graph && cp $CREDENTIALS_FILE ./gen3_etl/elasticsearch && cp $CREDENTIALS_FILE ./gen3_etl/graph diff --git a/pcdc_data/load_graph_db.sh b/pcdc_data/load_graph_db.sh new file mode 100755 index 000000000..3e3bb58a4 --- /dev/null +++ b/pcdc_data/load_graph_db.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +source ../.env + +cd ./gen3_etl/graph + +# Check if the 'env' directory exists +if [ ! -d "env" ]; then + # If 'env' directory doesn't exist, create a virtual environment + echo "Creating virtual environment..." + python -m venv env +else + echo "Virtual environment 'env' already exists." +fi + +# Activate the virtual environment +source env/bin/activate + +poetry install + +curr_dir=$(pwd) +auth_file_path="$curr_dir/env/lib/python3.9/site-packages/gen3/auth.py" +submission_file_path="$curr_dir/env/lib/python3.9/site-packages/gen3/submission.py" + +if [ ! -f "$auth_file_path" ]; then + echo "Error: File not found: $auth_file_path" + exit 1 +fi + +if [ ! -f "$submission_file_path" ]; then + echo "Error: File not found: $submission_file_path" + exit 1 +fi +# Find the line number where the text to be replaced is located +line_number=$(grep -n "resp = requests.post(auth_url, json=api_key)" "$auth_file_path" | cut -d ":" -f 1) + +# Edit the specified line +sed -i "" "${line_number}s/resp = requests.post(auth_url, json=api_key)/resp = requests.post(auth_url, json=api_key, verify=False)/" "$auth_file_path" + +echo "AUTH file edited successfully." + + +sed -i "" -E '/requests\./ s/^(.*\()(.*)(\)$)/\1\2, verify=False\3/' "$submission_file_path" + +echo "submission file edited successfully." + +cd ./operations + +python etl.py load + +deactivate \ No newline at end of file diff --git a/pcdc_data/run_all.sh b/pcdc_data/run_all.sh new file mode 100755 index 000000000..37fe47039 --- /dev/null +++ b/pcdc_data/run_all.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +ENV_FILE="../.env" + +# Check if .env file exists +if [ ! -f "$ENV_FILE" ]; then + touch "$ENV_FILE" + + # Get today's date in the format YYYYMMDD + DATE=$(date +"%Y%m%d") + + # Populate .env file with variables + echo "DICTIONARY_URL='https://pcdc-dev-dictionaries.s3.amazonaws.com/pcdc-schema-dev-20230912.json'" > "$ENV_FILE" + echo "PROGRAM_NAME='pcdc'" >> "$ENV_FILE" + echo "PROJECT_CODE='$DATE'" >> "$ENV_FILE" + echo "SAMPLE=400" >> "$ENV_FILE" + echo "BASE_URL=''" >> "$ENV_FILE" + echo "LOCAL_FILE_PATH='../fake_data/data-simulator'" >> "$ENV_FILE" + echo "FILE_TYPE='json'" >> "$ENV_FILE" + echo "TYPES=[\"program\", \"adverse_event\", \"biopsy_surgical_procedure\", \"biospecimen\", \"cellular_immunotherapy\", \"concomitant_medication\", \"core_metadata_collection\", \"cytology\", \"disease_characteristic\", \"external_reference\", \"family_medical_history\", \"function_test\", \"growing_teratoma_syndrome\", \"histology\", \"imaging\", \"immunohistochemistry\", \"lab\", \"late_effect\", \"lesion_characteristic\", \"medical_history\", \"minimal_residual_disease\", \"molecular_analysis\", \"myeloid_sarcoma_involvement\", \"non_protocol_therapy\", \"off_protocol_therapy_study\", \"patient_reported_outcomes_metadata\", \"person\", \"project\", \"protocol_treatment_modification\", \"radiation_therapy\", \"secondary_malignant_neoplasm\", \"staging\", \"stem_cell_transplant\", \"study\", \"subject\", \"subject_response\", \"survival_characteristic\", \"timing\", \"total_dose\", \"transfusion_medicine_procedure\", \"tumor_assessment\", \"vital\"]" >> "$ENV_FILE" + echo "PROJECT_LIST=[\"pcdc-$DATE\"]" >> "$ENV_FILE" + echo "CREDENTIALS='../credentials.json'" >> "$ENV_FILE" + echo "LOCAL_ES_FILE_PATH='../files/pcdc_data.json'" >> "$ENV_FILE" + echo "ES_PORT=9200" >> "$ENV_FILE" + echo "INDEX_NAME='pcdc-$DATE'" >> "$ENV_FILE" +fi + +chmod +x "$(dirname "$0")"/*.sh + +./load_gen3_etl.sh +# Update the build json file +./generate_data.sh +# Update the external ref file +./load_graph_db.sh + +./load_elasticsearch.sh \ No newline at end of file diff --git a/pelican.yaml b/pelican.yaml new file mode 100644 index 000000000..c226e3ce3 --- /dev/null +++ b/pelican.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pelican-pod +spec: + volumes: + - name: pelican-creds-volume + secret: + secretName: pelicanservice-g3auto + - name: peregrine-creds-volume + secret: + secretName: peregrine-creds + containers: + - name: pelican-container + image: pelican:test + imagePullPolicy: Never + command: ["/bin/sh"] + args: ["-c", "cd / && tail -f /dev/null"] + ports: + - containerPort: 80 + 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 + - name: OUTPUT_FILE_FORMAT + value: ZIP + - name: INPUT_DATA + value: "{}" + - name: ACCESS_FORMAT + value: presigned_url + - name: INPUT_VARIABLES_FOR_FILE_CONVERSION + value: '{"config": "config.py","analysis": "INRG","is_black_list": false}' + - name: ACCESS_TOKEN + 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 \ No newline at end of file diff --git a/tools/clear_elasticsearch.sh b/tools/clear_elasticsearch.sh new file mode 100755 index 000000000..2cfd2e1c8 --- /dev/null +++ b/tools/clear_elasticsearch.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +ESHOST="http://localhost:9200" + + +function es_indices() { + curl -X GET "${ESHOST}/_cat/indices?v" +} + +indexList=$(es_indices 2> /dev/null | awk '{ print $3 }' | grep -v "^index$") + + +for name in $indexList; do + echo curl -iv -X DELETE "${ESHOST}/$name" + curl -iv -X DELETE "${ESHOST}/$name" +done diff --git a/tools/connect_to_db.sh b/tools/connect_to_db.sh new file mode 100755 index 000000000..cf435b857 --- /dev/null +++ b/tools/connect_to_db.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Check if service name is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +project_name=$1 +service_name=$2 + +# Retrieve password from secret +password=$(kubectl get secret ${service_name}-dbcreds -o jsonpath="{.data.password}" | base64 --decode) + +# Execute command in the pod +kubectl exec -it ${project_name}-postgresql-0 -- /bin/bash -c "PGPASSWORD='${password}' psql -h ${project_name}-postgresql -U ${service_name}_${project_name} -d ${service_name}_${project_name}" \ No newline at end of file diff --git a/tools/connect_to_pod.sh b/tools/connect_to_pod.sh new file mode 100755 index 000000000..d624193cb --- /dev/null +++ b/tools/connect_to_pod.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Check if service name is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +service_name=$1 + +# Get pod name associated with the service +pod_name=$(kubectl get pods -l app="$service_name" -o jsonpath='{.items[*].metadata.name}') + +# Check if pod name is empty +if [ -z "$pod_name" ]; then + echo "Error: No pod found for service $service_name" + exit 1 +fi + +# Execute command in the pod +kubectl exec -it "$pod_name" -- /bin/bash \ No newline at end of file diff --git a/tools/cronjob.sh b/tools/cronjob.sh new file mode 100755 index 000000000..a9d5967c4 --- /dev/null +++ b/tools/cronjob.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Check if argument is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Extract the job name +job_name="$1" + +# Delete the specified Job +kubectl delete cronjob "$job_name" + +# Check if the deletion was successful +if [ $? -ne 0 ]; then + echo "Error: Failed to delete Job $job_name" + exit 1 +fi + +# Run roll.sh script +roll.sh \ No newline at end of file diff --git a/tools/gearbox b/tools/gearbox new file mode 100755 index 000000000..1486504f6 --- /dev/null +++ b/tools/gearbox @@ -0,0 +1,56 @@ +#!/bin/bash + + +# Define script names +CONNECT_SCRIPT="connect_to_db.sh" +ROLL_SCRIPT="roll.sh" +JOB_SCRIPT="job.sh" +POD_SCRIPT="connect_to_pod.sh" +RESTART_POD="restart_pod.sh" +LOGS="logs.sh" + + +# Check if command is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 [...]" + exit 1 +fi + +# Give execute permission to specific .sh scripts in the specified directory +chmod +x "$(dirname "$0")"/*.sh + +# Extract the command +command="$1" +shift # Remove the first argument (command) + +# Check if the command is valid +case "$command" in + "psql") + # Run the connect_to_db.sh script with the remaining arguments + "$CONNECT_SCRIPT" gearbox "$@" + ;; + "roll") + # Run the roll.sh script with the remaining arguments + "$ROLL_SCRIPT" "gearbox" "$@" + ;; + "job") + # Run the job.sh script with the remaining arguments + "$JOB_SCRIPT" "gearbox" "$@" + ;; + "pod") + # Run the connect_to_pod.sh script with the remaining arguments + "$POD_SCRIPT" "$@" + ;; + "restart") + # Run the connect_to_pod.sh script with the remaining arguments + "$RESTART_POD" "$@" + ;; + "logs") + # Run the connect_to_pod.sh script with the remaining arguments + "$LOGS" "$@" + ;; + *) + echo "Invalid command: $command" + exit 1 + ;; +esac \ No newline at end of file diff --git a/tools/job.sh b/tools/job.sh new file mode 100755 index 000000000..7207e1ac8 --- /dev/null +++ b/tools/job.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Check if argument is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Extract the job name +project="$1" +job_name="$2" + +# Delete the specified Job +kubectl delete job "$job_name" + +# Check if the deletion was successful +if [ $? -ne 0 ]; then + echo "Error: Failed to delete Job $job_name" + exit 1 +fi + +# Run roll.sh script +$project roll \ No newline at end of file diff --git a/tools/load_data.sh b/tools/load_data.sh new file mode 100755 index 000000000..99f6f6b09 --- /dev/null +++ b/tools/load_data.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd "$(dirname "$0")/../pcdc_data" || exit 1 || exit 1 + +chmod +x ./run_all.sh + + +./run_all.sh \ No newline at end of file diff --git a/tools/logs.sh b/tools/logs.sh new file mode 100755 index 000000000..374f055f1 --- /dev/null +++ b/tools/logs.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Check if service name is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 [-f] " + exit 1 +fi + +# Check if the first argument is "-f" +if [ "$1" = "-f" ]; then + follow_logs=true + shift # Remove the "-f" argument from the argument list +else + follow_logs=false +fi + +service_name=$1 + +# Get pod name associated with the service +pod_name=$(kubectl get pods -l app="$service_name" -o jsonpath='{.items[*].metadata.name}') + +# Check if pod name is empty +if [ -z "$pod_name" ]; then + echo "Error: No pod found for service $service_name" + exit 1 +fi + +# Execute kubectl logs with or without "-f" option based on the flag +if [ "$follow_logs" = true ]; then + kubectl logs -f "$pod_name" +else + kubectl logs "$pod_name" +fi \ No newline at end of file diff --git a/tools/pcdc b/tools/pcdc new file mode 100755 index 000000000..c2db14797 --- /dev/null +++ b/tools/pcdc @@ -0,0 +1,71 @@ +#!/bin/bash + + +# Define script names +CONNECT_SCRIPT="connect_to_db.sh" +ROLL_SCRIPT="roll.sh" +JOB_SCRIPT="job.sh" +POD_SCRIPT="connect_to_pod.sh" +CLEAR_ELASTICSEARCH="clear_elasticsearch.sh" +RESTART_POD="restart_pod.sh" +LOGS="logs.sh" +LOAD_DATA="load_data.sh" +CRONJOB_SCRIPT="cronjob.sh" + + +# Check if command is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 [...]" + exit 1 +fi + +# Give execute permission to specific .sh scripts in the specified directory +chmod +x "$(dirname "$0")"/*.sh + +# Extract the command +command="$1" +shift # Remove the first argument (command) + +# Check if the command is valid +case "$command" in + "psql") + # Run the connect_to_db.sh script with the remaining arguments + "$CONNECT_SCRIPT" pcdc "$@" + ;; + "roll") + # Run the roll.sh script with the remaining arguments + "$ROLL_SCRIPT" "pcdc" "$@" + ;; + "job") + # Run the job.sh script with the remaining arguments + "$JOB_SCRIPT" "pcdc" "$@" + ;; + "pod") + # Run the connect_to_pod.sh script with the remaining arguments + "$POD_SCRIPT" "$@" + ;; + "clear_elasticsearch") + # Run the connect_to_pod.sh script with the remaining arguments + "$CLEAR_ELASTICSEARCH" "$@" + ;; + "restart") + # Run the connect_to_pod.sh script with the remaining arguments + "$RESTART_POD" "$@" + ;; + "logs") + # Run the connect_to_pod.sh script with the remaining arguments + "$LOGS" "$@" + ;; + "load_data") + # Run the connect_to_pod.sh script with the remaining arguments + "$LOAD_DATA" "$@" + ;; + "cronjob") + # Run the CRONJOB_SCRIPT.sh script with the remaining arguments + "$CRONJOB_SCRIPT" "$@" + ;; + *) + echo "Invalid command: $command" + exit 1 + ;; +esac \ No newline at end of file diff --git a/tools/restart_pod.sh b/tools/restart_pod.sh new file mode 100755 index 000000000..90fa1a8b9 --- /dev/null +++ b/tools/restart_pod.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Check if service name is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +service_name=$1 + +# Get pod name associated with the service +pod_name=$(kubectl get pods -l app="$service_name" -o jsonpath='{.items[*].metadata.name}') + +# Check if pod name is empty +if [ -z "$pod_name" ]; then + echo "Error: No pod found for service $service_name" + exit 1 +fi + +# Execute delete pod +kubectl delete pod "$pod_name" \ No newline at end of file diff --git a/tools/roll.sh b/tools/roll.sh new file mode 100755 index 000000000..f76a626bf --- /dev/null +++ b/tools/roll.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# Change directory to the helm chart directory +cd "$(dirname "$0")/../helm/gen3" || exit 1 || exit 1 + +rm ../../values.yaml + +# Initialize variables +DISABLE_UPDATE=false + + + +project="$1" +shift + +# Parse arguments +for arg in "$@"; do + case $arg in + --disable-update) + DISABLE_UPDATE=true + shift # Remove --disable-update from arguments + ;; + *) + # Pass through other arguments + ;; + esac +done +# Check if ../../secret-values.yaml exists +if [ -f ../../secret-values.yaml ]; then + yq '. *= load("../../secret-values.yaml")' ../../$project-default-values.yaml > ../../values.yaml +else + cp ../../$project-default-values.yaml ../../values.yaml +fi +# Directory to store CA certificate +ca_dir=../../CA +ca_file=$ca_dir/ca.pem + +# Create the CA certificate directory if it doesn't exist +if [ ! -d "$ca_dir" ]; then + mkdir -p "$ca_dir" +fi + +# Check if CA certificate file exists, if not, create it +if [ ! -f "$ca_file" ]; then + # Create the CA certificate file + echo "Creating CA certificate file..." + touch "$ca_file" +fi + +# Extract the value from ../../values.yaml +ca_cert=$(yq eval '.global.tls.cert' ../../values.yaml) + +# Write the extracted value to the CA certificate file +echo "$ca_cert" > "$ca_file" + +# Check if arguments are passed +if [ $# -gt 0 ]; then + # Iterate over each argument (service name) + for service_name in "$@" + do + # Skip if service_name is disable-update + if [ "$service_name" = "--disable-update" ]; then + continue + fi + + # Delete the deployment corresponding to the service name + kubectl delete deployment ${service_name}-deployment + done +fi + +# Conditionally run helm dependency update +if [ "$DISABLE_UPDATE" = false ]; then + echo "Running helm dependency update..." + helm dependency update +else + echo "Skipping helm dependency update..." +fi + +# Run helm upgrade --install command +helm upgrade --install $project . -f ../../values.yaml --timeout 15m0s \ No newline at end of file