From a21b80897f6326874c5e5a9a8f0d5ac0ec1a817d Mon Sep 17 00:00:00 2001 From: Emma Doyle Date: Thu, 30 May 2024 15:28:54 -0400 Subject: [PATCH] fix-e2e-test-clone: rename, fix e2e env cloning, decrypted PassSecret conversion (#135) * fix-e2e-test-clone: rename, fix e2e env cloning, decrypted PassSecret conversion --- .github/settings.yml | 2 +- helm/README.md | 6 +- helm/operator-crds/_json/PassSecret.json | 4 +- helm/operator-crds/templates/PassSecret.yaml | 4 +- helm/operator-e2e/README.md | 20 +- helm/operator-e2e/values.schema.json | 2 +- helm/operator-e2e/values.yaml | 2 +- helm/operator/Chart.yaml | 2 +- helm/operator/README.md | 25 +- helm/operator/templates/deployment.yaml | 2 + helm/operator/values.schema.json | 9 +- helm/operator/values.yaml | 11 +- package.json | 5 +- poetry.lock | 348 +++++++++---------- pyproject.toml | 2 +- requirements.txt | 12 +- src/passoperator/__init__.py | 4 +- src/passoperator/daemon.py | 87 ++++- src/passoperator/gpg.py | 2 +- src/passoperator/secret.py | 42 ++- src/test/e2e/lib.py | 48 ++- src/test/e2e/test_crd.py | 205 +++++++++-- 22 files changed, 547 insertions(+), 297 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index c41640b..ffd52fa 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -2,7 +2,7 @@ _extends: .github repository: name: pass-operator - description: A kubernetes operator that syncs and decrypts secrets from pass git repositories + description: A Kubernetes operator that syncs and decrypts secrets from pass store git repositories homepage: https://github.com/premiscale/pass-operator # A comma-separated list of topics to set on the repository diff --git a/helm/README.md b/helm/README.md index 89e6188..6eb4255 100644 --- a/helm/README.md +++ b/helm/README.md @@ -1,5 +1,5 @@ # Operator and supporting Helm charts -- [`operator`](https://github.com/premiscale/pass-operator/tree/master/helm/operator) is the chart for deploying the Pass store operator -- [`operator-crds`](https://github.com/premiscale/pass-operator/tree/master/helm/operator-crds) is the chart for deploying the Pass store operator's custom resource definitions (CRDs) -- [`operator-e2e`](https://github.com/premiscale/pass-operator/tree/master/helm/operator-e2e) is the a for e2e-testing the operator and its CRDs. \ No newline at end of file +- [`operator`](https://github.com/premiscale/pass-operator/tree/master/helm/operator) is the chart for deploying the pass store operator +- [`operator-crds`](https://github.com/premiscale/pass-operator/tree/master/helm/operator-crds) is the chart for deploying the pass store operator's custom resource definitions (CRDs) +- [`operator-e2e`](https://github.com/premiscale/pass-operator/tree/master/helm/operator-e2e) is a chart for e2e-testing the operator and its CRDs. Unless you're a developer, you won't need to use this Helm chart. \ No newline at end of file diff --git a/helm/operator-crds/_json/PassSecret.json b/helm/operator-crds/_json/PassSecret.json index 415e120..72b5acf 100644 --- a/helm/operator-crds/_json/PassSecret.json +++ b/helm/operator-crds/_json/PassSecret.json @@ -63,9 +63,9 @@ "type": "object", "properties": { "encryptedData": { - "description": "Data to be contained in the secret.\n", + "description": "Data to be contained in the secret. Can be as few as zero key-value pairs, since the K8s API accepts Secrets with no data.\n", "type": "object", - "minProperties": 1, + "minProperties": 0, "additionalProperties": { "type": "string" } diff --git a/helm/operator-crds/templates/PassSecret.yaml b/helm/operator-crds/templates/PassSecret.yaml index 101fbd6..12150fa 100644 --- a/helm/operator-crds/templates/PassSecret.yaml +++ b/helm/operator-crds/templates/PassSecret.yaml @@ -59,9 +59,9 @@ spec: properties: encryptedData: description: |+ - Data to be contained in the secret. + Data to be contained in the secret. Can be as few as zero key-value pairs, since the K8s API accepts Secrets with no data. type: object - minProperties: 1 + minProperties: 0 # I tried to get patternProperties to work (1/20/24), but my control plane insisted it was a forbidden field. # https://github.com/kubernetes/kubernetes/issues/59485#issuecomment-366600460 # Using the work-around I've found in Bitnami's SealedSecrets CRD for now ~ diff --git a/helm/operator-e2e/README.md b/helm/operator-e2e/README.md index 3bccf4c..4bcf2f0 100644 --- a/helm/operator-e2e/README.md +++ b/helm/operator-e2e/README.md @@ -19,16 +19,16 @@ interface to installing this chart and others in a local e2e testing environment ### E2E Deployment -| Name | Description | Value | -| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------- | -| `deployment.pullSecrets` | A list of pull secret names. These names are automatically mapped to key: secretname in the imagePullSecrets field. | `[]` | -| `deployment.image.name` | The name of the image.## @param deployment.image.name [string, default: pass-operator] The name of the image. | `pass-operator` | -| `deployment.image.tag` | The tag of the image. The default is "ignore" to ensure users provide a tag. | `ignore` | -| `deployment.image.pullPolicy` | The pull policy of the image. | `Always` | -| `deployment.resources` | Set resources for the pod. | `{}` | -| `deployment.livenessProbe` | Configure the liveness probe for the pod. The defaults are set to check that SSHd is listening on TCP port 22. | `{}` | -| `deployment.podSecurityContext` | Configure the security context for the pod. | `{}` | -| `deployment.containerSecurityContext` | Configure the security context for the container. | `{}` | +| Name | Description | Value | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `deployment.pullSecrets` | A list of pull secret names. These names are automatically mapped to key: secretname in the imagePullSecrets field. | `[]` | +| `deployment.image.name` | The name of the image.## @param deployment.image.name [string, default: pass-operator] The name of the image. | `pass-operator-e2e` | +| `deployment.image.tag` | The tag of the image. The default is "ignore" to ensure users provide a tag. | `ignore` | +| `deployment.image.pullPolicy` | The pull policy of the image. | `Always` | +| `deployment.resources` | Set resources for the pod. | `{}` | +| `deployment.livenessProbe` | Configure the liveness probe for the pod. The defaults are set to check that SSHd is listening on TCP port 22. | `{}` | +| `deployment.podSecurityContext` | Configure the security context for the pod. | `{}` | +| `deployment.containerSecurityContext` | Configure the security context for the container. | `{}` | ### Operator Configuration diff --git a/helm/operator-e2e/values.schema.json b/helm/operator-e2e/values.schema.json index 715b46e..548fee7 100644 --- a/helm/operator-e2e/values.schema.json +++ b/helm/operator-e2e/values.schema.json @@ -34,7 +34,7 @@ "name": { "type": "string", "description": "The name of the image.## @param deployment.image.name [string, default: pass-operator] The name of the image.", - "default": "pass-operator" + "default": "pass-operator-e2e" }, "tag": { "type": "string", diff --git a/helm/operator-e2e/values.yaml b/helm/operator-e2e/values.yaml index d55977d..2c52237 100644 --- a/helm/operator-e2e/values.yaml +++ b/helm/operator-e2e/values.yaml @@ -12,7 +12,7 @@ deployment: pullSecrets: [] image: - ## @param deployment.image.name [string, default: pass-operator] The name of the image.## @param deployment.image.name [string, default: pass-operator] The name of the image. + ## @param deployment.image.name [string, default: pass-operator-e2e] The name of the image.## @param deployment.image.name [string, default: pass-operator] The name of the image. name: pass-operator-e2e ## @param deployment.image.tag [string, default: ignore] The tag of the image. The default is "ignore" to ensure users provide a tag. tag: ignore diff --git a/helm/operator/Chart.yaml b/helm/operator/Chart.yaml index 4db539f..138c06e 100644 --- a/helm/operator/Chart.yaml +++ b/helm/operator/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 name: pass-operator -description: A kubernetes operator that syncs and decrypts secrets from password store (https://www.passwordstore.org/) git repositories. +description: A Kubernetes operator that syncs and decrypts secrets from password store (https://www.passwordstore.org/) git repositories. type: application version: 1.0.0 \ No newline at end of file diff --git a/helm/operator/README.md b/helm/operator/README.md index 8fa00cd..5426855 100644 --- a/helm/operator/README.md +++ b/helm/operator/README.md @@ -162,24 +162,24 @@ helm upgrade --install password-store-operator helm/operator/ --namespace passwo ### Operator Deployment -| Name | Description | Value | -| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `deployment.pullSecrets` | A list of pull secret names. These names are automatically mapped to key: secretname in the imagePullSecrets field. | `[]` | -| `deployment.image.name` | The name of the image. | `pass-operator` | -| `deployment.image.tag` | The tag of the image. The default is "ignore" to ensure users provide a tag. | `ignore` | -| `deployment.image.pullPolicy` | The pull policy of the image. | `Always` | -| `deployment.resources` | Set resources for the pod. | `{}` | -| `deployment.livenessProbe` | Configure the liveness probe for the pod. The defaults are set to check the /healthz endpoint on port 8080, which is provided by Kopf. | `{}` | -| `deployment.podSecurityContext` | Configure the security context for the pod. | `{}` | -| `deployment.podSecurityContext.runAsNonRoot` | If true, the pod is required to run as a non-root user. | `true` | -| `deployment.containerSecurityContext` | Configure the security context for the container. | `{}` | +| Name | Description | Value | +| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `deployment.pullSecrets` | A list of pull secret names. These names are automatically mapped to key: secretname in the imagePullSecrets field. | `[]` | +| `deployment.image.name` | The name of the image. | `premiscale/pass-operator` | +| `deployment.image.tag` | The tag of the image. The default is "ignore" to ensure users provide a tag. | `ignore` | +| `deployment.image.pullPolicy` | The pull policy of the image. | `Always` | +| `deployment.resources` | Set resources for the pod. | `{}` | +| `deployment.livenessProbe` | Configure the liveness probe for the pod. The defaults are set to check the /healthz endpoint on port 8080, which is provided by Kopf. | `{}` | +| `deployment.podSecurityContext` | Configure the security context for the pod. | `{}` | +| `deployment.podSecurityContext.runAsNonRoot` | If true, the pod is required to run as a non-root user. | `true` | +| `deployment.containerSecurityContext` | Configure the security context for the container. | `{}` | ### Operator Configuration | Name | Description | Value | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | | `operator.interval` | The interval in seconds to check for changes in the secrets in the pass store. | `60` | -| `operator.initial_delay` | The initial delay in seconds before the first check for changes in the secrets in the pass store. | `3` | +| `operator.initial_delay` | The initial delay in seconds before the first check for changes in the secrets in the pass store. | `60` | | `operator.priority` | The priority of the operator. The higher the number, the higher the priority. Only useful if multiple operators are running. | `100` | | `operator.pass.binary` | The path to the pass binary. | `""` | | `operator.pass.storeSubPath` | A subpath within `~/.password-store`. | `""` | @@ -192,6 +192,7 @@ helm upgrade --install password-store-operator helm/operator/ --namespace passwo | `operator.gpg.key_id` | The key ID of the (private) GPG key. | `""` | | `operator.gpg.value` | The armored string of the private GPG key b64enc'd. | `""` | | `operator.gpg.passphrase` | The passphrase for the GPG key, if there is one. | `""` | +| `operator.gpg.threads` | Number of threads to spawn for decryption. This can help significantly speed up decryption on secrets with many fields. | `4` | | `operator.git.branch` | The branch of the Git repository to clone and pull from. | `main` | | `operator.git.url` | The (SSH) URL of the Git repository. HTTPS is not supported at this time. | `""` | diff --git a/helm/operator/templates/deployment.yaml b/helm/operator/templates/deployment.yaml index 30a523f..e3f98f1 100644 --- a/helm/operator/templates/deployment.yaml +++ b/helm/operator/templates/deployment.yaml @@ -116,6 +116,8 @@ spec: value: {{ .key_id }} {{- end }} {{- end }} + - name: PASS_DECRYPT_THREADS + value: {{ .Values.operator.gpg.threads | quote }} - name: PASS_GIT_URL value: {{ .Values.operator.git.url | quote }} - name: PASS_GIT_BRANCH diff --git a/helm/operator/values.schema.json b/helm/operator/values.schema.json index e6c77e7..0ab4ded 100644 --- a/helm/operator/values.schema.json +++ b/helm/operator/values.schema.json @@ -34,7 +34,7 @@ "name": { "type": "string", "description": "The name of the image.", - "default": "pass-operator" + "default": "premiscale/pass-operator" }, "tag": { "type": "string", @@ -71,7 +71,7 @@ "initial_delay": { "type": "number", "description": "The initial delay in seconds before the first check for changes in the secrets in the pass store.", - "default": "3" + "default": "60" }, "priority": { "type": "number", @@ -150,6 +150,11 @@ "type": "string", "description": "The passphrase for the GPG key, if there is one.", "default": "\"\"" + }, + "threads": { + "type": "number", + "description": "Number of threads to spawn for decryption. This can help significantly speed up decryption on secrets with many fields.", + "default": "4" } } }, diff --git a/helm/operator/values.yaml b/helm/operator/values.yaml index abc13d9..7e554ca 100644 --- a/helm/operator/values.yaml +++ b/helm/operator/values.yaml @@ -12,8 +12,8 @@ deployment: pullSecrets: [] image: - ## @param deployment.image.name [string, default: pass-operator] The name of the image. - name: pass-operator + ## @param deployment.image.name [string, default: premiscale/pass-operator] The name of the image. + name: premiscale/pass-operator ## @param deployment.image.tag [string, default: ignore] The tag of the image. The default is "ignore" to ensure users provide a tag. tag: ignore ## @param deployment.image.pullPolicy [string, default: Always] The pull policy of the image. @@ -53,8 +53,8 @@ operator: ## @param operator.interval [default: 60] The interval in seconds to check for changes in the secrets in the pass store. interval: 60 - ## @param operator.initial_delay [default: 3] The initial delay in seconds before the first check for changes in the secrets in the pass store. - initial_delay: 3 + ## @param operator.initial_delay [default: 60] The initial delay in seconds before the first check for changes in the secrets in the pass store. + initial_delay: 60 ## @param operator.priority [default: 100] The priority of the operator. The higher the number, the higher the priority. Only useful if multiple operators are running. priority: 100 @@ -96,6 +96,9 @@ operator: ## @param operator.gpg.passphrase [string, default: ""] The passphrase for the GPG key, if there is one. passphrase: "" + ## @param operator.gpg.threads [default: 4] Number of threads to spawn for decryption. This can help significantly speed up decryption on secrets with many fields. + threads: 4 + git: ## @param operator.git.branch [string, default: main] The branch of the Git repository to clone and pull from. branch: main diff --git a/package.json b/package.json index 4dd8699..d3286a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pass-operator", - "description": "A kubernetes operator that syncs and decrypts secrets from pass git repositories", + "description": "A Kubernetes operator that syncs and decrypts secrets from pass git repositories", "repository": "git@github.com:premiscale/pass-operator.git", "author": "Emma Doyle ", "license": "GPLv3", @@ -18,6 +18,7 @@ "minikube:delete": "./scripts/minikube.sh delete", "test:e2e": "yarn minikube:up && poetry run pytest --full-trace -vrP src/test/e2e; yarn minikube:delete", "test:unit": "poetry run pytest --full-trace -vrP src/test/unit", - "test:e2e:test": "./src/test_hook.py" + "test:e2e:test": "./src/test_hook.py", + "helm:update:crds:json": "helm template helm/operator-crds/ | yq -o json -M '.' > helm/operator-crds/_json/PassSecret.json" } } \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 3d4f8e1..bd48bda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" @@ -463,63 +463,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.1" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, - {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, - {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, - {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, - {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, - {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, - {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, - {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, - {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, - {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, - {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, - {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, - {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, - {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.extras] @@ -1066,8 +1066,8 @@ files = [ [package.dependencies] aiohttp = [ - {version = "*", markers = "python_version < \"3.12\""}, {version = ">=3.9.0", markers = "python_version >= \"3.12\""}, + {version = "*", markers = "python_version < \"3.12\""}, ] click = "*" iso8601 = "*" @@ -1352,18 +1352,15 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.0" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.0-py2.py3-none-any.whl", hash = "sha256:508ecec98f9f3330b636d4448c0f1a56fc68017c68f1e7857ebc52acf0eb879a"}, + {file = "nodeenv-1.9.0.tar.gz", hash = "sha256:07f144e90dae547bf0d4ee8da0ee42664a42a04e02ed68e06324348dafe4bdb1"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "oauthlib" version = "3.2.2" @@ -1617,8 +1614,8 @@ files = [ astroid = ">=3.2.2,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -1773,101 +1770,104 @@ files = [ [[package]] name = "rapidfuzz" -version = "3.9.0" +version = "3.9.2" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.8" files = [ - {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd375c4830fee11d502dd93ecadef63c137ae88e1aaa29cc15031fa66d1e0abb"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:55e2c5076f38fc1dbaacb95fa026a3e409eee6ea5ac4016d44fb30e4cad42b20"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:488f74126904db6b1bea545c2f3567ea882099f4c13f46012fe8f4b990c683df"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3f2d1ea7cd57dfcd34821e38b4924c80a31bcf8067201b1ab07386996a9faee"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b11e602987bcb4ea22b44178851f27406fca59b0836298d0beb009b504dba266"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3083512e9bf6ed2bb3d25883922974f55e21ae7f8e9f4e298634691ae1aee583"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b33c6d4b3a1190bc0b6c158c3981535f9434e8ed9ffa40cf5586d66c1819fb4b"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcb95fde22f98e6d0480db8d6038c45fe2d18a338690e6f9bba9b82323f3469"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:08d8b49b3a4fb8572e480e73fcddc750da9cbb8696752ee12cca4bf8c8220d52"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e721842e6b601ebbeb8cc5e12c75bbdd1d9e9561ea932f2f844c418c31256e82"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7988363b3a415c5194ce1a68d380629247f8713e669ad81db7548eb156c4f365"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2d267d4c982ab7d177e994ab1f31b98ff3814f6791b90d35dda38307b9e7c989"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0bb28ab5300cf974c7eb68ea21125c493e74b35b1129e629533468b2064ae0a2"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-win32.whl", hash = "sha256:1b1f74997b6d94d66375479fa55f70b1c18e4d865d7afcd13f0785bfd40a9d3c"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c56d2efdfaa1c642029f3a7a5bb76085c5531f7a530777be98232d2ce142553c"}, - {file = "rapidfuzz-3.9.0-cp310-cp310-win_arm64.whl", hash = "sha256:6a83128d505cac76ea560bb9afcb3f6986e14e50a6f467db9a31faef4bd9b347"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e2218d62ab63f3c5ad48eced898854d0c2c327a48f0fb02e2288d7e5332a22c8"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36bf35df2d6c7d5820da20a6720aee34f67c15cd2daf8cf92e8141995c640c25"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:905b01a9b633394ff6bb5ebb1c5fd660e0e180c03fcf9d90199cc6ed74b87cf7"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33cfabcb7fd994938a6a08e641613ce5fe46757832edc789c6a5602e7933d6fa"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1179dcd3d150a67b8a678cd9c84f3baff7413ff13c9e8fe85e52a16c97e24c9b"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47d97e28c42f1efb7781993b67c749223f198f6653ef177a0c8f2b1c516efcaf"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28da953eb2ef9ad527e536022da7afff6ace7126cdd6f3e21ac20f8762e76d2c"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:182b4e11de928fb4834e8f8b5ecd971b5b10a86fabe8636ab65d3a9b7e0e9ca7"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c74f2da334ce597f31670db574766ddeaee5d9430c2c00e28d0fbb7f76172036"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:014ac55b03f4074f903248ded181f3000f4cdbd134e6155cbf643f0eceb4f70f"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c4ef34b2ddbf448f1d644b4ec6475df8bbe5b9d0fee173ff2e87322a151663bd"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fc02157f521af15143fae88f92ef3ddcc4e0cff05c40153a9549dc0fbdb9adb3"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ff08081c49b18ba253a99e6a47f492e6ee8019e19bbb6ddc3ed360cd3ecb2f62"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-win32.whl", hash = "sha256:b9bf90b3d96925cbf8ef44e5ee3cf39ef0c422f12d40f7a497e91febec546650"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5d5684f54d82d9b0cf0b2701e55a630527a9c3dd5ddcf7a2e726a475ac238f2"}, - {file = "rapidfuzz-3.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:a2de844e0e971d7bd8aa41284627dbeacc90e750b90acfb016836553c7a63192"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f81fe99a69ac8ee3fd905e70c62f3af033901aeb60b69317d1d43d547b46e510"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:633b9d03fc04abc585c197104b1d0af04b1f1db1abc99f674d871224cd15557a"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab872cb57ae97c54ba7c71a9e3c9552beb57cb907c789b726895576d1ea9af6f"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdd8c15c3a14e409507fdf0c0434ec481d85c6cbeec8bdcd342a8cd1eda03825"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2444d8155d9846f206e2079bb355b85f365d9457480b0d71677a112d0a7f7128"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83bd3d01f04061c3660742dc85143a89d49fd23eb31eccbf60ad56c4b955617"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ca799f882364e69d0872619afb19efa3652b7133c18352e4a3d86a324fb2bb1"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6993d361f28b9ef5f0fa4e79b8541c2f3507be7471b9f9cb403a255e123b31e1"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:170822a1b1719f02b58e3dce194c8ad7d4c5b39be38c0fdec603bd19c6f9cf81"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e86e39c1c1a0816ceda836e6f7bd3743b930cbc51a43a81bb433b552f203f25"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:731269812ea837e0b93d913648e404736407408e33a00b75741e8f27c590caa2"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8e5ff882d3a3d081157ceba7e0ebc7fac775f95b08cbb143accd4cece6043819"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2003071aa633477a01509890c895f9ef56cf3f2eaa72c7ec0b567f743c1abcba"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-win32.whl", hash = "sha256:13857f9070600ea1f940749f123b02d0b027afbaa45e72186df0f278915761d0"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:134b7098ac109834eeea81424b6822f33c4c52bf80b81508295611e7a21be12a"}, - {file = "rapidfuzz-3.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:2a96209f046fe328be30fc43f06e3d4b91f0d5b74e9dcd627dbfd65890fa4a5e"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:544b0bf9d17170720809918e9ccd0d482d4a3a6eca35630d8e1459f737f71755"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d536f8beb8dd82d6efb20fe9f82c2cfab9ffa0384b5d184327e393a4edde91d"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:30f7609da871510583f87484a10820b26555a473a90ab356cdda2f3b4456256c"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f4a2468432a1db491af6f547fad8f6d55fa03e57265c2f20e5eaceb68c7907e"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a7ec4676242c8a430509cff42ce98bca2fbe30188a63d0f60fdcbfd7e84970"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dcb523243e988c849cf81220164ec3bbed378a699e595a8914fffe80596dc49f"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4eea3bf72c4fe68e957526ffd6bcbb403a21baa6b3344aaae2d3252313df6199"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4514980a5d204c076dd5b756960f6b1b7598f030009456e6109d76c4c331d03c"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9a06a99f1335fe43464d7121bc6540de7cd9c9475ac2025babb373fe7f27846b"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c1ed63345d1581c39d4446b1a8c8f550709656ce2a3c88c47850b258167f3c2"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cd2e6e97daf17ebb3254285cf8dd86c60d56d6cf35c67f0f9a557ef26bd66290"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9bc0f7e6256a9c668482c41c8a3de5d0aa12e8ca346dcc427b97c7edb82cba48"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c09f4e87e82a164c9db769474bc61f8c8b677f2aeb0234b8abac73d2ecf9799"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-win32.whl", hash = "sha256:e65b8f7921bf60cbb207c132842a6b45eefef48c4c3b510eb16087d6c08c70af"}, - {file = "rapidfuzz-3.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9d6478957fb35c7844ad08f2442b62ba76c1857a56370781a707eefa4f4981e1"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65d9250a4b0bf86320097306084bc3ca479c8f5491927c170d018787793ebe95"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:47b7c0840afa724db3b1a070bc6ed5beab73b4e659b1d395023617fc51bf68a2"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a16c48c6df8fb633efbbdea744361025d01d79bca988f884a620e63e782fe5b"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48105991ff6e4a51c7f754df500baa070270ed3d41784ee0d097549bc9fcb16d"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a7f273906b3c7cc6d63a76e088200805947aa0bc1ada42c6a0e582e19c390d7"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c396562d304e974b4b0d5cd3afc4f92c113ea46a36e6bc62e45333d6aa8837e"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68da1b70458fea5290ec9a169fcffe0c17ff7e5bb3c3257e63d7021a50601a8e"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c5b8f9a7b177af6ce7c6ad5b95588b4b73e37917711aafa33b2e79ee80fe709"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3c42a238bf9dd48f4ccec4c6934ac718225b00bb3a438a008c219e7ccb3894c7"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a365886c42177b2beab475a50ba311b59b04f233ceaebc4c341f6f91a86a78e2"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ce897b5dafb7fb7587a95fe4d449c1ea0b6d9ac4462fbafefdbbeef6eee4cf6a"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:413ac49bae291d7e226a5c9be65c71b2630b3346bce39268d02cb3290232e4b7"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8982fc3bd49d55a91569fc8a3feba0de4cef0b391ff9091be546e9df075b81"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-win32.whl", hash = "sha256:3904d0084ab51f82e9f353031554965524f535522a48ec75c30b223eb5a0a488"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:3733aede16ea112728ffeafeb29ccc62e095ed8ec816822fa2a82e92e2c08696"}, - {file = "rapidfuzz-3.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:fc4e26f592b51f97acf0a3f8dfed95e4d830c6a8fbf359361035df836381ab81"}, - {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e33362e98c7899b5f60dcb06ada00acd8673ce0d59aefe9a542701251fd00423"}, - {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb67cf43ad83cb886cbbbff4df7dcaad7aedf94d64fca31aea0da7d26684283c"}, - {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2e106cc66453bb80d2ad9c0044f8287415676df5c8036d737d05d4b9cdbf8e"}, - {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1256915f7e7a5cf2c151c9ac44834b37f9bd1c97e8dec6f936884f01b9dfc7d"}, - {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ae643220584518cbff8bf2974a0494d3e250763af816b73326a512c86ae782ce"}, - {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:491274080742110427f38a6085bb12dffcaff1eef12dccf9e8758398c7e3957e"}, - {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bc5559b9b94326922c096b30ae2d8e5b40b2e9c2c100c2cc396ad91bcb84d30"}, - {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:849160dc0f128acb343af514ca827278005c1d00148d025e4035e034fc2d8c7f"}, - {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:623883fb78e692d54ed7c43b09beec52c6685f10a45a7518128e25746667403b"}, - {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d20ab9abc7e19767f1951772a6ab14cb4eddd886493c2da5ee12014596ad253f"}, - {file = "rapidfuzz-3.9.0.tar.gz", hash = "sha256:b182f0fb61f6ac435e416eb7ab330d62efdbf9b63cf0c7fa12d1f57c2eaaf6f3"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45e0c3e279e70589381f47ad410de7211bac943e827eb09eb8339d2124abca90"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:280ef2f3066df9c486ffd3874d2489978fb8021044c47c006eb96be8d47917d7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe128ac0e05ca3a71d8ff18e70884a64fde00b6fbd2b4d9f59f7a4d798257c55"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fbc0f6e1b6f4063b937d0edcf0a56cbc1d7179ade9b7d6c849c94e44a7b20f6"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df19455c2fb85e86a721111b84ac8dd3685194f0edc9faefb226731ad3e134a7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:801a5d97c465a3467b3cdf50cdcdadec129ddca582b24430f5d24c715c80be9b"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f218524596d261a6cb33cda965687e62dd30def478d39f0befa243642c3985"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5c61d53f293b4e3286919b0e081513367afabcb5aef0b6f899d006117778e558"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0ed70fc6627ae37319f822e5d8d21d561044e0b3331b6f0e6904476faa8d8ed7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96fa229d06ee005d2f46374fb2af65590a590a6fa2fd56e66474829f5fa9adfe"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6609e881b57cabb40d515cc226bbf570e32e768bd2cc688ba026a45ffbc60875"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:204fd4d293ef4d409c4142ddf830b7613924b998670f67e512ab1f880a60218a"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win32.whl", hash = "sha256:5b331a09446bc8f8971cf488c9e6c0f7dbf2739828588e063cf08fd400638a24"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:01a9975984953fe549649e6a4c3f0d9c60707acf458184ec09678d6a57560112"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win_arm64.whl", hash = "sha256:ca4af5d7fc9c17bdc498aa1cab9ecf5140c8535c9cedeba1990bbe4b8be75098"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:300ab53981a5d6831fe7e0f30c407c79520ad0f0ab51b2cece8717689026f495"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4828642acdb075154ce2ff3260f8afb6a17b5b0c8a437efbadac06e9995dd7b"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b262883c3ce93dee1a9a974992961c8098e96b8142e2e01cabdb15ea8105c4a"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8582d85e35641734d6c1f43eb37c1f2a5eda338d3cfa8e651e078246b9ec58"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e33b61ef87e1876d216c479fa2256233b3bb0424465ab2db1d94ab7b8649ae1c"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fa1b3eb21756003a6a3977847dd4e0e9a26e2e02731d9daa5e92a9258e7f0db"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923ae0301a56356364f1159e3005fbeb2191e7a0e8705d5cc1b481d9eea27b97"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8e4041cfd87f0a022aa8a9a187d3b0824e35be2bd9b3bceada11578ddd9ad65"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1f832b430f976727bdbba009ee64acda25412602976fbfb2113d41e765d81849"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6ce5e57e0c6acf5a98ffbdfaf8bccb6e41fbddb9eda3e041f4cc69b7cade5fa0"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d65f34e71102d9cbe733d4ba1c645e7623eef850562501bab1ac79d217831436"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dd9ba4df0db46b9f909289e4687cc7721c622985c4cd169969005dd30fc1e24"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win32.whl", hash = "sha256:34c8bca3fef33d7e71f290de68be2184fac7a9e136fa0ed22b17ec597e181406"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e1a8872c0b8aef95c33db86d25e8bdea6f557b9cdf683123c25035b2bcfb8e"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed02d73e46b7a4604d2bc1e0364b25f204862d40dd162f6b36ee22b9bf6d9df2"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ae6c4ba2778b097397968130f2b0cb795cdc415c115539a49ce798f606152ad5"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7270556ddebaa98fb777f493f17ed6a733b3527de16c43342bce1db109042845"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4625273447bdd94f2ab06b2951cd8b74356c3a48552208279a3ec2947ceee141"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5107b5ec8821453f7cac70b2d0bc4866699b25bff4819ada8b28bf2b11e87f65"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b04c851d309df8261ed42951444db657936234ceddf4032f4409b0214c95ecbe"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aeefff80f3f5d6841c30ffe0cdc84d62874de5a64cff509ae26fbd7478297af8"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cdc106b5a99edd46443449c767287dbb5d4464a7536475a365e368e7ee4d651"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce253a2b7a71a01a4abac71ac31fd05f6ac1f1cd2af2d98fa80fe5c402175e54"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c30407cadbfe99753b7a996f0dd6da490b1e27d318c01db227e8f49770a01ec"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3fc387783f70387a91aababd8a5faeb230931b655ad99bcf838cd72404ba66"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c409852a89535ec8720301a847bab198c1c14d0f34ed07dfabbb90b1dbfc506d"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8603050e547249c1cc8a8dc6a49917076572ea69b04bc51eb1748c403cfc9f46"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win32.whl", hash = "sha256:77bdb96e82d8831f0dd6db83e2ff0d4a731cff53e926d029c65a1dc3ae0f160a"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:09f354fa28e0fd170c6e4eea5e97eea0dba43761067df93109f49a5414ca8584"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:168299c9a2b4f20f10c1bb96d8da0bb05bf1f3b9957be3a0bae5db65ce9f095f"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d87621d60078f87cb52082b1cbf9849afeaa1cb6d0a2b072fce25fe21c8675b4"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c447d0e534418ef3eaabcd890d85c7e9f289c1c6ef6e060a0b1f239799781747"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7161b205f25eff5f88ab809fb05a2a102634e06f452c0deb9535c9f41cd7b0a"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f13a6bbadba8fdd42676c1213ebc692bba9fac00f7db0ae92acc06bb734294c4"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54534743820a15bd0dc30a0a0010825be337973236550fd63587700a7950bbca"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea61851a4c2f93148aa2779458fb3f70a62342d77c9ec3d9d08445c8485b738"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e941f81a60351a842976fea208e6a6701a5899eb8a80b907e57d7c3099337900"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1bbfaf439e48efe3a48cada946cf7678b09c818ce9668e09dac40d05b772f6f8"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:574f464da18d660712e9776072572d462cf6a26144c833d18d9c93778286e023"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8a56c494246d29aacf5ac93ca3cf338d79588a1a5c05d8f496c3f4d7127e9031"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2943b0f17195c000948a7668bb11979ea0e50079a3d3db9d139e51b68c3a7c26"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:27214f93555d4f9b7b1baf107a6ba13e9daee21f1ec6e36418556d04a7ee4d9b"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-win32.whl", hash = "sha256:876c6628fec6241262c27f8fda3c73bab88e205e9b9394c8868361e2eda59048"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf1952b486589ffcfbde2015ca9be15e0f4b0e63d1e2c25f3daec0263fda4e69"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ca9a135060ee4d887d6af86493c3e0eb1c99ca205bca943fe5994dc93e648d5"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:723518c9a18e8bda996d77aa9307b6f8b0e77905702b2772b020adf24191073a"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65eb9aeae73ac60e53a9d6c509daaa217ea256a5e184eb8920c9b15295c48677"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef2964f4eb9a37487c96e5e32167a3c4fa51bf8e899853d0ac67e0465a27702c"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c64a252c96f29667c206726903bb9705c5195f01850360c9b9268de92ac878dc"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b32b03398517b5e33c7f36d625a00fcb1c955b9fe3c939325688175fb21730"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec5f7b1bac77439b624f5acbd8bfe61e7b833678701068b43f7a489c151427c0"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5fd1b49fba8b4b9172eed5b131c1e9864d4d76bebea34359274f16a3591e5f44"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c05b033fc3ff043f48e744f67038af7fd34003047c7810f24bec7c01ce7da05b"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3bea20db89b510d78d017b349b9d87159c32418693ddf091d9035dbe20b4dc0"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:77226a77590f83ee073f4f8cc86a1232da88e24d19d349361faa169fb17ba1cd"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:83ed8bc2c942dc61ab739bbca1ead791143b4639dc92156d3060bd0b6f4541ea"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win32.whl", hash = "sha256:2db70f64974c10b76ae37d5cff6124dce791def815d4fdf5ac16fe60be88d905"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:bdead23114206dea4a22ed3aad6565b99a9e4b3fff9837c423afc556d2814b1a"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win_arm64.whl", hash = "sha256:0ec69ad076cfc7c88323d671613e40bb8754ba95a203556d9a7759e60f0544e8"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:018360654881e75131b227aa96cdaba543c438da881c70a12ca0c86e2c4083b2"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:eaa8178ec9238f32f15b6e49f70b852accda0a848448c4e30bce77c6624ebaba"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dd79b0f90ce609df96d0d48ef4327cf1f0415b9274588a466d3610a775d2f9"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04a1c38a72a50f3e6d346a33d53fa51ba390552b3592fca64a07e54d749b439b"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ca96eec40e815f0cf10b00008f295fd26ca43792a844cf62588a8ea614e160"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c01c515a928f295f49d588b6523f44b474f047f9f2de0079bc57bcd00b870778"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07e14ef260b6f4ee03dff07a0ac95a16aff1ddbc7e6171e07e49d2d61526f3be"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:64f3480bddc12b89969930f12a50a1aeb53e09aad41cf8b27694d83ca1cc7864"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3c9e33ec21755bda1878095537cb84848e9cf6510d4837d22144ba04e33df29"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a70045e84225697ddf67d656aa25b70d6802e2ff339d51f9545fca5b9b13fb8c"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ec1fd328518c33adb9171afe8735137cb7b492e4a81cddc23568f9980c235c"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1fd8458fdac232766d55593c1228c70968f382fdc376c25685273f99b5d1d921"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a373748fddb5403b562b6d682082de360bb08395f44e3cb7e74819461e39a16c"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:45f80856db3e22cb5f96ad1572aa1d004714514625ed4668144661d8a7c7e61f"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e52cf878e0ccbbad0744eb3e2bb83a784645b146f15611bac225bc218f19b"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbe4d3034a8cfe59a2b477375ad7d739b3e5935f10af08abdf64aae55780cad"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd38abfda97e42b30093f207108dcba944beab1edf6624ba757cf57354063177"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:16b41fe360387283a3184ce72d4d26d1928e7ce809268a88e8491a776dd770af"}, + {file = "rapidfuzz-3.9.2.tar.gz", hash = "sha256:c899d78709f8d4bd0059784fa27a9f6c53d04fc4aeaa21de7c0c8e34a7154e88"}, ] [package.extras] @@ -1875,13 +1875,13 @@ full = ["numpy"] [[package]] name = "requests" -version = "2.32.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ - {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, - {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1955,22 +1955,6 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" -[[package]] -name = "setuptools" -version = "69.5.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "shellingham" version = "1.5.4" @@ -2028,24 +2012,24 @@ files = [ [[package]] name = "trove-classifiers" -version = "2024.5.17" +version = "2024.5.22" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove_classifiers-2024.5.17-py3-none-any.whl", hash = "sha256:2ebdddebd43e749ac6b6e9b9aa158ab489603dff147cba8714787729cdb9ea37"}, - {file = "trove_classifiers-2024.5.17.tar.gz", hash = "sha256:d47a6f1c48803091c3fc81f535fecfeef65b558f2b9e4e83df7a79d17bce8bbf"}, + {file = "trove_classifiers-2024.5.22-py3-none-any.whl", hash = "sha256:c43ade18704823e4afa3d9db7083294bc4708a5e02afbcefacd0e9d03a7a24ef"}, + {file = "trove_classifiers-2024.5.22.tar.gz", hash = "sha256:8a6242bbb5c9ae88d34cf665e816b287d2212973c8777dfaef5ec18d72ac1d03"}, ] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -2279,13 +2263,13 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.18.2" +version = "3.19.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, - {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, + {file = "zipp-3.19.0-py3-none-any.whl", hash = "sha256:96dc6ad62f1441bcaccef23b274ec471518daf4fbbc580341204936a5a3dddec"}, + {file = "zipp-3.19.0.tar.gz", hash = "sha256:952df858fb3164426c976d9338d3961e8e8b3758e2e059e0f754b8c4262625ee"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index 818fb31..ab50918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pass-operator" version = "0.0.1" -description = "A kubernetes operator that syncs and decrypts secrets from pass git repositories" +description = "A Kubernetes operator that syncs and decrypts secrets from pass store git repositories" authors = ["Emma Doyle "] maintainers = ["Emma Doyle "] license = "GPL-3.0-or-later" diff --git a/requirements.txt b/requirements.txt index fdb079f..57027bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -459,9 +459,9 @@ pyyaml==6.0.1 ; python_version >= "3.10" and python_version < "4.0" \ requests-oauthlib==2.0.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 \ --hash=sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9 -requests==2.31.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 rsa==4.9 ; python_version >= "3.10" and python_version < "4" \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 @@ -471,9 +471,9 @@ six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" \ smmap==5.0.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da -typing-extensions==4.11.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ - --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a +typing-extensions==4.12.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8 \ + --hash=sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594 urllib3==2.2.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \ --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19 diff --git a/src/passoperator/__init__.py b/src/passoperator/__init__.py index 3027d46..abfec51 100644 --- a/src/passoperator/__init__.py +++ b/src/passoperator/__init__.py @@ -36,7 +36,8 @@ 'PASS_GPG_KEY': os.getenv('PASS_GPG_KEY', ''), 'PASS_GPG_KEY_ID': os.getenv('PASS_GPG_KEY_ID', ''), 'PASS_GIT_URL': os.getenv('PASS_GIT_URL', ''), - 'PASS_GIT_BRANCH': os.getenv('PASS_GIT_BRANCH', 'main') + 'PASS_GIT_BRANCH': os.getenv('PASS_GIT_BRANCH', 'main'), + 'PASS_DECRYPT_THREADS': os.getenv('PASS_DECRYPT_THREADS', '4'), } @@ -46,6 +47,7 @@ float(env['OPERATOR_INITIAL_DELAY']) int(env['OPERATOR_PRIORITY']) IPv4Address(env['OPERATOR_POD_IP']) + int(env['PASS_DECRYPT_THREADS']) except (ValueError, AddressValueError) as e: log.error(e) sys.exit(1) \ No newline at end of file diff --git a/src/passoperator/daemon.py b/src/passoperator/daemon.py index df74ed6..87098d3 100644 --- a/src/passoperator/daemon.py +++ b/src/passoperator/daemon.py @@ -3,7 +3,7 @@ """ -from typing import Any +from typing import Any, List, Tuple from pathlib import Path from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from importlib import metadata @@ -27,6 +27,55 @@ log = logging.getLogger(__name__) +__in_progress_queue: List[Tuple[Any, Any]] = [] + + +def _passsecret_block(body: kopf.Body) -> None: + """ + Block handlers' progress on a PassSecret until it's safe to modify the managed secret. + Decryption takes time, so we want to be sure to queue up any changes. + + Args: + body [kopf.Body]: raw body of the PassSecret. + """ + __in_progress_queue.append( + ( + body['metadata']['name'], + body['metadata']['namespace'] + ) + ) + + +def _is_passsecret_blocked(body: kopf.Body) -> bool: + """ + Check if a PassSecret is blocked from modification. + + Args: + body [kopf.Body]: raw body of the PassSecret. + + Returns: + bool: True if the PassSecret is blocked, else False. + """ + return ( + body['metadata']['name'], + body['metadata']['namespace'] + ) in __in_progress_queue + + +def _lift_passsecret_block(body: kopf.Body) -> None: + """ + Unblock handlers' progress to modify the managed secret. + + Args: + body [kopf.Body]: raw body of the PassSecret. + """ + __in_progress_queue.remove( + ( + body['metadata']['name'], + body['metadata']['namespace'] + ) + ) + @kopf.on.startup() def start(settings: kopf.OperatorSettings, **_: Any) -> None: @@ -43,7 +92,7 @@ def start(settings: kopf.OperatorSettings, **_: Any) -> None: 'secrets.premiscale.com', 'v1alpha1', 'passsecret', # Interval to check every instance of a PassSecret. interval=float(env['OPERATOR_INTERVAL']), - # Initial delay in seconds before reviewing all managed PassSecrets. + # Initial delay in seconds before reviewing managed PassSecrets. initial_delay=float(env['OPERATOR_INITIAL_DELAY']), # Don't delay if the prior reconciliation hasn't completed. sharp=True) @@ -57,11 +106,18 @@ def reconciliation(body: kopf.Body, **_: Any) -> None: body [kopf.Body]: raw body of the PassSecret. """ + # Before we parse and decrypt anything, which is more expensive, check if this PassSecret is already in progress. + if _is_passsecret_blocked(body): + log.info(f'PassSecret "{body["metadata"]["name"]}" is still in progress. Skipping.') + return None + # Ensure the GPG key ID in ~/.password-store/${PASS_DIRECTORY}/.gpg-id did not change with the git update. check_gpg_id( path=f'{env["PASS_DIRECTORY"]}/.gpg-id' ) + v1 = client.CoreV1Api() + # Create a new PassSecret object with an up-to-date managedSecret decrypted value from the pass store. passSecretObj = PassSecret.from_kopf(body) @@ -69,9 +125,6 @@ def reconciliation(body: kopf.Body, **_: Any) -> None: f'Reconciling PassSecret "{passSecretObj.metadata.name}" managed Secret "{passSecretObj.spec.managedSecret.metadata.name}" in Namespace "{passSecretObj.spec.managedSecret.metadata.namespace}" against password store.' ) - v1 = client.CoreV1Api() - - try: secret = v1.read_namespaced_secret( name=passSecretObj.spec.managedSecret.metadata.name, @@ -113,6 +166,7 @@ def reconciliation(body: kopf.Body, **_: Any) -> None: else: raise kopf.PermanentError(e) + # @kopf.on.cleanup() # def cleanup(**kwargs) -> None: # pass @@ -152,7 +206,7 @@ def lookup_managing_passsecret(managedSecretName: str) -> PassSecret | None: @kopf.on.update('secrets.premiscale.com', 'v1alpha1', 'passsecret') -def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf.Meta, **_: Any) -> None: +def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf.Meta, body: kopf.Body, **_: Any) -> None: """ An update was received on the PassSecret object, so attempt to update the corresponding Secret. @@ -160,6 +214,7 @@ def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf. Args: body [kopf.Body]: raw body of the PassSecret. + meta [kopf.Meta]: metadata of the PassSecret. old [kopf.BodyEssence]: old body of the PassSecret. new [kopf.BodyEssence]: new body of the PassSecret. """ @@ -170,6 +225,8 @@ def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf. } } + _passsecret_block(body) + # Parse the old PassSecret manifest. try: oldPassSecret = PassSecret.from_kopf( @@ -179,6 +236,7 @@ def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf. } ) except (ValueError, KeyError) as e: + _lift_passsecret_block(body) raise kopf.PermanentError(e) # Parse the new PassSecret manifest. @@ -190,6 +248,7 @@ def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf. } ) except (ValueError, KeyError) as e: + _lift_passsecret_block(body) raise kopf.PermanentError(e) v1 = client.CoreV1Api() @@ -224,6 +283,8 @@ def update(old: kopf.BodyEssence | Any, new: kopf.BodyEssence | Any, meta: kopf. ) except client.ApiException as e: raise kopf.PermanentError(e) + finally: + _lift_passsecret_block(body) @kopf.on.create('secrets.premiscale.com', 'v1alpha1', 'passsecret') @@ -235,8 +296,12 @@ def create(body: kopf.Body, **_: Any) -> None: body [kopf.Body]: raw body of the created PassSecret. """ try: + # Indicate to the reconciliation loop that this PassSecret is in progress. + _passsecret_block(body) + passSecretObj = PassSecret.from_kopf(body) except (ValueError, KeyError) as e: + _lift_passsecret_block(body) raise kopf.PermanentError(e) log.info(f'PassSecret "{passSecretObj.metadata.name}" created') @@ -250,13 +315,17 @@ def create(body: kopf.Body, **_: Any) -> None: **passSecretObj.spec.managedSecret.to_client_dict(finalizers=False) ) ) + log.info( f'Created PassSecret "{passSecretObj.metadata.name}" managed Secret "{passSecretObj.spec.managedSecret.metadata.name}" in Namespace "{passSecretObj.spec.managedSecret.metadata.namespace}"' ) except client.ApiException as e: if e.status == HTTPStatus.CONFLICT: raise kopf.TemporaryError(f'Duplicate PassSecret "{passSecretObj.metadata.name}" managed Secret "{passSecretObj.spec.managedSecret.metadata.name}" in Namespace "{passSecretObj.spec.managedSecret.metadata.namespace}". Skipping.') + raise kopf.PermanentError(e) + finally: + _lift_passsecret_block(body) @kopf.on.delete('secrets.premiscale.com', 'v1alpha1', 'passsecret') @@ -268,8 +337,12 @@ def delete(body: kopf.Body, **_: Any) -> None: body [kopf.Body]: raw body of the deleted PassSecret. """ try: + # Indicate to the reconciliation loop that this PassSecret is in progress. + _passsecret_block(body) + passSecretObj = PassSecret.from_kopf(body) except (ValueError, KeyError) as e: + _lift_passsecret_block(body) raise kopf.PermanentError(e) log.info(f'PassSecret "{passSecretObj.metadata.name}" deleted') @@ -286,6 +359,8 @@ def delete(body: kopf.Body, **_: Any) -> None: if e.status == HTTPStatus.NOT_FOUND: log.warning(f'PassSecret "{passSecretObj.metadata.name}" managed Secret "{passSecretObj.spec.managedSecret.metadata.name}" was not found. Skipping.') raise kopf.PermanentError(e) + finally: + _lift_passsecret_block(body) def check_gpg_id(path: Path | str, remove: bool =False) -> None: diff --git a/src/passoperator/gpg.py b/src/passoperator/gpg.py index ed756f2..27de7e1 100644 --- a/src/passoperator/gpg.py +++ b/src/passoperator/gpg.py @@ -32,7 +32,7 @@ def decrypt(path: Path, home: Path = Path('~/.gnupg').expanduser(), passphrase: try: # https://gnupg.readthedocs.io/en/latest/#decryption decrypted_file = gpg.decrypt_file( - f'{str(path)}.gpg', + f'{path}.gpg', always_trust=True, passphrase=passphrase ) diff --git a/src/passoperator/secret.py b/src/passoperator/secret.py index 7fedb54..386b7b7 100644 --- a/src/passoperator/secret.py +++ b/src/passoperator/secret.py @@ -10,6 +10,7 @@ from cattrs import structure as from_dict from humps import camelize from datetime import datetime +from concurrent.futures import ThreadPoolExecutor from passoperator.gpg import decrypt from passoperator.utils import b64Dec, b64Enc @@ -121,11 +122,18 @@ def to_client_dict(self, finalizers: bool = False) -> Dict: finalizers (bool): if True, include the finalizers field in the output. """ d = dict(self.to_dict(export=True)) - d.pop('data') + + # Refine the data a bit so it corresponds to the k8s.client.V1Secret object. + if 'data' in d and not d['data']: + d.pop('data') + d.pop('apiVersion') + if not finalizers: d.pop('finalizers') + d['string_data'] = self.stringData + return d def __eq__(self, __value: object) -> bool: @@ -193,19 +201,29 @@ def decrypt(ms: ManagedSecret, encryptedData: Dict[str, str]) -> ManagedSecret: """ stringData = {} - for secretKey in encryptedData: - secretPath = encryptedData[secretKey] + with ThreadPoolExecutor(max_workers=int(env['PASS_DECRYPT_THREADS'])) as executor: + threads: Dict = {} + + # Decrypt each secret in a separate thread and store the result in a dictionary. + for secretKey in encryptedData: + secretPath = encryptedData[secretKey] - decryptedSecret = decrypt( - Path(f'{env["PASS_DIRECTORY"]}/{secretPath}'), - passphrase=env['PASS_GPG_PASSPHRASE'] - ) + # Because we're only decrypting, this operation should be threadsafe. + threads[secretKey] = executor.submit( + decrypt, + Path(f'{env["PASS_DIRECTORY"]}/{secretPath}'), + passphrase=env['PASS_GPG_PASSPHRASE'] + ) - if decryptedSecret: - stringData[secretKey] = decryptedSecret - else: - log.error(f'Failed to decrypt secret at path: {secretPath}') - stringData[secretKey] = '' + for secretKey in threads: + thread = threads[secretKey] + decryptedSecret = thread.result() + + if decryptedSecret is not None: + stringData[secretKey] = decryptedSecret + else: + log.error(f'Failed to decrypt secret at path: {secretPath}') + stringData[secretKey] = '' return ManagedSecret( metadata=ms.metadata, diff --git a/src/test/e2e/lib.py b/src/test/e2e/lib.py index 735d834..7e88c83 100644 --- a/src/test/e2e/lib.py +++ b/src/test/e2e/lib.py @@ -86,11 +86,15 @@ def _check_namespaced_pods(_namespace: client.V1Namespace) -> bool: # Ensure that all pods on the cluster are in a healthy state. for ns in namespaces: - while True: + # Try for up to 3 minutes to ensure all pods are running or completed or completely ready. + # Otherwise, return False and exit early. + for _ in range(60): if _check_namespaced_pods(ns): break log.warning(f'Namespace {ns.metadata.name} has pods that are not running or completed or completely ready.') # type: ignore syncsleep(3) + else: + return False log.info('All pods in the cluster are running or completed or completely ready.') @@ -185,12 +189,22 @@ def build_e2e_image( Returns: int: The return code of the docker build or push command that failed, or 0 if both succeeded. """ - return run([ - 'docker', 'build', '-t', f'{registry}/pass-operator-e2e:{tag}', '-f', './src/test/Dockerfile.e2e', './src/test/', - '--build-arg', f'PASS_VERSION={pass_version}', - '--build-arg', f'TINI_VERSION={tini_version}', - '--build-arg', f'ARCHITECTURE={architecture}' - ]).returnCode or run(['docker', 'push', f'{registry}/pass-operator-e2e:{tag}']).returnCode + if (ret := run([ + 'docker', 'build', '-t', f'{registry}/pass-operator-e2e:{tag}', '-f', './src/test/Dockerfile.e2e', './src/test/', + '--build-arg', f'PASS_VERSION={pass_version}', + '--build-arg', f'TINI_VERSION={tini_version}', + '--build-arg', f'ARCHITECTURE={architecture}' + ]).returnCode) == 0: + # Retry pushing the image up to 3 times before failing. + for i in range(3): + if (ret := run(['docker', 'push', f'{registry}/pass-operator-e2e:{tag}']).returnCode) == 0: + return ret + elif i != 2: + log.warning(f'Failed to push e2e image to {registry}/pass-operator-e2e:{tag}. Retrying...') + syncsleep(3) + else: + log.error(f'Failed to push e2e image to {registry}/pass-operator-e2e:{tag}.') + return ret def install_pass_operator_e2e( @@ -215,6 +229,7 @@ def install_pass_operator_e2e( return run([ 'helm', 'upgrade', '--install', '--namespace', namespace, '--create-namespace', 'pass-operator-e2e', './helm/operator-e2e', '--set', f'global.image.registry={registry}', + '--set', 'deployment.image.name=pass-operator-e2e', '--set', f'deployment.image.tag={image_tag}', '--set', f'operator.ssh.createSecret={str(ssh_createSecret).lower()}', '--set', f'operator.pass.storeSubPath={pass_storeSubPath}', @@ -293,10 +308,20 @@ def build_operator_image( Returns: int: The return code of the docker build or push command that failed, or 0 if both succeeded. """ - return run([ - 'docker', 'build', '-t', f'{registry}/pass-operator:{tag}', '-f', './Dockerfile', '.', - '--target', 'develop' - ]).returnCode or run(['docker', 'push', f'{registry}/pass-operator:{tag}']).returnCode + if (ret := run([ + 'docker', 'build', '-t', f'{registry}/pass-operator:{tag}', '-f', './Dockerfile', '.', + '--target', 'develop' + ]).returnCode) == 0: + # Retry pushing the image up to 3 times before failing. + for i in range(3): + if (ret := run(['docker', 'push', f'{registry}/pass-operator:{tag}']).returnCode) == 0: + return ret + elif i != 2: + log.warning(f'Failed to push e2e image to {registry}/pass-operator:{tag}. Retrying...') + syncsleep(3) + else: + log.error(f'Failed to push e2e image to {registry}/pass-operator:{tag}.') + return ret def install_pass_operator( @@ -324,6 +349,7 @@ def install_pass_operator( return run([ 'helm', 'upgrade', '--install', 'pass-operator', './helm/operator', '--namespace', namespace, '--create-namespace', '--set', f'global.image.registry={registry}', + '--set', 'deployment.image.name=pass-operator', '--set', f'deployment.image.tag={image_tag}', '--set', f'operator.interval={operator_interval}', '--set', 'operator.initial_delay=1', diff --git a/src/test/e2e/test_crd.py b/src/test/e2e/test_crd.py index 78691e0..229b28a 100644 --- a/src/test/e2e/test_crd.py +++ b/src/test/e2e/test_crd.py @@ -8,6 +8,7 @@ from time import sleep from deepdiff import DeepDiff from http import HTTPStatus +from humps import decamelize from passoperator.utils import b64Enc @@ -37,29 +38,22 @@ uninstall_pass_operator_e2e ) -import logging -import sys - - -log = logging.getLogger(__name__) -logging.basicConfig( - stream=sys.stdout, - format='%(asctime)s | %(levelname)s | %(name)s | %(message)s', - level=logging.INFO -) - -# Generate GPG and SSH keypairs for use in testing. config.load_kube_config( context='pass-operator' ) +ATTEMPTS_TO_READ_SECRETS = 10 + + class PassSecretE2E(TestCase): """ Methods for testing the Kubernetes operator end-to-end. """ + maxDiff = None + ## Test data attributes. passsecret_data_singular: dict @@ -88,13 +82,21 @@ class PassSecretE2E(TestCase): @staticmethod def convertDecryptedPassSecrets(passsecret: dict, decrypted_passsecret: dict) -> dict: """ - This is just a helper method for DeepDiff'ing locally-unencrypted (random) PassSecret data with the managed + This is just a helper method for when we're DeepDiff'ing locally-unencrypted (random) PassSecret data with the managed secret data that the operator decrypts and creates. This method is similar to what src.operator.secret looked like - before it was refactored with attrs and cattrs. + before it was refactored with attrs and cattrs, but it's different in that it ties together the randomly generated + secret data with the original PassSecret objects, which is only useful for e2e testing. + + This method also ties together the snake_case and camelCase fields of the PassSecret and managed secret objects returned + by the k8s client. Args: - passsecret (dict): The PassSecret object. - decrypted_passsecret (dict): The decrypted PassSecret object. + passsecret (dict): The PassSecret object that's submitted to the cluster to generate a managed secret. + decrypted_passsecret (dict): The decrypted PassSecret object, so we can tie the managed secret and generated test data together. + + Returns: + dict: The expected managed secret object from tying together a PassSecret manifest and a decrypted PassSecret + manifest, which is generated on-the-fly every e2e run. """ converted_managed_secret = { @@ -106,15 +108,25 @@ def convertDecryptedPassSecrets(passsecret: dict, decrypted_passsecret: dict) -> 'data': {} } - # Now populate the data with the proper keys. - for key in passsecret['spec']['managedSecret']['data']: - passstore_path = passsecret['spec']['managedSecret']['data'][key] + # Handle optional fields. + if 'immutable' in passsecret['spec']['managedSecret']: + converted_managed_secret['immutable'] = passsecret['spec']['managedSecret']['immutable'] + else: + converted_managed_secret['immutable'] = None - converted_managed_secret['data'][key] = b64Enc( - decrypted_passsecret['spec']['managedSecret']['data'][passstore_path] - ) + if 'stringData' in passsecret['spec']['managedSecret']: + converted_managed_secret['string_data'] = passsecret['spec']['managedSecret']['stringData'] + else: + converted_managed_secret['string_data'] = None - return converted_managed_secret + decamelized_converted_managed_secret = decamelize(converted_managed_secret) + + # Now populate the data with the proper keys. + for key in passsecret['spec']['encryptedData']: + passstore_path = passsecret['spec']['encryptedData'][key] + decamelized_converted_managed_secret['data'][key] = b64Enc(decrypted_passsecret['spec']['encryptedData'][passstore_path]) + + return decamelized_converted_managed_secret @classmethod def setUpClass(cls) -> None: @@ -182,7 +194,7 @@ def setUpClass(cls) -> None: pass_storeSubPath='repo', gpg_createSecret=True, gpg_passphrase=gpg_passphrase, - git_url='root@pass-operator-e2e:/opt/operator/repo.git', + git_url='root@pass-operator-e2e:/root/repo.git', git_branch='main' ) @@ -192,14 +204,15 @@ def setUp(self) -> None: """ # Wait for all pods in the cluster to be ready before running each test. - check_cluster_pod_status() + if not check_cluster_pod_status(): + self.fail('Not all pods are determined ready in the cluster.') # def tearDown(self) -> None: # return super().tearDown() def test_operator_singular_data(self) -> None: """ - Test that the operator is running as intended in the cluster. + Test that a PassSecret object with a single encrypted data field is created and managed by the operator correctly. """ v1custom = client.CustomObjectsApi() v1 = client.CoreV1Api() @@ -213,8 +226,10 @@ def test_operator_singular_data(self) -> None: body=self.passsecret_data_singular ) + _managedSecret: client.V1Secret + # Check that the managed secret exists. - while True: + for _ in range(ATTEMPTS_TO_READ_SECRETS): try: _managedSecret = v1.read_namespaced_secret( name='singular-data', @@ -225,36 +240,154 @@ def test_operator_singular_data(self) -> None: if e.status == HTTPStatus.NOT_FOUND: sleep(3) continue - log.error(e) + self.fail( + f'Failed to create managed secret for singular-data PassSecret: {e}' + ) + else: + self.fail( + 'Failed to read managed secret within the alotted time period.' + ) - # Check that the managed secret contains the proper data. + # Check that the managed secret contains the expected data. This is done by asserting that the difference between the + # expected managed secret data and the actual managed secret data is an empty dictionary, with the exception of a few + # fields that are not relevant to the test. self.assertDictEqual( DeepDiff( - dict(_managedSecret.data), + _managedSecret.to_dict(), # Convert the unencrypted PassSecret data to the format that the operator would have created the managed secret in. self.convertDecryptedPassSecrets( self.passsecret_data_singular, self.decrypted_passsecret_data_singular ), + include_paths=[ + "root['metadata']['name']", + "root['metadata']['namespace']", + ], exclude_paths=[ - "root['metadata']['labels']", - "root['metadata']['annotations']", - "root['spec']['managedSecret']['apiVersion']", - "root['spec']['managedSecret']['finalizers']" - ] + "root['metadata']" + ], + ignore_order=True ), {} ) def test_operator_zero_data(self) -> None: """ - Test that the operator is running as intended in the cluster. + Test that the application of a PassSecret with no data is handled correctly by the operator. """ + v1custom = client.CustomObjectsApi() + v1 = client.CoreV1Api() + + # Create a namespaced PassSecret object, then vet that the managed secret both exists and contains the proper data (i.e., the operator did its job). + v1custom.create_namespaced_custom_object( + group='secrets.premiscale.com', + version='v1alpha1', + namespace='pass-operator', + plural='passsecrets', + body=self.passsecret_data_zero + ) + + _managedSecret: client.V1Secret + + # Check that the managed secret exists. + for _ in range(ATTEMPTS_TO_READ_SECRETS): + try: + _managedSecret = v1.read_namespaced_secret( + name='zero-data', + namespace='pass-operator' + ) + break + except client.rest.ApiException as e: + if e.status == HTTPStatus.NOT_FOUND: + sleep(3) + continue + self.fail( + f'Failed to create managed secret for zero-data PassSecret: {e}' + ) + else: + self.fail( + 'Failed to read managed secret within the alotted time period.' + ) + + self.assertDictEqual( + DeepDiff( + _managedSecret.to_dict(), + self.convertDecryptedPassSecrets( + self.passsecret_data_zero, + self.decrypted_passsecret_data_zero + ), + include_paths=[ + "root['metadata']['name']", + "root['metadata']['namespace']", + ], + exclude_paths=[ + "root['metadata']" + ], + ignore_order=True + ), + {} + ) def test_operator_multiple_data(self) -> None: """ Test that the operator is running as intended in the cluster. """ + v1custom = client.CustomObjectsApi() + v1 = client.CoreV1Api() + + # Create a namespaced PassSecret object, then vet that the managed secret both exists and contains the proper data (i.e., the operator did its job). + v1custom.create_namespaced_custom_object( + group='secrets.premiscale.com', + version='v1alpha1', + namespace='pass-operator', + plural='passsecrets', + body=self.passsecret_data_multiple + ) + + _managedSecret: client.V1Secret + + # Check that the managed secret exists. + for _ in range(ATTEMPTS_TO_READ_SECRETS): + try: + _managedSecret = v1.read_namespaced_secret( + name='multiple-data', + namespace='pass-operator' + ) + break + except client.rest.ApiException as e: + if e.status == HTTPStatus.NOT_FOUND: + sleep(3) + continue + self.fail( + f'Failed to create managed secret for multiple-data PassSecret: {e}' + ) + else: + self.fail( + 'Failed to read managed secret within the alotted time period.' + ) + + # Check that the managed secret contains the expected data. This is done by asserting that the difference between the + # expected managed secret data and the actual managed secret data is an empty dictionary, with the exception of a few + # fields that are not relevant to the test. + self.assertDictEqual( + DeepDiff( + _managedSecret.to_dict(), + # Convert the unencrypted PassSecret data to the format that the operator would have created the managed secret in. + self.convertDecryptedPassSecrets( + self.passsecret_data_multiple, + self.decrypted_passsecret_data_multiple + ), + include_paths=[ + "root['metadata']['name']", + "root['metadata']['namespace']", + ], + exclude_paths=[ + "root['metadata']" + ], + ignore_order=True + ), + {} + ) def test_operator_singular_data_immutable(self) -> None: """