diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e4c69bf5..e3e2a0de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: check-toml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.7.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/Dockerfile b/Dockerfile index 7ac21624..b1a965fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,4 +61,4 @@ USER appuser EXPOSE 8080 # Run the application. -CMD ["uvicorn", "mobu.main:app", "--host", "0.0.0.0", "--port", "8080"] +CMD ["uvicorn", "mobu.main:create_app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/changelog.d/20241112_163347_danfuchs_app_metrics.md b/changelog.d/20241112_163347_danfuchs_app_metrics.md new file mode 100644 index 00000000..937a5f93 --- /dev/null +++ b/changelog.d/20241112_163347_danfuchs_app_metrics.md @@ -0,0 +1,5 @@ + + +### Backwards-incompatible changes + +- All app config, including autostart config (and excluding secrets, which still come from env vars) now comes from a single YAML file, provisioned by a single `ConfigMap` in Phalanx. diff --git a/docs/changelog.md b/docs/changelog.md new file mode 120000 index 00000000..04c99a55 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/docs/development/idfdev.rst b/docs/development/idfdev.rst index a040061d..c5a85bb9 100644 --- a/docs/development/idfdev.rst +++ b/docs/development/idfdev.rst @@ -17,56 +17,85 @@ You can run mobu locally while having all of the actual business run against ser set -euo pipefail config_dir="/tmp/mobu_test" - ci_config_file="github.yaml" - ci_config_path="$config_dir/$ci_config_file" - autostart_config_file="autostart.yaml" - autostart_config_path="$config_dir/$autostart_config_file" + config_file="mobu_config.yaml" + config_path="$config_dir/$config_file" mkdir -p "$config_dir" # Note: This whitespace must be actual chars! - cat <<- 'END' > "$ci_config_path" - users: + cat <<-'END' >"$config_path" + logLevel: debug + githubRefreshApp: + acceptedGithubOrgs: + - lsst-sqre + githubCiApp: + users: - username: bot-mobu-ci-local-1 - username: bot-mobu-ci-local-2 - accepted_github_orgs: + scopes: + - "exec:notebook" + - "exec:portal" + - "read:image" + - "read:tap" + acceptedGithubOrgs: - lsst-sqre + autostart: + - name: "my-test" + count: 1 + users: + - username: "bot-mobu-my-test-local" + scopes: + - "exec:notebook" + business: + type: "NotebookRunner" + options: + repo_url: "https://github.com/lsst-sqre/dfuchs-test-mobu.git" + repo_ref: "dfuchs-test-pr" + max_executions: 10 + restart: true + - name: "my-other-test" + count: 1 + users: + - username: "bot-mobu-my-test-local2" + scopes: + - "exec:notebook" + business: + type: "NotebookRunner" + options: + repo_url: "https://github.com/lsst-sqre/dfuchs-test-mobu.git" + repo_ref: "main" + max_executions: 10 + restart: true + - name: "dfuchs-test-tap" + count: 1 + users: + - username: "bot-mobu-dfuchs-test-tap" + scopes: ["read:tap"] + business: + type: "TAPQuerySetRunner" + options: + query_set: "dp0.2" + restart: true + - name: "tap" + count: 1 + users: + - username: "bot-mobu-dfuchs-test-tap-query" + scopes: ["read:tap"] + business: + type: "TAPQueryRunner" + options: + queries: + - "SELECT TOP 10 * FROM TAP_SCHEMA.tables" + restart: true END - # Note: This whitespace must be actual chars! - cat <<- 'END' > "$autostart_config_path" - - name: "my-test" - count: 1 - users: - - username: "bot-mobu-my-test-local" - scopes: - - "exec:notebook" - business: - type: "NotebookRunner" - options: - repo_url: "https://github.com/lsst-sqre/dfuchs-test-mobu.git" - repo_ref: "main" - max_executions: 10 - restart: true - END - + export MOBU_CONFIG_PATH="$config_path" export MOBU_ENVIRONMENT_URL=https://data-dev.lsst.cloud export MOBU_GAFAELFAWR_TOKEN=$(op read "op://Employee/data-dev.lsst.cloud personal token/credential") - export MOBU_AUTOSTART_PATH="$autostart_config_path" - export MOBU_LOG_LEVEL=debug - - # Don't set the MOBU_GITHUB_REFRESH* vars if you don't need that integration - export MOBU_GITHUB_REFRESH_ENABLED=true export MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET=$(op read "op://RSP data-dev.lsst.cloud/mobu/github-refresh-app-webhook-secret") - - # Don't set the MOBU_GITHUB_REFRESH* vars if you don't need that integration - export MOBU_GITHUB_CI_APP_ENABLED=true export MOBU_GITHUB_CI_APP_WEBHOOK_SECRET=$(op read "op://RSP data-dev.lsst.cloud/mobu/github-ci-app-webhook-secret") export MOBU_GITHUB_CI_APP_ID=$(op read "op://RSP data-dev.lsst.cloud/mobu/github-ci-app-id") export MOBU_GITHUB_CI_APP_PRIVATE_KEY=$(op read "op://RSP data-dev.lsst.cloud/mobu/github-ci-app-private-key" | base64 -d) + export UVICORN_PORT=8001 - # Don't set MOBU_GITHUB_CONFIG_PATH if you don't need any of the GitHub integrations. - export MOBU_GITHUB_CONFIG_PATH="$ci_config_path" - - uvicorn mobu.main:app 2>&1 - + uvicorn mobu.main:create_app 2>&1 diff --git a/docs/index.rst b/docs/index.rst index 3c5b06ea..4415af1c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,11 @@ development/index api +.. toctree:: + :hidden: + + changelog + #### Mobu #### diff --git a/docs/operations/github_ci_app.rst b/docs/operations/github_ci_app.rst index e368cd56..27d02549 100644 --- a/docs/operations/github_ci_app.rst +++ b/docs/operations/github_ci_app.rst @@ -40,7 +40,7 @@ In :samp:`applications/mobu/values-{env}.yaml`, add a ``config.githubCiApp`` val .. code:: yaml config: - github: + githubCiApp: acceptedGithubOrgs: - lsst-sqre users: @@ -58,7 +58,7 @@ In :samp:`applications/mobu/values-{env}.yaml`, add a ``config.githubCiApp`` val All items are required. -``accepted_github_orgs`` +``acceptedGithubOrgs`` A list of GitHub organizations from which this instance of Mobu will accept webhook requests. Webhook requests from any orgs not in this list will get a ``403`` response. diff --git a/docs/operations/github_refresh_app.rst b/docs/operations/github_refresh_app.rst index fe9da7ed..34694308 100644 --- a/docs/operations/github_refresh_app.rst +++ b/docs/operations/github_refresh_app.rst @@ -43,6 +43,6 @@ In :samp:`applications/mobu/values-{env}.yaml`, add a ``config.githubRefreshApp` All of these items are required. -``accepted_github_orgs`` +``acceptedGithubOrgs`` A list of GitHub organizations from which this instance of Mobu will accept webhook requests. Webhook requests from any orgs not in this list will get a ``403`` response. diff --git a/pyproject.toml b/pyproject.toml index d8f38749..667d80c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ monkeyflocker = "monkeyflocker.cli:main" [project.urls] Homepage = "https://mobu.lsst.io" Source = "https://github.com/lsst-sqre/mobu" +"Change log" = "https://mobu.lsst.io/changelog.html" +"Issue tracker" = "https://github.com/lsst-sqre/mobu/issues" [build-system] requires = ["setuptools>=61", "wheel", "setuptools_scm[toml]>=6.2"] diff --git a/requirements/dev.txt b/requirements/dev.txt index c3e7cb08..76f6642d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -16,10 +16,6 @@ anyio==4.6.2.post1 \ # via # -c requirements/main.txt # httpx -appnope==0.1.4 \ - --hash=sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee \ - --hash=sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c - # via ipykernel asgi-lifespan==2.1.0 \ --hash=sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308 \ --hash=sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f @@ -399,6 +395,7 @@ greenlet==3.1.1 \ # via # -c requirements/main.txt # -r requirements/dev.in + # sqlalchemy h11==0.14.0 \ --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 @@ -1078,133 +1075,120 @@ respx==0.21.1 \ --hash=sha256:05f45de23f0c785862a2c92a3e173916e8ca88e4caad715dd5f68584d6053c20 \ --hash=sha256:0bd7fe21bfaa52106caa1223ce61224cf30786985f17c63c5d71eff0307ee8af # via -r requirements/dev.in -rpds-py==0.20.1 \ - --hash=sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9 \ - --hash=sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28 \ - --hash=sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2 \ - --hash=sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5 \ - --hash=sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6 \ - --hash=sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6 \ - --hash=sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712 \ - --hash=sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0 \ - --hash=sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338 \ - --hash=sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86 \ - --hash=sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c \ - --hash=sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c \ - --hash=sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f \ - --hash=sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30 \ - --hash=sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f \ - --hash=sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd \ - --hash=sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e \ - --hash=sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963 \ - --hash=sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36 \ - --hash=sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2 \ - --hash=sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e \ - --hash=sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d \ - --hash=sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17 \ - --hash=sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb \ - --hash=sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1 \ - --hash=sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5 \ - --hash=sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163 \ - --hash=sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf \ - --hash=sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356 \ - --hash=sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804 \ - --hash=sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93 \ - --hash=sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a \ - --hash=sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1 \ - --hash=sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496 \ - --hash=sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1 \ - --hash=sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0 \ - --hash=sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899 \ - --hash=sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc \ - --hash=sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db \ - --hash=sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c \ - --hash=sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8 \ - --hash=sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684 \ - --hash=sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191 \ - --hash=sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06 \ - --hash=sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff \ - --hash=sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca \ - --hash=sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8 \ - --hash=sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc \ - --hash=sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a \ - --hash=sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7 \ - --hash=sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4 \ - --hash=sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751 \ - --hash=sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e \ - --hash=sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b \ - --hash=sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75 \ - --hash=sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e \ - --hash=sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74 \ - --hash=sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425 \ - --hash=sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84 \ - --hash=sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d \ - --hash=sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a \ - --hash=sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a \ - --hash=sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83 \ - --hash=sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535 \ - --hash=sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb \ - --hash=sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd \ - --hash=sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979 \ - --hash=sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d \ - --hash=sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d \ - --hash=sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c \ - --hash=sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782 \ - --hash=sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad \ - --hash=sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75 \ - --hash=sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4 \ - --hash=sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad \ - --hash=sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e \ - --hash=sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c \ - --hash=sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780 \ - --hash=sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01 \ - --hash=sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf \ - --hash=sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1 \ - --hash=sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab \ - --hash=sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732 \ - --hash=sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa \ - --hash=sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f \ - --hash=sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3 \ - --hash=sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711 \ - --hash=sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8 \ - --hash=sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a \ - --hash=sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d \ - --hash=sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c \ - --hash=sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519 \ - --hash=sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350 \ - --hash=sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f \ - --hash=sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e \ - --hash=sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb \ - --hash=sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc \ - --hash=sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f \ - --hash=sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977 \ - --hash=sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311 \ - --hash=sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d \ - --hash=sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad \ - --hash=sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982 +rpds-py==0.21.0 \ + --hash=sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba \ + --hash=sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d \ + --hash=sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e \ + --hash=sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a \ + --hash=sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202 \ + --hash=sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271 \ + --hash=sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250 \ + --hash=sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d \ + --hash=sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928 \ + --hash=sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0 \ + --hash=sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d \ + --hash=sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333 \ + --hash=sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e \ + --hash=sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a \ + --hash=sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18 \ + --hash=sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044 \ + --hash=sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677 \ + --hash=sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664 \ + --hash=sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75 \ + --hash=sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89 \ + --hash=sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027 \ + --hash=sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9 \ + --hash=sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e \ + --hash=sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8 \ + --hash=sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44 \ + --hash=sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3 \ + --hash=sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95 \ + --hash=sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd \ + --hash=sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab \ + --hash=sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a \ + --hash=sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560 \ + --hash=sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035 \ + --hash=sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919 \ + --hash=sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c \ + --hash=sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266 \ + --hash=sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e \ + --hash=sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592 \ + --hash=sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9 \ + --hash=sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3 \ + --hash=sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624 \ + --hash=sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9 \ + --hash=sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b \ + --hash=sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f \ + --hash=sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca \ + --hash=sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1 \ + --hash=sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8 \ + --hash=sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590 \ + --hash=sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed \ + --hash=sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952 \ + --hash=sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11 \ + --hash=sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061 \ + --hash=sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c \ + --hash=sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74 \ + --hash=sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c \ + --hash=sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94 \ + --hash=sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c \ + --hash=sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8 \ + --hash=sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf \ + --hash=sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a \ + --hash=sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5 \ + --hash=sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6 \ + --hash=sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5 \ + --hash=sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3 \ + --hash=sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed \ + --hash=sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87 \ + --hash=sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b \ + --hash=sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72 \ + --hash=sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05 \ + --hash=sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed \ + --hash=sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f \ + --hash=sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c \ + --hash=sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153 \ + --hash=sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b \ + --hash=sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0 \ + --hash=sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d \ + --hash=sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d \ + --hash=sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e \ + --hash=sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e \ + --hash=sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd \ + --hash=sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682 \ + --hash=sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4 \ + --hash=sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db \ + --hash=sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976 \ + --hash=sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937 \ + --hash=sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1 \ + --hash=sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb \ + --hash=sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a \ + --hash=sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7 \ + --hash=sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356 \ + --hash=sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be # via # -c requirements/main.txt # jsonschema # referencing -ruff==0.7.1 \ - --hash=sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37 \ - --hash=sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35 \ - --hash=sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c \ - --hash=sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7 \ - --hash=sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a \ - --hash=sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8 \ - --hash=sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99 \ - --hash=sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd \ - --hash=sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565 \ - --hash=sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad \ - --hash=sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378 \ - --hash=sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca \ - --hash=sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250 \ - --hash=sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4 \ - --hash=sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9 \ - --hash=sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112 \ - --hash=sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89 \ - --hash=sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307 +ruff==0.7.2 \ + --hash=sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2 \ + --hash=sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828 \ + --hash=sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f \ + --hash=sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691 \ + --hash=sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4 \ + --hash=sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80 \ + --hash=sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9 \ + --hash=sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e \ + --hash=sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748 \ + --hash=sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8 \ + --hash=sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760 \ + --hash=sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba \ + --hash=sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088 \ + --hash=sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88 \ + --hash=sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b \ + --hash=sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88 \ + --hash=sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8 \ + --hash=sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859 # via -r requirements/dev.in scriv==1.5.1 \ --hash=sha256:30ae9ff8d144f8e0cf394c4e1d379542f1b3823767642955b54ec40dc00b32b6 \ diff --git a/requirements/main.in b/requirements/main.in index b460676d..ec3a04b6 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -18,17 +18,12 @@ httpx httpx-sse jinja2 pydantic>2 -pydantic-settings==2.5.2 -# 2.6 dies horribly with: -# pydantic_core._pydantic_core.ValidationError: 1 validation error for GitHubRefreshAppConfig -# MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET -# Field required [type=missing, input_value={'accepted_github_orgs': ...', 'org2', 'lsst-sqre']}, input_type=dict] -# For further information visit https://errors.pydantic.dev/2.9/v/missing -pyvo +pydantic-settings>=2.6.1 +pyvo<1.6 pyyaml #rubin-nublado-client@git+https://github.com/lsst-sqre/nublado@tickets/DM-47277#subdirectory=client rubin.nublado.client>=8.0.2 -safir>=6.1.0 +safir>=6.5.1 shortuuid structlog websockets diff --git a/requirements/main.txt b/requirements/main.txt index 138150a6..306c0f78 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -54,44 +54,44 @@ anyio==4.6.2.post1 \ # httpx # starlette # watchfiles -astropy==6.1.4 \ - --hash=sha256:024cbba2b20d6abf50395669a010b1c026a0b966ba2292addd354163b0b44e55 \ - --hash=sha256:048ae0883db33f94dea03800211d2e4a0aacaf8d0f0640196815d9d1298b7449 \ - --hash=sha256:22cc40cb137bbd777389d96871d84438537d01e1cd1dd1fc3dca374b4ece3200 \ - --hash=sha256:2491dc9a8f9c046b808995b2e3dc9ad8349c83042729b26c7907b5f7fbc53fd4 \ - --hash=sha256:26c6cc099a312aaf0e473a4ea2ac4279f79ef53c8413e8259758d0f5930b4745 \ - --hash=sha256:27496654ae2e672fa13eee9200caa776f47fbdce9ece55adcfe1d7e37e80341e \ - --hash=sha256:361558e2b093a99bebe69f1fd47fac86a192607a4c16ed39ba0a800b2ab60c34 \ - --hash=sha256:50ab8d8097df76e33b56ab429d07240df6f273ccd267949ff99f8df79c7fcc42 \ - --hash=sha256:52683072d106162ca124e09f2f132de58ca9756e04c25ebd45d56cbdd6feb8f7 \ - --hash=sha256:59576f354772c448300438acc910b5a37b9d15ddfaa5c942746a7253a6c7765f \ - --hash=sha256:601d8a9a8d44f45064d2d8cc963cecf3949d3fbc10c9b6412c241cdc0c686b2c \ - --hash=sha256:7954cdfea00445ed186431888ec8ab12d9e3adfdf316038c44009f57438e6389 \ - --hash=sha256:81152825e80a03562cf2b95164c0ae7f56cd3521208a13221b3afa683c1469a6 \ - --hash=sha256:86e045e7ddfbfc500d015a9a6603be446c492c68fc019e69274b26e098aae50c \ - --hash=sha256:9d4d10d964bfd110751fd47132af5227807d2beca85669b3d00d29cf5bae167d \ - --hash=sha256:a1598720ac43875e602f1fe9766133ab67d50164047b5ed18f5e35d2fa69e914 \ - --hash=sha256:aeec3aa6c12f613b2609dfbf9bac355b5e64446e67643637ee449bdcf583f5e2 \ - --hash=sha256:af49e0a80ee6243727f449a6d8c99e711b2a4fdd1701d92c42964cd3e01a3490 \ - --hash=sha256:b23cb881cf1fa0795b92ab2d86b04339a0a38b336e3a391fd050b6caf695b01b \ - --hash=sha256:b28752d9515dac72358bce58214ef4bae89bfc4af548c98f6052ff91b38e93ee \ - --hash=sha256:b2fa9e1031eb83bee2d0b29f3e4d85a9d8b38ecb1b759a0ffbb8145fe8685d0e \ - --hash=sha256:c0298f0e0d57cc61915a4438d54e47b4517ff6c7c5144ae4679077cfb0826d0b \ - --hash=sha256:cf4ce31973dd18da522bb8f0fac91685ab1fcf21ca81251c79b09ef27d2b0d8f \ - --hash=sha256:cfc23b9e5e899f66f1377dc1116c247e6c7d0f92623ca115ad084a297414de03 \ - --hash=sha256:d3085e201e9d4bd223a543f7e2c0810dc873ea6a81bbdf06b5a987aa10e09bce \ - --hash=sha256:dbd7addf8c79d50e18eeef6c85aee7dd009f2e80756bc4d9b866592eeaca3577 \ - --hash=sha256:ec13a67ffac3d2b79ab2b725b16943133951cdd6833ab8941db798bc4de2da49 \ - --hash=sha256:f1ed2dfb1551b2f140ca636c0156ab6a6a13f4202b7f3bc06fa739058f311d18 \ - --hash=sha256:fa1a48e1dbd8072b3ef8a94334bc5344bf33bfd2b1b0236ca02eaa2cc3ca4b90 +astropy==6.1.5 \ + --hash=sha256:131fb322dd35c4a5e776e477121358bdc58e50a47b9eacbb47d0d9f554c487bf \ + --hash=sha256:1c1ed16cb05ffa03af2191e0281a8aa0e4cde1a05929b96b90ea7c04f5b89dd0 \ + --hash=sha256:23faf820cffce97b734062682cdf61a8edda1438db1715437b9b94672a347c88 \ + --hash=sha256:28cac3a5029a66be962a34de668e94d081c7aa6f895b5031251c1349d3be828f \ + --hash=sha256:2f4666521439df07e195d655d96b4c473c1189495183a27f60182982010cfea3 \ + --hash=sha256:303e21eaa75f832118769fa17db79644a6802969c1a9f95ffdd4cc513802946c \ + --hash=sha256:48b5353462e51dc90e54aab13619c0e38580dc4dcfb568c02332254cb41689a4 \ + --hash=sha256:4a7b7b8e80ec715b686f20a227eb144fef1097adc21f735d685d01af00a9976c \ + --hash=sha256:4e18ea5980cfc0d37169f7ea9c589d2612ca9724afe076a334525301e885f5df \ + --hash=sha256:5b798931700a785000d1b368d512d13ee19dbdbc872f809622e63d36aae28535 \ + --hash=sha256:62aee557f12f0a9e4b7abe5423c65be7832cfe7d3c5e97dbd3e3cac8b91d7c14 \ + --hash=sha256:6d8c2783b86a0dd6b9d216854bb1e920ac6519f68dd9fefba31f964e0de9a47b \ + --hash=sha256:6f9d5f4f90f5b52fd5ce7bc1bd44e582123e70e6772c4bbc16d3c4a441209608 \ + --hash=sha256:706d1581650cc1f4de70ea90ccb59b895f0b4618447d9ed334511cea94212b3a \ + --hash=sha256:7bbd760ee0b959925555bebeb0ea3f565910a4d3d51fd0cef040282605ca25c5 \ + --hash=sha256:7c57d44440488813da38c5b5f8c10e1949775bff67adb97f1a8ef80d926c85d2 \ + --hash=sha256:802a076ebdd63818f55106940dd7726f51aeedae48bbadcb94cdc72b9b803ae0 \ + --hash=sha256:9098dddd0946a6a39df622ecbf8704ed031cfb066381e9d960c0913c2db46b81 \ + --hash=sha256:979bf5a6243c2d6bf8c7295cd3aaff2b3c6d6c9fb2282b0838fc41c6b8249f14 \ + --hash=sha256:9c3d7d809a568d6fb0329a187bd72fd2caf94eaafae02a596ed18b8573a7896d \ + --hash=sha256:c1d315af36ab04e32e4a483d3627b82ff4ee9559bbd13052f252ddaaeb943da8 \ + --hash=sha256:c22ff955036b693abfcfc3687643bca3d87098f424c829ddc6b937fd9c3d73c2 \ + --hash=sha256:d5f6c4817ed191c6f9c92f5ad20500d9428ba41936461d9d1ed36ccb65bb85ff \ + --hash=sha256:d73caddd5067879d616a1a33dd6f6ec10e3054143cf4bc7b86a6fcaea0f5c06b \ + --hash=sha256:d8d4127cf70be3ee20830625071c2f25091b9e5e4a9abb1c12f66accd3baeaf0 \ + --hash=sha256:daf471245bf59542bce04287f41bb043364ccfb0947624b0990225c784ee7e68 \ + --hash=sha256:e725cc290adf8fd463ae22d55ec12038f272f53d23b9a9682db07d39f9edcb8c \ + --hash=sha256:f649b191d8f042628012dcfd9a9554c1fcba3232924b423387664a904460865e \ + --hash=sha256:fa3f749a88a9df5dbef4560b9cd50de994861b78c6860feb5335525804cf2d23 # via pyvo -astropy-iers-data==0.2024.10.28.0.34.7 \ - --hash=sha256:09dcb8c5df99b17025dfa6b392e141d3ef90a1f72225267ab6ffe03da5a1e207 \ - --hash=sha256:6b3dc3b8f4d9bd74cb310e0ed154757b9ecc3ee820f570cf4918169bd64dcbb3 +astropy-iers-data==0.2024.11.4.0.33.34 \ + --hash=sha256:364d5501f6213c58c68c171ca6576f2b1672f7463488bbf37279fe2a04b560a7 \ + --hash=sha256:98dbbc194a90b067a60a6a80b467c4b97af504dae3a379073e034af2bb468e86 # via astropy -async-timeout==5.0.0 \ - --hash=sha256:49675ec889daacfe65ff66d2dde7dd1447a6f4b2f23721022e4ba121f8772a85 \ - --hash=sha256:904719a4bd6e0520047d0ddae220aabee67b877f7ca17bf8cea20f67f6247ae0 +async-timeout==5.0.1 \ + --hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \ + --hash=sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3 # via aiokafka attrs==24.2.0 \ --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ @@ -327,9 +327,9 @@ cryptography==43.0.3 \ dacite==1.8.1 \ --hash=sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe # via dataclasses-avroschema -dataclasses-avroschema==0.64.2 \ - --hash=sha256:4be3f4f97c583a6de8b1782a7aff60741fd772a821aeb58b68604d5cc4421307 \ - --hash=sha256:875f23a0ac2069e52acb84b1979c8549f3716421f683d3a69e9df960a9ef778b +dataclasses-avroschema==0.65.0 \ + --hash=sha256:9f8687f08e1c00fd2f3fdb6226fa2e5443c104eccd02138c36eee64ee4da2f4d \ + --hash=sha256:a209ec628eb338934af0221d54a2f684298ce7f435dfb3be3d469956c346b5ef # via safir fast-depends==2.4.12 \ --hash=sha256:9393e6de827f7afa0141e54fa9553b737396aaf06bd0040e159d1f790487b16d \ @@ -376,9 +376,9 @@ fastavro==1.9.7 \ # via # dataclasses-avroschema # python-schema-registry-client -faststream==0.5.28 \ - --hash=sha256:620f3edd2b85ac3d329726907540027abebc939ea7f16d209ef18de7d6e82f49 \ - --hash=sha256:67794ffa8054169488f211832c50c3a992d897ebba415032a327372e019bda4c +faststream==0.5.29 \ + --hash=sha256:055942ee5b0f27f93a8cb515d55917caa2de18a41591f466367ba32eef1b3565 \ + --hash=sha256:b4f8709dc5c1b06508b83ca697478eee419a8284a4d669f2f436838d59f0b5ad # via safir gidgethub==5.3.0 \ --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ @@ -616,60 +616,62 @@ markupsafe==3.0.2 \ --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 # via jinja2 -numpy==2.1.2 \ - --hash=sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8 \ - --hash=sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466 \ - --hash=sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35 \ - --hash=sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c \ - --hash=sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4 \ - --hash=sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6 \ - --hash=sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0 \ - --hash=sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7 \ - --hash=sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a \ - --hash=sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a \ - --hash=sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e \ - --hash=sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62 \ - --hash=sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2 \ - --hash=sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5 \ - --hash=sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee \ - --hash=sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe \ - --hash=sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a \ - --hash=sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e \ - --hash=sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf \ - --hash=sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c \ - --hash=sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3 \ - --hash=sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86 \ - --hash=sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df \ - --hash=sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98 \ - --hash=sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d \ - --hash=sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2 \ - --hash=sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146 \ - --hash=sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550 \ - --hash=sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8 \ - --hash=sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb \ - --hash=sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e \ - --hash=sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d \ - --hash=sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366 \ - --hash=sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0 \ - --hash=sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db \ - --hash=sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe \ - --hash=sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426 \ - --hash=sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952 \ - --hash=sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03 \ - --hash=sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f \ - --hash=sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7 \ - --hash=sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b \ - --hash=sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17 \ - --hash=sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5 \ - --hash=sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1 \ - --hash=sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142 \ - --hash=sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884 \ - --hash=sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a \ - --hash=sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9 \ - --hash=sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445 \ - --hash=sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1 \ - --hash=sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1 \ - --hash=sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648 +numpy==2.1.3 \ + --hash=sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe \ + --hash=sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0 \ + --hash=sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48 \ + --hash=sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a \ + --hash=sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564 \ + --hash=sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958 \ + --hash=sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17 \ + --hash=sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0 \ + --hash=sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee \ + --hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \ + --hash=sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4 \ + --hash=sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4 \ + --hash=sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6 \ + --hash=sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4 \ + --hash=sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d \ + --hash=sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f \ + --hash=sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f \ + --hash=sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f \ + --hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \ + --hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \ + --hash=sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd \ + --hash=sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23 \ + --hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \ + --hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \ + --hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \ + --hash=sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1 \ + --hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \ + --hash=sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f \ + --hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \ + --hash=sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f \ + --hash=sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc \ + --hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \ + --hash=sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0 \ + --hash=sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761 \ + --hash=sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef \ + --hash=sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5 \ + --hash=sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e \ + --hash=sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b \ + --hash=sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d \ + --hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \ + --hash=sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c \ + --hash=sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41 \ + --hash=sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff \ + --hash=sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408 \ + --hash=sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2 \ + --hash=sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9 \ + --hash=sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57 \ + --hash=sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb \ + --hash=sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9 \ + --hash=sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3 \ + --hash=sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a \ + --hash=sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0 \ + --hash=sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e \ + --hash=sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598 \ + --hash=sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4 # via # astropy # pyerfa @@ -786,9 +788,9 @@ pydantic-core==2.23.4 \ # via # pydantic # safir -pydantic-settings==2.5.2 \ - --hash=sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907 \ - --hash=sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0 +pydantic-settings==2.6.1 \ + --hash=sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87 \ + --hash=sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0 # via # -r requirements/main.in # rubin-nublado-client @@ -897,110 +899,97 @@ requests==2.32.3 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 # via pyvo -rpds-py==0.20.1 \ - --hash=sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9 \ - --hash=sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28 \ - --hash=sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2 \ - --hash=sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5 \ - --hash=sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6 \ - --hash=sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6 \ - --hash=sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712 \ - --hash=sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0 \ - --hash=sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338 \ - --hash=sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86 \ - --hash=sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c \ - --hash=sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c \ - --hash=sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f \ - --hash=sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30 \ - --hash=sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f \ - --hash=sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd \ - --hash=sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e \ - --hash=sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963 \ - --hash=sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36 \ - --hash=sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2 \ - --hash=sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e \ - --hash=sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d \ - --hash=sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17 \ - --hash=sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb \ - --hash=sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1 \ - --hash=sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5 \ - --hash=sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163 \ - --hash=sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf \ - --hash=sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356 \ - --hash=sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804 \ - --hash=sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93 \ - --hash=sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a \ - --hash=sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1 \ - --hash=sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496 \ - --hash=sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1 \ - --hash=sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0 \ - --hash=sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899 \ - --hash=sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc \ - --hash=sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db \ - --hash=sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c \ - --hash=sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8 \ - --hash=sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684 \ - --hash=sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191 \ - --hash=sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06 \ - --hash=sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff \ - --hash=sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca \ - --hash=sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8 \ - --hash=sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc \ - --hash=sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a \ - --hash=sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7 \ - --hash=sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4 \ - --hash=sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751 \ - --hash=sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e \ - --hash=sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b \ - --hash=sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75 \ - --hash=sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e \ - --hash=sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74 \ - --hash=sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425 \ - --hash=sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84 \ - --hash=sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d \ - --hash=sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a \ - --hash=sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a \ - --hash=sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83 \ - --hash=sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535 \ - --hash=sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb \ - --hash=sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd \ - --hash=sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979 \ - --hash=sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d \ - --hash=sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d \ - --hash=sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c \ - --hash=sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782 \ - --hash=sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad \ - --hash=sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75 \ - --hash=sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4 \ - --hash=sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad \ - --hash=sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e \ - --hash=sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c \ - --hash=sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780 \ - --hash=sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01 \ - --hash=sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf \ - --hash=sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1 \ - --hash=sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab \ - --hash=sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732 \ - --hash=sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa \ - --hash=sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f \ - --hash=sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3 \ - --hash=sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711 \ - --hash=sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8 \ - --hash=sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a \ - --hash=sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d \ - --hash=sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c \ - --hash=sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519 \ - --hash=sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350 \ - --hash=sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f \ - --hash=sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e \ - --hash=sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb \ - --hash=sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc \ - --hash=sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f \ - --hash=sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977 \ - --hash=sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311 \ - --hash=sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d \ - --hash=sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad \ - --hash=sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982 +rpds-py==0.21.0 \ + --hash=sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba \ + --hash=sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d \ + --hash=sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e \ + --hash=sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a \ + --hash=sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202 \ + --hash=sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271 \ + --hash=sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250 \ + --hash=sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d \ + --hash=sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928 \ + --hash=sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0 \ + --hash=sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d \ + --hash=sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333 \ + --hash=sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e \ + --hash=sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a \ + --hash=sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18 \ + --hash=sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044 \ + --hash=sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677 \ + --hash=sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664 \ + --hash=sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75 \ + --hash=sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89 \ + --hash=sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027 \ + --hash=sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9 \ + --hash=sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e \ + --hash=sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8 \ + --hash=sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44 \ + --hash=sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3 \ + --hash=sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95 \ + --hash=sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd \ + --hash=sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab \ + --hash=sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a \ + --hash=sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560 \ + --hash=sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035 \ + --hash=sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919 \ + --hash=sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c \ + --hash=sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266 \ + --hash=sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e \ + --hash=sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592 \ + --hash=sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9 \ + --hash=sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3 \ + --hash=sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624 \ + --hash=sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9 \ + --hash=sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b \ + --hash=sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f \ + --hash=sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca \ + --hash=sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1 \ + --hash=sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8 \ + --hash=sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590 \ + --hash=sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed \ + --hash=sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952 \ + --hash=sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11 \ + --hash=sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061 \ + --hash=sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c \ + --hash=sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74 \ + --hash=sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c \ + --hash=sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94 \ + --hash=sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c \ + --hash=sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8 \ + --hash=sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf \ + --hash=sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a \ + --hash=sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5 \ + --hash=sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6 \ + --hash=sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5 \ + --hash=sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3 \ + --hash=sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed \ + --hash=sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87 \ + --hash=sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b \ + --hash=sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72 \ + --hash=sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05 \ + --hash=sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed \ + --hash=sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f \ + --hash=sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c \ + --hash=sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153 \ + --hash=sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b \ + --hash=sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0 \ + --hash=sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d \ + --hash=sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d \ + --hash=sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e \ + --hash=sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e \ + --hash=sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd \ + --hash=sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682 \ + --hash=sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4 \ + --hash=sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db \ + --hash=sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976 \ + --hash=sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937 \ + --hash=sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1 \ + --hash=sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb \ + --hash=sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a \ + --hash=sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7 \ + --hash=sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356 \ + --hash=sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be # via # jsonschema # referencing diff --git a/src/mobu/config.py b/src/mobu/config.py index 3cf09b4a..86cd50bc 100644 --- a/src/mobu/config.py +++ b/src/mobu/config.py @@ -3,63 +3,167 @@ from __future__ import annotations from pathlib import Path +from textwrap import dedent +from typing import Self -from pydantic import Field, HttpUrl -from pydantic_settings import BaseSettings +import yaml +from pydantic import AliasChoices, Field, HttpUrl +from pydantic.alias_generators import to_camel +from pydantic_settings import BaseSettings, SettingsConfigDict from safir.logging import LogLevel, Profile +from mobu.models.flock import FlockConfig + +from .models.user import User + __all__ = [ "Configuration", - "config", + "GitHubCiAppConfig", + "GitHubRefreshAppConfig", ] -class Configuration(BaseSettings): - """Configuration for mobu.""" +class GitHubCiAppConfig(BaseSettings): + """Configuration for GitHub CI app functionality if it is enabled.""" - alert_hook: HttpUrl | None = Field( - None, - title="Slack webhook URL used for sending alerts", + model_config = SettingsConfigDict( + alias_generator=to_camel, extra="forbid", populate_by_name=True + ) + + id: int = Field( + ..., + title="Github CI app id", description=( - "An https URL, which should be considered secret. If not set or" - " set to `None`, this feature will be disabled." + "Found on the GitHub app's settings page (NOT the installation" + " configuration page). For example:" + " https://github.com/organizations/lsst-sqre/settings/apps/mobu-ci-data-dev-lsst-cloud" ), - validation_alias="MOBU_ALERT_HOOK", - examples=["https://slack.example.com/ADFAW1452DAF41/"], + examples=[123456], + validation_alias=AliasChoices("MOBU_GITHUB_CI_APP_ID", "id"), ) - autostart: Path | None = Field( - None, - title="Path to YAML file defining flocks to automatically start", + private_key: str = Field( + ..., + title="Github CI app private key", description=( - "If given, the YAML file must contain a list of flock" - " specifications. All flocks given there will be automatically" - " started when mobu starts." + "Generated when the GitHub app was set up. This should NOT be" + " base64 enocded, and will contain newlines. You can find this" + " in 1Password; check the Phalanx mobu values for more details." + ), + examples=[ + dedent(""" + -----BEGIN RSA PRIVATE KEY----- + abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo + abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo + abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo + etc, etc + -----END RSA PRIVATE KEY----- + """) + ], + validation_alias=AliasChoices( + "MOBU_GITHUB_CI_APP_PRIVATE_KEY", "privateKey" ), - validation_alias="MOBU_AUTOSTART_PATH", - examples=["/etc/mobu/autostart.yaml"], ) - github_ci_app_config_path: Path | None = Field( - None, - title="GitHub CI app config path", + webhook_secret: str = Field( + ..., + title="Github CI app webhook secret", description=( - "Path to YAML file defining settings for GitHub CI app" - " integration" + "Generated when the GitHub app was set up. You can find this" + " in 1Password; check the Phalanx mobu values for more details." + ), + validation_alias=AliasChoices( + "MOBU_GITHUB_CI_APP_WEBHOOK_SECRET", "webhookSecret" ), - validation_alias="MOBU_GITHUB_CI_APP_CONFIG_PATH", - examples=["/etc/mobu/github-ci-app.yaml"], ) - github_refresh_app_config_path: Path | None = Field( + users: list[User] = Field( + ..., + title="Environment users for CI jobs to run as.", + description=( + "Must be prefixed with 'bot-', like all mobu users. In " + " environments without Firestore, users have to be provisioned" + " by environment admins, and their usernames, uids, and guids must" + " be specified here. In environments with firestore, only " + " usernames need to be specified, but you still need to explicitly" + " specify as many users as needed to get the amount of concurrency" + " that you want." + ), + ) + + scopes: list[str] = Field( + ..., + title="Gafaelfawr Scopes", + description=( + "A list of Gafaelfawr scopes that will be granted to the" + " user when running notebooks for a GitHub CI app check." + ), + ) + + accepted_github_orgs: list[str] = Field( + [], + title="Allowed GitHub organizations.", + description=( + "Any webhook payload request from a repo in an organization not in" + " this list will get a 403 response." + ), + ) + + +class GitHubRefreshAppConfig(BaseSettings): + """Configuration for GitHub refresh app functionality.""" + + model_config = SettingsConfigDict( + alias_generator=to_camel, extra="forbid", populate_by_name=True + ) + + webhook_secret: str = Field( + ..., + title="Github refresh app webhook secret", + description=( + "Generated when the GitHub app was set up. You can find this" + " in 1Password; check the Phalanx mobu values for more details." + ), + validation_alias=AliasChoices( + "MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET", "webhookSecret" + ), + ) + + accepted_github_orgs: list[str] = Field( + [], + title="Allowed GitHub organizations.", + description=( + "Any webhook payload request from a repo in an organization not in" + " this list will get a 403 response." + ), + ) + + +class Configuration(BaseSettings): + """Configuration for mobu.""" + + model_config = SettingsConfigDict( + alias_generator=to_camel, extra="forbid", populate_by_name=True + ) + + slack_alerts: bool = Field( + False, + title="Enable Slack alerts", + description=( + "Whether to enable Slack alerts. If true, ``alert_hook`` must" + " also be set." + ), + ) + + alert_hook: HttpUrl | None = Field( None, - title="GitHub refresh app config path", + title="Slack webhook URL used for sending alerts", description=( - "Path to YAML file defining settings for GitHub refresh app" - " integration" + "An https URL, which should be considered secret. If not set or" + " set to `None`, this feature will be disabled." ), - validation_alias="MOBU_GITHUB_REFRESH_APP_CONFIG_PATH", - examples=["/etc/mobu/github-refresh-app.yaml"], + examples=["https://slack.example.com/ADFAW1452DAF41/"], + validation_alias=AliasChoices("MOBU_ALERT_HOOK", "alertHook"), ) environment_url: HttpUrl | None = Field( @@ -71,8 +175,10 @@ class Configuration(BaseSettings): " suite easier. If it is not set to a valid URL, mobu will abort" " during startup." ), - validation_alias="MOBU_ENVIRONMENT_URL", examples=["https://data.example.org/"], + validation_alias=AliasChoices( + "MOBU_ENVIRONMENT_URL", "environmentUrl" + ), ) gafaelfawr_token: str | None = Field( @@ -83,8 +189,10 @@ class Configuration(BaseSettings): " get a token for the user. This is only optional to make writing" " tests easier. mobu will abort during startup if it is not set." ), - validation_alias="MOBU_GAFAELFAWR_TOKEN", examples=["gt-vilSCi1ifK_MyuaQgMD2dQ.d6SIJhowv5Hs3GvujOyUig"], + validation_alias=AliasChoices( + "MOBU_GAFAELFAWR_TOKEN", "gafaelfawrToken" + ), ) available_services: set[str] = Field( @@ -96,7 +204,6 @@ class Configuration(BaseSettings): " When we have a service discovery mechanism in place, it should" " be used here." ), - validation_alias="MOBU_AVAILABLE_SERVICES", examples=[{"tap", "ssotap", "butler"}], ) @@ -104,27 +211,57 @@ class Configuration(BaseSettings): "mobu", title="Name of application", description="Doubles as the root HTTP endpoint path.", - validation_alias="MOBU_NAME", + ) + + autostart: list[FlockConfig] = Field( + default=[], + title="Autostart config", + description=( + "Configuration of flocks of monkeys that will run businesses" + " repeatedly as long as Mobu is running." + ), ) path_prefix: str = Field( "/mobu", title="URL prefix for application API", - validation_alias="MOBU_PATH_PREFIX", ) profile: Profile = Field( Profile.development, title="Application logging profile", - validation_alias="MOBU_LOGGING_PROFILE", ) log_level: LogLevel = Field( LogLevel.INFO, title="Log level of the application's logger", - validation_alias="MOBU_LOG_LEVEL", ) + github_ci_app: GitHubCiAppConfig | None = Field( + None, + title="GitHub CI app config", + description=("Configuration for GitHub CI app functionality"), + ) + + github_refresh_app: GitHubRefreshAppConfig | None = Field( + None, + title="GitHub refresh app config", + description=("Configuration for GitHub refresh app functionality"), + ) + + @classmethod + def from_file(cls, path: Path) -> Self: + """Construct a Configuration object from a configuration file. + + Parameters + ---------- + path + Path to the configuration file in YAML. -config = Configuration() -"""Configuration for mobu.""" + Returns + ------- + Config + The corresponding `Configuration` object. + """ + with path.open("r") as f: + return cls.model_validate(yaml.safe_load(f)) diff --git a/src/mobu/constants.py b/src/mobu/constants.py index 61e0f712..cfc3c4db 100644 --- a/src/mobu/constants.py +++ b/src/mobu/constants.py @@ -6,6 +6,7 @@ from pathlib import Path __all__ = [ + "CONFIGURATION_PATH", "GITHUB_REPO_CONFIG_PATH", "GITHUB_WEBHOOK_WAIT_SECONDS", "NOTEBOOK_REPO_BRANCH", @@ -15,6 +16,8 @@ "WEBSOCKET_OPEN_TIMEOUT", ] +CONFIGURATION_PATH = Path("/etc/mobu/config.yaml") +"""Default path to configuration.""" GITHUB_REPO_CONFIG_PATH = Path("mobu.yaml") """The path to a config file with repo-specific configuration.""" diff --git a/src/mobu/dependencies/config.py b/src/mobu/dependencies/config.py new file mode 100644 index 00000000..484f8800 --- /dev/null +++ b/src/mobu/dependencies/config.py @@ -0,0 +1,65 @@ +"""Config dependency.""" + +import os +from pathlib import Path + +from ..config import Configuration +from ..constants import CONFIGURATION_PATH + +__all__ = [ + "ConfigDependency", + "config_dependency", +] + + +class ConfigDependency: + """Dependency to manage a cached Mobu configuration. + + The controller configuration is read on first request, cached, and + returned to all dependency callers unless `~ConfigDependency.set_path` is + called to change the configuration. + + Parameters + ---------- + path + Path to the Nublado mobu configuration. + """ + + def __init__(self, path: Path = CONFIGURATION_PATH) -> None: + # This is needed for running mobu locally, and in unit tests, to + # specify an alternate config file when mobu is started in a separate + # process from the tests. + if test_path := os.environ.get("MOBU_CONFIG_PATH"): + path = Path(test_path) + self._path = path + self._config: Configuration | None = None + + async def __call__(self) -> Configuration: + return self.config + + @property + def config(self) -> Configuration: + """Load configuration if needed and return it.""" + if self._config is None: + self._config = Configuration.from_file(self._path) + return self._config + + @property + def is_initialized(self) -> bool: + """Whether the configuration has been initialized.""" + return self._config is not None + + def set_path(self, path: Path) -> None: + """Change the configuration path and reload. + + Parameters + ---------- + path + New configuration path. + """ + self._path = path + self._config = Configuration.from_file(path) + + +config_dependency = ConfigDependency() +"""The dependency that will return the global configuration.""" diff --git a/src/mobu/dependencies/github.py b/src/mobu/dependencies/github.py index 0ef70d8d..7aa2e974 100644 --- a/src/mobu/dependencies/github.py +++ b/src/mobu/dependencies/github.py @@ -1,47 +1,10 @@ """Dependencies GitHub CI app functionality.""" -from pathlib import Path - -import yaml - -from ..github_config import GitHubCiAppConfig, GitHubRefreshAppConfig from ..models.user import User from ..services.github_ci.ci_manager import CiManager from .context import ContextDependency -class GitHubCiAppConfigDependency: - """Config for GitHub CI app integration, loaded from a file.""" - - def __init__(self) -> None: - self.config: GitHubCiAppConfig - - def __call__(self) -> GitHubCiAppConfig: - return self.config - - def initialize(self, path: Path) -> None: - self.config = GitHubCiAppConfig.model_validate( - yaml.safe_load(path.read_text()) - ) - - -class GitHubRefreshAppConfigDependency: - """Config for GitHub refresh app integration, loaded from a - file. - """ - - def __init__(self) -> None: - self.config: GitHubRefreshAppConfig - - def __call__(self) -> GitHubRefreshAppConfig: - return self.config - - def initialize(self, path: Path) -> None: - self.config = GitHubRefreshAppConfig.model_validate( - yaml.safe_load(path.read_text()) - ) - - class CiManagerDependency: """A process-global object to manage background CI workers. @@ -103,7 +66,5 @@ def __call__(self) -> CiManager | None: return None -github_refresh_app_config_dependency = GitHubRefreshAppConfigDependency() -github_ci_app_config_dependency = GitHubCiAppConfigDependency() ci_manager_dependency = CiManagerDependency() maybe_ci_manager_dependency = MaybeCiManagerDependency(ci_manager_dependency) diff --git a/src/mobu/factory.py b/src/mobu/factory.py index 58c92e8e..0c6e4d76 100644 --- a/src/mobu/factory.py +++ b/src/mobu/factory.py @@ -7,7 +7,7 @@ from safir.slack.webhook import SlackWebhookClient from structlog.stdlib import BoundLogger -from .config import config +from .dependencies.config import config_dependency from .models.solitary import SolitaryConfig from .services.manager import FlockManager from .services.solitary import Solitary @@ -68,6 +68,7 @@ def __init__( ) -> None: self._context = context self._logger = logger if logger else structlog.get_logger("mobu") + self._config = config_dependency.config def create_slack_webhook_client(self) -> SlackWebhookClient | None: """Create a Slack webhook client if configured for Slack alerting. @@ -78,9 +79,11 @@ def create_slack_webhook_client(self) -> SlackWebhookClient | None: Newly-created Slack client, or `None` if Slack alerting is not configured. """ - if not config.alert_hook: - return None - return SlackWebhookClient(str(config.alert_hook), "Mobu", self._logger) + if self._config.slack_alerts and self._config.alert_hook: + return SlackWebhookClient( + str(self._config.alert_hook), "Mobu", self._logger + ) + return None def create_solitary(self, solitary_config: SolitaryConfig) -> Solitary: """Create a runner for a solitary monkey. diff --git a/src/mobu/github_config.py b/src/mobu/github_config.py deleted file mode 100644 index 8bf4a074..00000000 --- a/src/mobu/github_config.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Config for GitHub application integrations.""" - -from textwrap import dedent - -from pydantic import Field -from pydantic.alias_generators import to_camel -from pydantic_settings import BaseSettings, SettingsConfigDict - -from .models.user import User - - -class GitHubCiAppConfig(BaseSettings): - """Configuration for GitHub CI app functionality if it is enabled.""" - - model_config = SettingsConfigDict( - alias_generator=to_camel, extra="forbid", populate_by_name=True - ) - - id: int = Field( - ..., - title="Github CI app id", - description=( - "Found on the GitHub app's settings page (NOT the installation" - " configuration page). For example:" - " https://github.com/organizations/lsst-sqre/settings/apps/mobu-ci-data-dev-lsst-cloud" - ), - validation_alias="MOBU_GITHUB_CI_APP_ID", - examples=[123456], - ) - - private_key: str = Field( - ..., - title="Github CI app private key", - description=( - "Generated when the GitHub app was set up. This should NOT be" - " base64 enocded, and will contain newlines. You can find this" - " in 1Password; check the Phalanx mobu values for more details." - ), - validation_alias="MOBU_GITHUB_CI_APP_PRIVATE_KEY", - examples=[ - dedent(""" - -----BEGIN RSA PRIVATE KEY----- - abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo - abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo - abc123MeowMeow456abc123MeowMeow456abc123MeowMeow456abc123MeowMeo - etc, etc - -----END RSA PRIVATE KEY----- - """) - ], - ) - - webhook_secret: str = Field( - ..., - title="Github CI app webhook secret", - description=( - "Generated when the GitHub app was set up. You can find this" - " in 1Password; check the Phalanx mobu values for more details." - ), - validation_alias="MOBU_GITHUB_CI_APP_WEBHOOK_SECRET", - ) - - users: list[User] = Field( - ..., - title="Environment users for CI jobs to run as.", - description=( - "Must be prefixed with 'bot-', like all mobu users. In " - " environments without Firestore, users have to be provisioned" - " by environment admins, and their usernames, uids, and guids must" - " be specified here. In environments with firestore, only " - " usernames need to be specified, but you still need to explicitly" - " specify as many users as needed to get the amount of concurrency" - " that you want." - ), - ) - - scopes: list[str] = Field( - ..., - title="Gafaelfawr Scopes", - description=( - "A list of Gafaelfawr scopes that will be granted to the" - " user when running notebooks for a GitHub CI app check." - ), - ) - - accepted_github_orgs: list[str] = Field( - [], - title="Allowed GitHub organizations.", - description=( - "Any webhook payload request from a repo in an organization not in" - " this list will get a 403 response." - ), - ) - - -class GitHubRefreshAppConfig(BaseSettings): - """Configuration for GitHub refresh app functionality.""" - - model_config = SettingsConfigDict( - alias_generator=to_camel, extra="forbid", populate_by_name=True - ) - - webhook_secret: str = Field( - ..., - title="Github refresh app webhook secret", - description=( - "Generated when the GitHub app was set up. You can find this" - " in 1Password; check the Phalanx mobu values for more details." - ), - validation_alias="MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET", - ) - - accepted_github_orgs: list[str] = Field( - [], - title="Allowed GitHub organizations.", - description=( - "Any webhook payload request from a repo in an organization not in" - " this list will get a 403 response." - ), - ) diff --git a/src/mobu/handlers/external.py b/src/mobu/handlers/external.py index c61933bc..e79a25a6 100644 --- a/src/mobu/handlers/external.py +++ b/src/mobu/handlers/external.py @@ -12,7 +12,9 @@ from safir.models import ErrorModel from safir.slack.webhook import SlackRouteErrorHandler -from ..config import config +from mobu.config import Configuration + +from ..dependencies.config import config_dependency from ..dependencies.context import RequestContext, context_dependency from ..dependencies.github import maybe_ci_manager_dependency from ..models.flock import FlockConfig, FlockData, FlockSummary @@ -49,7 +51,9 @@ def render(self, content: Any) -> bytes: response_model_exclude_none=True, summary="Application metadata", ) -async def get_index() -> Index: +async def get_index( + config: Annotated[Configuration, Depends(config_dependency)], +) -> Index: metadata = get_metadata( package_name="mobu", application_name=config.name, diff --git a/src/mobu/handlers/github_ci_app.py b/src/mobu/handlers/github_ci_app.py index b752fdb9..31c96b90 100644 --- a/src/mobu/handlers/github_ci_app.py +++ b/src/mobu/handlers/github_ci_app.py @@ -12,13 +12,11 @@ ) from safir.slack.webhook import SlackRouteErrorHandler +from ..config import Configuration from ..constants import GITHUB_WEBHOOK_WAIT_SECONDS +from ..dependencies.config import config_dependency from ..dependencies.context import RequestContext, anonymous_context_dependency -from ..dependencies.github import ( - ci_manager_dependency, - github_ci_app_config_dependency, -) -from ..github_config import GitHubCiAppConfig +from ..dependencies.github import ci_manager_dependency from ..services.github_ci.ci_manager import CiManager __all__ = ["api_router"] @@ -39,9 +37,7 @@ ) async def post_webhook( context: Annotated[RequestContext, Depends(anonymous_context_dependency)], - ci_app_config: Annotated[ - GitHubCiAppConfig, Depends(github_ci_app_config_dependency) - ], + config: Annotated[Configuration, Depends(config_dependency)], ci_manager: Annotated[CiManager, Depends(ci_manager_dependency)], ) -> None: """Process GitHub webhook events for the mobu CI GitHubApp. @@ -49,18 +45,20 @@ async def post_webhook( Rejects webhooks from organizations that are not explicitly allowed via the mobu config. This should be exposed via a Gafaelfawr anonymous ingress. """ - webhook_secret = ci_app_config.webhook_secret + if config.github_ci_app is None: + raise RuntimeError("GitHub CI app configuration is missing") + webhook_secret = config.github_ci_app.webhook_secret body = await context.request.body() event = Event.from_http( context.request.headers, body, secret=webhook_secret ) owner = event.data.get("organization", {}).get("login") - if owner not in ci_app_config.accepted_github_orgs: + if owner not in config.github_ci_app.accepted_github_orgs: context.logger.debug( "Ignoring GitHub event for unaccepted org", owner=owner, - accepted_orgs=ci_app_config.accepted_github_orgs, + accepted_orgs=config.github_ci_app.accepted_github_orgs, ) raise HTTPException( status_code=403, diff --git a/src/mobu/handlers/github_refresh_app.py b/src/mobu/handlers/github_refresh_app.py index 0aacedbc..05f24307 100644 --- a/src/mobu/handlers/github_refresh_app.py +++ b/src/mobu/handlers/github_refresh_app.py @@ -9,10 +9,10 @@ from safir.github.webhooks import GitHubPushEventModel from safir.slack.webhook import SlackRouteErrorHandler +from ..config import Configuration from ..constants import GITHUB_WEBHOOK_WAIT_SECONDS +from ..dependencies.config import config_dependency from ..dependencies.context import RequestContext, anonymous_context_dependency -from ..dependencies.github import github_refresh_app_config_dependency -from ..github_config import GitHubRefreshAppConfig __all__ = ["api_router"] @@ -32,28 +32,27 @@ ) async def post_webhook( context: Annotated[RequestContext, Depends(anonymous_context_dependency)], - refresh_app_config: Annotated[ - GitHubRefreshAppConfig, - Depends(github_refresh_app_config_dependency), - ], + config: Annotated[Configuration, Depends(config_dependency)], ) -> None: """Process GitHub webhook events for the mobu refresh GitHub app. Rejects webhooks from organizations that are not explicitly allowed via the mobu config. This should be exposed via a Gafaelfawr anonymous ingress. """ - webhook_secret = refresh_app_config.webhook_secret + if config.github_refresh_app is None: + raise RuntimeError("GitHub refresh app configuration is missing") + webhook_secret = config.github_refresh_app.webhook_secret body = await context.request.body() event = Event.from_http( context.request.headers, body, secret=webhook_secret ) owner = event.data.get("organization", {}).get("login") - if owner not in refresh_app_config.accepted_github_orgs: + if owner not in config.github_refresh_app.accepted_github_orgs: context.logger.debug( "Ignoring GitHub event for unaccepted org", owner=owner, - accepted_orgs=refresh_app_config.accepted_github_orgs, + accepted_orgs=config.github_refresh_app.accepted_github_orgs, ) raise HTTPException( status_code=403, diff --git a/src/mobu/handlers/internal.py b/src/mobu/handlers/internal.py index e9872c4a..425e1399 100644 --- a/src/mobu/handlers/internal.py +++ b/src/mobu/handlers/internal.py @@ -7,11 +7,14 @@ or other information that should not be visible outside the Kubernetes cluster. """ -from fastapi import APIRouter +from typing import Annotated + +from fastapi import APIRouter, Depends from safir.metadata import Metadata, get_metadata from safir.slack.webhook import SlackRouteErrorHandler -from ..config import config +from ..config import Configuration +from ..dependencies.config import config_dependency internal_router = APIRouter(route_class=SlackRouteErrorHandler) """FastAPI router for all internal handlers.""" @@ -30,7 +33,9 @@ response_model_exclude_none=True, summary="Application metadata", ) -async def get_index() -> Metadata: +async def get_index( + config: Annotated[Configuration, Depends(config_dependency)], +) -> Metadata: return get_metadata( package_name="mobu", application_name=config.name, diff --git a/src/mobu/main.py b/src/mobu/main.py index 63d0006e..26d8f25d 100644 --- a/src/mobu/main.py +++ b/src/mobu/main.py @@ -24,13 +24,9 @@ from safir.slack.webhook import SlackRouteErrorHandler from .asyncio import schedule_periodic -from .config import config +from .dependencies.config import config_dependency from .dependencies.context import context_dependency -from .dependencies.github import ( - ci_manager_dependency, - github_ci_app_config_dependency, - github_refresh_app_config_dependency, -) +from .dependencies.github import ci_manager_dependency from .handlers.external import external_router from .handlers.github_ci_app import api_router as github_ci_app_router from .handlers.github_refresh_app import ( @@ -39,12 +35,13 @@ from .handlers.internal import internal_router from .status import post_status -__all__ = ["app", "lifespan"] +__all__ = ["create_app", "lifespan"] @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: """Set up and tear down the the base application.""" + config = config_dependency.config if not config.environment_url: raise RuntimeError("MOBU_ENVIRONMENT_URL was not set") if not config.gafaelfawr_token: @@ -56,23 +53,13 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: status_interval = timedelta(days=1) app.state.periodic_status = schedule_periodic(post_status, status_interval) - if config.github_refresh_app_config_path: - github_refresh_app_config_dependency.initialize( - config.github_refresh_app_config_path - ) - - if config.github_ci_app_config_path: - github_ci_app_config_dependency.initialize( - config.github_ci_app_config_path - ) - ci_app_config = github_ci_app_config_dependency.config - + if config.github_ci_app: ci_manager_dependency.initialize( base_context=context_dependency, - github_app_id=ci_app_config.id, - github_private_key=ci_app_config.private_key, - scopes=ci_app_config.scopes, - users=ci_app_config.users, + github_app_id=config.github_ci_app.id, + github_private_key=config.github_ci_app.private_key, + scopes=config.github_ci_app.scopes, + users=config.github_ci_app.users, ) await ci_manager_dependency.ci_manager.start() @@ -83,53 +70,81 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app.state.periodic_status.cancel() -configure_logging( - name="mobu", profile=config.profile, log_level=config.log_level -) -if config.profile == Profile.production: - configure_uvicorn_logging(config.log_level) - -app = FastAPI( - title="mobu", - description=metadata("mobu")["Summary"], - version=version("mobu"), - openapi_url=f"{config.path_prefix}/openapi.json", - docs_url=f"{config.path_prefix}/docs", - redoc_url=f"{config.path_prefix}/redoc", - lifespan=lifespan, -) -"""The main FastAPI application for mobu.""" +def create_app(*, load_config: bool = True) -> FastAPI: + """Create the FastAPI application. + + This is in a function rather than using a global variable (as is more + typical for FastAPI) because some routing depends on configuration + settings and we therefore want to recreate the application between tests. + + Parameters + ---------- + load_config + If set to `False`, do not try to load the configuration. This is used + primarily for OpenAPI schema generation, where constructing the app is + required but the configuration won't matter. + """ + if load_config: + config = config_dependency.config + path_prefix = config.path_prefix + github_ci_app = config.github_ci_app + github_refresh_app = config.github_refresh_app + + configure_logging( + name="mobu", profile=config.profile, log_level=config.log_level + ) + if config.profile == Profile.production: + configure_uvicorn_logging(config.log_level) + + # Enable Slack alerting for uncaught exceptions. + if config.slack_alerts and config.alert_hook: + logger = structlog.get_logger("mobu") + SlackRouteErrorHandler.initialize( + str(config.alert_hook), "mobu", logger + ) + logger.debug("Initialized Slack webhook") + else: + path_prefix = "/mobu" + github_ci_app = None + github_refresh_app = None + + app = FastAPI( + title="mobu", + description=metadata("mobu")["Summary"], + version=version("mobu"), + openapi_url=f"{path_prefix}/openapi.json", + docs_url=f"{path_prefix}/docs", + redoc_url=f"{path_prefix}/redoc", + lifespan=lifespan, + ) -# Attach the routers. -app.include_router(internal_router) -app.include_router(external_router, prefix=config.path_prefix) + # Attach the routers. + app.include_router(internal_router) + app.include_router(external_router, prefix=path_prefix) -if config.github_ci_app_config_path: - app.include_router( - github_ci_app_router, prefix=f"{config.path_prefix}/github/ci" - ) + if github_ci_app: + app.include_router( + github_ci_app_router, prefix=f"{path_prefix}/github/ci" + ) -if config.github_refresh_app_config_path: - app.include_router( - github_refresh_app_router, - prefix=f"{config.path_prefix}/github/refresh", - ) + if github_refresh_app: + app.include_router( + github_refresh_app_router, + prefix=f"{config.path_prefix}/github/refresh", + ) -# Add middleware. -app.add_middleware(XForwardedMiddleware) + # Add middleware. + app.add_middleware(XForwardedMiddleware) -# Enable Slack alerting for uncaught exceptions. -if config.alert_hook: - logger = structlog.get_logger("mobu") - SlackRouteErrorHandler.initialize(str(config.alert_hook), "mobu", logger) - logger.debug("Initialized Slack webhook") + # Enable the generic exception handler for client errors. + app.exception_handler(ClientRequestError)(client_request_error_handler) -# Enable the generic exception handler for client errors. -app.exception_handler(ClientRequestError)(client_request_error_handler) + return app def create_openapi() -> str: """Create the OpenAPI spec for static documentation.""" + app = create_app(load_config=False) return json.dumps( get_openapi( title=app.title, diff --git a/src/mobu/services/business/notebookrunner.py b/src/mobu/services/business/notebookrunner.py index 593d2b22..be753a8c 100644 --- a/src/mobu/services/business/notebookrunner.py +++ b/src/mobu/services/business/notebookrunner.py @@ -21,8 +21,8 @@ from rubin.nublado.client.models import CodeContext from structlog.stdlib import BoundLogger -from ...config import config from ...constants import GITHUB_REPO_CONFIG_PATH +from ...dependencies.config import config_dependency from ...exceptions import NotebookRepositoryError, RepositoryConfigError from ...models.business.notebookrunner import ( ListNotebookRunnerOptions, @@ -62,6 +62,7 @@ def __init__( logger: BoundLogger, ) -> None: super().__init__(options, user, http_client, logger) + self._config = config_dependency.config self._notebook: Path | None = None self._notebook_paths: list[Path] | None = None self._repo_dir: Path | None = None @@ -155,7 +156,7 @@ def missing_services(self, notebook: Path) -> bool: """ metadata = self.read_notebook_metadata(notebook) missing_services = ( - metadata.required_services - config.available_services + metadata.required_services - self._config.available_services ) if missing_services: msg = "Environment does not provide required services for notebook" diff --git a/src/mobu/services/business/nublado.py b/src/mobu/services/business/nublado.py index bb2980f7..a3c403b2 100644 --- a/src/mobu/services/business/nublado.py +++ b/src/mobu/services/business/nublado.py @@ -17,7 +17,7 @@ from safir.slack.blockkit import SlackException from structlog.stdlib import BoundLogger -from ...config import config +from ...dependencies.config import config_dependency from ...exceptions import ( CodeExecutionError, JupyterProtocolError, @@ -111,6 +111,7 @@ def __init__( ) -> None: super().__init__(options, user, http_client, logger) + config = config_dependency.config if not config.environment_url: raise RuntimeError("environment_url not set") environment_url = str(config.environment_url).rstrip("/") diff --git a/src/mobu/services/business/tap.py b/src/mobu/services/business/tap.py index 5ec01ab7..99f69850 100644 --- a/src/mobu/services/business/tap.py +++ b/src/mobu/services/business/tap.py @@ -12,7 +12,7 @@ from httpx import AsyncClient from structlog.stdlib import BoundLogger -from ...config import config +from ...dependencies.config import config_dependency from ...exceptions import CodeExecutionError, TAPClientError from ...models.business.tap import TAPBusinessData, TAPBusinessOptions from ...models.user import AuthenticatedUser @@ -145,6 +145,7 @@ def _make_client(self, token: str) -> pyvo.dal.TAPService: pyvo.dal.TAPService TAP client object. """ + config = config_dependency.config if not config.environment_url: raise RuntimeError("environment_url not set") tap_url = str(config.environment_url).rstrip("/") + "/api/tap" diff --git a/src/mobu/services/github_ci/ci_manager.py b/src/mobu/services/github_ci/ci_manager.py index e30b7342..9e829310 100644 --- a/src/mobu/services/github_ci/ci_manager.py +++ b/src/mobu/services/github_ci/ci_manager.py @@ -12,7 +12,7 @@ from safir.github import GitHubAppClientFactory from structlog.stdlib import BoundLogger -from ...config import config +from ...dependencies.config import config_dependency from ...models.ci_manager import CiManagerSummary, CiWorkerSummary from ...models.user import User from ...storage.gafaelfawr import GafaelfawrStorage @@ -78,6 +78,7 @@ def __init__( gafaelfawr_storage: GafaelfawrStorage, logger: BoundLogger, ) -> None: + self._config = config_dependency.config self._scopes = scopes self._users = users self._gafaelfawr = gafaelfawr_storage @@ -242,7 +243,7 @@ async def enqueue( ) check_run = await storage.create_check_run( - name=f"Mobu ({config.environment_url})", + name=f"Mobu ({self._config.environment_url})", summary="Waiting for Mobu to run...", ) diff --git a/src/mobu/services/manager.py b/src/mobu/services/manager.py index 9231a02a..3aad00b9 100644 --- a/src/mobu/services/manager.py +++ b/src/mobu/services/manager.py @@ -4,12 +4,11 @@ import asyncio -import yaml from aiojobs import Scheduler from httpx import AsyncClient from structlog.stdlib import BoundLogger -from ..config import config +from ..dependencies.config import config_dependency from ..exceptions import FlockNotFoundError from ..models.flock import FlockConfig, FlockSummary from ..storage.gafaelfawr import GafaelfawrStorage @@ -41,6 +40,7 @@ def __init__( http_client: AsyncClient, logger: BoundLogger, ) -> None: + self._config = config_dependency.config self._gafaelfawr = gafaelfawr_storage self._http_client = http_client self._logger = logger @@ -59,14 +59,7 @@ async def autostart(self) -> None: This function should be called from the startup hook of the FastAPI application. """ - if not config.autostart: - return - with config.autostart.open("r") as f: - autostart = yaml.safe_load(f) - flock_configs = [ - FlockConfig.model_validate(flock) for flock in autostart - ] - for flock_config in flock_configs: + for flock_config in self._config.autostart: await self.start_flock(flock_config) async def start_flock(self, flock_config: FlockConfig) -> Flock: diff --git a/src/mobu/services/monkey.py b/src/mobu/services/monkey.py index 13eaea9f..2f6d7c49 100644 --- a/src/mobu/services/monkey.py +++ b/src/mobu/services/monkey.py @@ -15,7 +15,7 @@ from safir.slack.webhook import SlackWebhookClient from structlog.stdlib import BoundLogger -from ..config import config +from ..dependencies.config import config_dependency from ..exceptions import MobuMixin from ..models.business.base import BusinessConfig from ..models.business.empty import EmptyLoopConfig @@ -69,6 +69,7 @@ def __init__( http_client: AsyncClient, logger: BoundLogger, ) -> None: + self._config = config_dependency.config self._name = name self._flock = flock self._restart = business_config.restart @@ -117,9 +118,9 @@ def __init__( raise TypeError(msg) self._slack = None - if config.alert_hook: + if self._config.slack_alerts and self._config.alert_hook: self._slack = SlackWebhookClient( - str(config.alert_hook), "Mobu", self._global_logger + str(self._config.alert_hook), "Mobu", self._global_logger ) async def alert(self, exc: Exception) -> None: @@ -283,7 +284,7 @@ def _build_logger(self, logfile: _TemporaryFileWrapper) -> BoundLogger: logger.setLevel(self._log_level.value) logger.addHandler(file_handler) logger.propagate = False - if config.profile == Profile.development: + if self._config.profile == Profile.development: stream_handler = logging.StreamHandler(stream=sys.stdout) stream_handler.setFormatter(formatter) logger.addHandler(stream_handler) diff --git a/src/mobu/status.py b/src/mobu/status.py index ede95035..67a1fee2 100644 --- a/src/mobu/status.py +++ b/src/mobu/status.py @@ -4,7 +4,7 @@ from safir.slack.blockkit import SlackMessage -from .config import config +from .dependencies.config import config_dependency from .dependencies.context import context_dependency from .factory import Factory @@ -18,6 +18,7 @@ async def post_status() -> None: clear that mobu is alive, but a secondary benefit is to provide some summary statistics. """ + config = config_dependency.config process_context = context_dependency.process_context factory = Factory(process_context) slack = factory.create_slack_webhook_client() diff --git a/src/mobu/storage/gafaelfawr.py b/src/mobu/storage/gafaelfawr.py index 8729ab89..8d46fb27 100644 --- a/src/mobu/storage/gafaelfawr.py +++ b/src/mobu/storage/gafaelfawr.py @@ -11,8 +11,8 @@ from safir.datetime import current_datetime from structlog.stdlib import BoundLogger -from ..config import config from ..constants import TOKEN_LIFETIME, USERNAME_REGEX +from ..dependencies.config import config_dependency from ..exceptions import GafaelfawrParseError, GafaelfawrWebError from ..models.user import AuthenticatedUser, User @@ -82,10 +82,11 @@ class GafaelfawrStorage: def __init__(self, http_client: AsyncClient, logger: BoundLogger) -> None: self._client = http_client self._logger = logger + self._config = config_dependency.config - if not config.environment_url: + if not self._config.environment_url: raise RuntimeError("environment_url not set") - base_url = str(config.environment_url).rstrip("/") + base_url = str(self._config.environment_url).rstrip("/") self._token_url = base_url + "/auth/api/v1/tokens" async def create_service_token( @@ -131,7 +132,9 @@ async def create_service_token( # a better way to do this. r = await self._client.post( self._token_url, - headers={"Authorization": f"Bearer {config.gafaelfawr_token}"}, + headers={ + "Authorization": f"Bearer {self._config.gafaelfawr_token}" + }, json=json.loads(request.model_dump_json(exclude_none=True)), ) r.raise_for_status() diff --git a/tests/autostart_test.py b/tests/autostart_test.py index 8744053c..6bdb8ed0 100644 --- a/tests/autostart_test.py +++ b/tests/autostart_test.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Iterator -from pathlib import Path from unittest.mock import ANY import pytest @@ -11,51 +10,20 @@ from httpx import AsyncClient from rubin.nublado.client.testing import MockJupyter -from mobu.config import config +from mobu.dependencies.config import config_dependency +from .support.config import config_path from .support.gafaelfawr import mock_gafaelfawr from .support.util import wait_for_flock_start -AUTOSTART_CONFIG = """ -- name: basic - count: 10 - user_spec: - username_prefix: bot-mobu-testuser - uid_start: 1000 - gid_start: 2000 - scopes: ["exec:notebook"] - business: - type: EmptyLoop -- name: python - count: 2 - users: - - username: bot-mobu-python - uidnumber: 60000 - - username: bot-mobu-otherpython - uidnumber: 70000 - scopes: ["exec:notebook"] - restart: true - business: - type: NubladoPythonLoop - restart: True - options: - image: - image_class: latest-weekly - size: Large - spawn_settle_time: 0 -""" - @pytest.fixture(autouse=True) -def _configure_autostart( - tmp_path: Path, respx_mock: respx.Router -) -> Iterator[None]: +def _configure_autostart(respx_mock: respx.Router) -> Iterator[None]: """Set up the autostart configuration.""" + config_dependency.set_path(config_path("autostart")) mock_gafaelfawr(respx_mock, any_uid=True) - config.autostart = tmp_path / "autostart.yaml" - config.autostart.write_text(AUTOSTART_CONFIG) yield - config.autostart = None + config_dependency.set_path(config_path("base")) @pytest.mark.asyncio diff --git a/tests/business/nubladopythonloop_test.py b/tests/business/nubladopythonloop_test.py index e6c671f7..df6bd5ad 100644 --- a/tests/business/nubladopythonloop_test.py +++ b/tests/business/nubladopythonloop_test.py @@ -17,7 +17,7 @@ ) from safir.testing.slack import MockSlackWebhook -from mobu.config import config +from mobu.dependencies.config import config_dependency from ..support.gafaelfawr import mock_gafaelfawr from ..support.util import wait_for_business @@ -183,6 +183,7 @@ async def test_hub_failed( slack: MockSlackWebhook, respx_mock: respx.Router, ) -> None: + config = config_dependency.config mock_gafaelfawr(respx_mock) jupyter.fail("bot-mobu-testuser2", JupyterAction.SPAWN) @@ -268,6 +269,7 @@ async def test_redirect_loop( slack: MockSlackWebhook, respx_mock: respx.Router, ) -> None: + config = config_dependency.config mock_gafaelfawr(respx_mock) jupyter.redirect_loop = True diff --git a/tests/conftest.py b/tests/conftest.py index 86824b62..2bca6c1f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,10 +4,8 @@ from collections.abc import AsyncIterator, Iterator from contextlib import asynccontextmanager -from importlib import reload from pathlib import Path from tempfile import TemporaryDirectory -from textwrap import dedent from unittest.mock import DEFAULT, patch import pytest @@ -18,7 +16,6 @@ from asgi_lifespan import LifespanManager from fastapi import FastAPI from httpx import ASGITransport, AsyncClient -from pydantic import HttpUrl from rubin.nublado.client import NubladoClient from rubin.nublado.client.models import User from rubin.nublado.client.testing import ( @@ -31,10 +28,11 @@ from structlog.stdlib import BoundLogger from mobu import main -from mobu.config import config +from mobu.dependencies.config import config_dependency from mobu.services.business.gitlfs import GitLFSBusiness from mobu.services.business.nublado import _GET_IMAGE, _GET_NODE +from .support.config import config_path from .support.constants import ( TEST_BASE_URL, TEST_GITHUB_CI_APP_PRIVATE_KEY, @@ -72,7 +70,7 @@ def configured_logger() -> BoundLogger: @pytest.fixture(autouse=True) -def _configure(environment_url: str) -> Iterator[None]: +def _configure(environment_url: str, monkeypatch: pytest.MonkeyPatch) -> None: """Set minimal configuration settings. Add an environment URL for testing purposes and create a Gafaelfawr admin @@ -82,13 +80,8 @@ def _configure(environment_url: str) -> Iterator[None]: minimal test configuration and a unique admin token that is replaced after the test runs. """ - config.environment_url = HttpUrl(environment_url) - config.gafaelfawr_token = make_gafaelfawr_token() - config.available_services = {"some_service", "some_other_service"} - yield - config.environment_url = None - config.gafaelfawr_token = None - config.available_services = set() + monkeypatch.setenv("MOBU_GAFAELFAWR_TOKEN", make_gafaelfawr_token()) + config_dependency.set_path(config_path("base")) @pytest.fixture @@ -101,28 +94,7 @@ def test_filesystem() -> Iterator[Path]: def _enable_github_ci_app( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> Iterator[None]: - """Enable the GitHub CI app functionality. - - We need to reload the main module here because including the router is done - conditionally on module import. - """ - github_config = tmp_path / "github_ci_app_config.yaml" - github_config.write_text( - dedent(""" - users: - - username: bot-mobu-unittest-1 - - username: bot-mobu-unittest-2 - accepted_github_orgs: - - org1 - - org2 - - lsst-sqre - scopes: - - "exec:notebook" - - "exec:portal" - - "read:image" - - "read:tap" - """) - ) + """Enable the GitHub CI app functionality.""" monkeypatch.setenv("MOBU_GITHUB_CI_APP_ID", "1") monkeypatch.setenv( "MOBU_GITHUB_CI_APP_WEBHOOK_SECRET", TEST_GITHUB_CI_APP_SECRET @@ -130,47 +102,27 @@ def _enable_github_ci_app( monkeypatch.setenv( "MOBU_GITHUB_CI_APP_PRIVATE_KEY", TEST_GITHUB_CI_APP_PRIVATE_KEY ) - monkeypatch.setattr(config, "github_ci_app_config_path", github_config) - - reload(main) + config_dependency.set_path(config_path("github_ci_app")) yield - reload(main) + config_dependency.set_path(config_path("base")) @pytest.fixture def _enable_github_refresh_app( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> Iterator[None]: - """Enable the GitHub refresh app functionality. - - We need to reload the main module here because including the router is done - conditionally on module import. - """ - github_config = tmp_path / "github_ci_app_refresh.yaml" - github_config.write_text( - dedent(""" - accepted_github_orgs: - - org1 - - org2 - - lsst-sqre - """) - ) + """Enable the GitHub refresh app functionality.""" monkeypatch.setenv("MOBU_GITHUB_REFRESH_APP_ID", "1") monkeypatch.setenv( "MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET", TEST_GITHUB_REFRESH_APP_SECRET, ) - monkeypatch.setattr( - config, "github_refresh_app_config_path", github_config - ) - - reload(main) - + config_dependency.set_path(config_path("github_refresh_app")) yield - reload(main) + config_dependency.set_path(config_path("base")) @pytest_asyncio.fixture @@ -189,8 +141,9 @@ async def app(jupyter: MockJupyter) -> AsyncIterator[FastAPI]: A tests in :file:`business/jupyterloginloop_test.py` depends on the exact shutdown timeout. """ - async with LifespanManager(main.app, shutdown_timeout=10): - yield main.app + app = main.create_app() + async with LifespanManager(app, shutdown_timeout=10): + yield app @pytest.fixture @@ -279,10 +232,13 @@ async def mock_connect( @pytest.fixture -def slack(respx_mock: respx.Router) -> Iterator[MockSlackWebhook]: - config.alert_hook = HttpUrl("https://slack.example.com/XXXX") - yield mock_slack_webhook(str(config.alert_hook), respx_mock) - config.alert_hook = None +def slack( + respx_mock: respx.Router, monkeypatch: pytest.MonkeyPatch +) -> MockSlackWebhook: + alert_hook = "https://slack.example.com/XXXX" + monkeypatch.setenv("MOBU_ALERT_HOOK", alert_hook) + config_dependency.set_path(config_path("base")) + return mock_slack_webhook(alert_hook, respx_mock) @pytest.fixture diff --git a/tests/data/config/autostart.yaml b/tests/data/config/autostart.yaml new file mode 100644 index 00000000..d8ed8f6e --- /dev/null +++ b/tests/data/config/autostart.yaml @@ -0,0 +1,32 @@ +slackAlerts: true +environmentUrl: "https://example.com" +availableServices: + - some_service + - some_other_service +autostart: + - name: basic + count: 10 + user_spec: + username_prefix: bot-mobu-testuser + uid_start: 1000 + gid_start: 2000 + scopes: ["exec:notebook"] + business: + type: EmptyLoop + - name: python + count: 2 + users: + - username: bot-mobu-python + uidnumber: 60000 + - username: bot-mobu-otherpython + uidnumber: 70000 + scopes: ["exec:notebook"] + restart: true + business: + type: NubladoPythonLoop + restart: True + options: + image: + image_class: latest-weekly + size: Large + spawn_settle_time: 0 diff --git a/tests/data/config/base.yaml b/tests/data/config/base.yaml new file mode 100644 index 00000000..9f4a5101 --- /dev/null +++ b/tests/data/config/base.yaml @@ -0,0 +1,5 @@ +slackAlerts: true +environmentUrl: "https://example.com" +availableServices: + - some_service + - some_other_service diff --git a/tests/data/config/github_ci_app.yaml b/tests/data/config/github_ci_app.yaml new file mode 100644 index 00000000..33f86a86 --- /dev/null +++ b/tests/data/config/github_ci_app.yaml @@ -0,0 +1,18 @@ +slackAlerts: true +environmentUrl: "https://example.com" +availableServices: + - some_service + - some_other_service +githubCiApp: + users: + - username: bot-mobu-unittest-1 + - username: bot-mobu-unittest-2 + acceptedGithubOrgs: + - org1 + - org2 + - lsst-sqre + scopes: + - "exec:notebook" + - "exec:portal" + - "read:image" + - "read:tap" diff --git a/tests/data/config/github_refresh_app.yaml b/tests/data/config/github_refresh_app.yaml new file mode 100644 index 00000000..12dab819 --- /dev/null +++ b/tests/data/config/github_refresh_app.yaml @@ -0,0 +1,10 @@ +slackAlerts: true +environmentUrl: "https://example.com" +availableServices: + - some_service + - some_other_service +githubRefreshApp: + acceptedGithubOrgs: + - org1 + - org2 + - lsst-sqre diff --git a/tests/handlers/index_test.py b/tests/handlers/index_test.py index 99934ffb..57ffbe96 100644 --- a/tests/handlers/index_test.py +++ b/tests/handlers/index_test.py @@ -5,12 +5,13 @@ import pytest from httpx import AsyncClient -from mobu.config import config +from mobu.dependencies.config import config_dependency @pytest.mark.asyncio async def test_get_index(client: AsyncClient) -> None: """Test ``GET /mobu/``.""" + config = config_dependency.config response = await client.get("/mobu/") assert response.status_code == 200 data = response.json() diff --git a/tests/handlers/internal_test.py b/tests/handlers/internal_test.py index a6c19f21..08467414 100644 --- a/tests/handlers/internal_test.py +++ b/tests/handlers/internal_test.py @@ -5,12 +5,13 @@ import pytest from httpx import AsyncClient -from mobu.config import config +from mobu.dependencies.config import config_dependency @pytest.mark.asyncio async def test_get_index(client: AsyncClient) -> None: """Test ``GET /``.""" + config = config_dependency.config response = await client.get("/") assert response.status_code == 200 data = response.json() diff --git a/tests/monkeyflocker_test.py b/tests/monkeyflocker_test.py index 46ddf5b1..19c84523 100644 --- a/tests/monkeyflocker_test.py +++ b/tests/monkeyflocker_test.py @@ -14,10 +14,10 @@ from click.testing import CliRunner from safir.testing.uvicorn import UvicornProcess, spawn_uvicorn -from mobu.config import config +from mobu.dependencies.config import config_dependency from monkeyflocker.cli import main -from .support.gafaelfawr import make_gafaelfawr_token +from .support.config import config_path FLOCK_CONFIG = """ name: basic @@ -31,28 +31,27 @@ @pytest.fixture -def monkeyflocker_app( - tmp_path: Path, test_filesystem: Path, environment_url: str -) -> Iterator[UvicornProcess]: +def monkeyflocker_app(tmp_path: Path) -> Iterator[UvicornProcess]: """Run the application as a separate process for monkeyflocker access.""" + config = config_dependency.config + assert config.gafaelfawr_token assert config.environment_url - config.gafaelfawr_token = make_gafaelfawr_token() uvicorn = spawn_uvicorn( working_directory=tmp_path, factory="tests.support.monkeyflocker:create_app", env={ - "MOBU_ENVIRONMENT_URL": str(config.environment_url), "MOBU_GAFAELFAWR_TOKEN": config.gafaelfawr_token, + "MOBU_CONFIG_PATH": str(config_path("base")), }, ) yield uvicorn - config.gafaelfawr_token = None uvicorn.process.terminate() def test_start_report_refresh_stop( tmp_path: Path, monkeyflocker_app: UvicornProcess ) -> None: + config = config_dependency.config runner = CliRunner() spec_path = tmp_path / "spec.yaml" spec_path.write_text(FLOCK_CONFIG) diff --git a/tests/support/config.py b/tests/support/config.py new file mode 100644 index 00000000..5f69fc02 --- /dev/null +++ b/tests/support/config.py @@ -0,0 +1,27 @@ +"""Build test configuration for mobu.""" + +from __future__ import annotations + +from pathlib import Path + +__all__ = [ + "config_path", +] + + +def config_path(filename: str) -> Path: + """Return the path to a test configuration file. + + Parameters + ---------- + filename + The base name of a test configuration file or template. + + Returns + ------- + Path + The path to that file. + """ + return ( + Path(__file__).parent.parent / "data" / "config" / (filename + ".yaml") + ) diff --git a/tests/support/gafaelfawr.py b/tests/support/gafaelfawr.py index 05b7a71e..ec441706 100644 --- a/tests/support/gafaelfawr.py +++ b/tests/support/gafaelfawr.py @@ -12,7 +12,7 @@ from httpx import Request, Response from safir.datetime import current_datetime -from mobu.config import config +from mobu.dependencies.config import config_dependency __all__ = ["make_gafaelfawr_token", "mock_gafaelfawr"] @@ -47,6 +47,7 @@ def mock_gafaelfawr( Optionally verifies that the username and UID provided to Gafaelfawr are correct. """ + config = config_dependency.config scopes = scopes or ["exec:notebook"] admin_token = config.gafaelfawr_token assert admin_token diff --git a/tests/support/monkeyflocker.py b/tests/support/monkeyflocker.py index 753a1aeb..bf886f97 100644 --- a/tests/support/monkeyflocker.py +++ b/tests/support/monkeyflocker.py @@ -10,7 +10,7 @@ from rubin.nublado.client.testing import mock_jupyter from starlette.middleware.base import BaseHTTPMiddleware -from mobu.main import app +from mobu.main import create_app as main_create_app from .constants import TEST_BASE_URL from .gafaelfawr import mock_gafaelfawr @@ -45,5 +45,6 @@ def create_app() -> FastAPI: respx.start() mock_gafaelfawr(respx.mock) mock_jupyter(respx.mock, base_url=TEST_BASE_URL, user_dir=Path()) + app = main_create_app() app.add_middleware(AddAuthHeaderMiddleware) return app diff --git a/tox.ini b/tox.ini index a4a4e93c..d4db4ad0 100644 --- a/tox.ini +++ b/tox.ini @@ -45,4 +45,4 @@ commands = pre-commit run --all-files [testenv:run] description = Run the development server with auto-reload for code changes. usedevelop = true -commands = uvicorn mobu.main:app --reload +commands = uvicorn mobu.main:create_app --reload