Compare commits

..
52 Commits
Author SHA1 Message Date
mbecker20 c38849961e add Repos to search 2024-05-09 14:08:01 -07:00
mbecker20 3acfa0c4b1 more client docs 2024-05-09 03:25:37 -07:00
mbecker20 62b083c3be docsite 2024-05-09 03:12:53 -07:00
mbecker20 ee6fc4c590 include description / tags in diff 2024-05-09 01:53:29 -07:00
mbecker20 4fa550b3d3 monrun diffing 2024-05-09 01:33:03 -07:00
mbecker20 1c44ae98fb monrun / api better diffs 2024-05-09 00:34:21 -07:00
mbecker20 148223f995 config diff update log 2024-05-08 00:34:41 -07:00
mbecker20 4678f83542 fix clippy lints 2024-05-08 00:21:37 -07:00
mbecker20 340bac078f unneeded in cargo toml 2024-05-07 23:31:31 -07:00
mbecker20 3f8e75bbd8 update to partial derive 0.4.0 2024-05-07 23:29:57 -07:00
mbecker20 c4278d14a9 only show actions if more than 0 Actions on resource 2024-05-07 04:08:12 -07:00
mbecker20 4909106c3c turn off build icon spinning. still turns green 2024-05-07 03:45:00 -07:00
mbecker20 0f04a2848a clean up action states 2024-05-07 03:43:20 -07:00
mbecker20 ba073bf8b2 minimize update diffs 2024-05-07 03:40:30 -07:00
mbecker20 640809aa6b use trait for resource crud 2024-05-07 02:54:01 -07:00
mbecker20 943bb4c61a handle update config / description / tags seperately 2024-05-06 03:10:57 -07:00
mbecker20 ef43cb7920 clean up deployments toml 2024-05-06 02:39:04 -07:00
mbecker20 1abe634679 export individual resources 2024-05-06 02:24:35 -07:00
mbecker20 18ab18b6f6 fix for server template sync 2024-05-06 01:55:14 -07:00
mbecker20 aad971a599 sync working 2024-05-06 01:44:20 -07:00
mbecker20 3d9da97d7b monrun better check for empty 2024-05-06 01:36:22 -07:00
mbecker20 2d0a09f760 monrun 2024-05-06 01:12:14 -07:00
mbecker20 568c317d6d create repo does not clone 2024-05-06 00:47:27 -07:00
mbecker20 f0e1f253f4 add queue to listeners using locks 2024-05-06 00:33:19 -07:00
mbecker20 7634b9fa1d example 2024-05-06 00:21:03 -07:00
mbecker20 2e1795eba6 webhook 2024-05-06 00:16:22 -07:00
mbecker20 0f6b3d6e9b add github webhook copiers 2024-05-06 00:00:47 -07:00
mbecker20 a70afcc461 fix build client 2024-05-05 23:20:45 -07:00
mbecker20 d78fc2b282 improve onpull 2024-05-05 23:17:27 -07:00
mbecker20 249610afce frontend onpull 2024-05-05 22:55:37 -07:00
mbecker20 b93b639b40 add extra args style 2024-05-05 22:40:27 -07:00
mbecker20 13ceb55fe8 fix primary dropdown templates 2024-05-05 20:36:56 -07:00
mbecker20 7ebd38d350 slight style 2024-05-05 20:32:19 -07:00
mbecker20 c12c74005e finish configure server templates 2024-05-05 19:08:47 -07:00
mbecker20 c44dc1c6f6 clean server template schema 2024-05-05 18:39:10 -07:00
mbecker20 cd2b9ec4ed validate name not taken on LaunchServer 2024-05-05 16:35:55 -07:00
mbecker20 dc62442c00 LaunchServer revise 2024-05-05 14:16:19 -07:00
mbecker20 81a90f7ae4 launch server 2024-05-05 14:08:49 -07:00
mbecker20 af092e0d88 include server template in monrun export / import 2024-05-05 13:18:50 -07:00
mbecker20 0e70015fd1 add server template page 2024-05-05 13:02:25 -07:00
mbecker20 a214deef86 fix GetServerTemplatesSummary 2024-05-05 13:00:46 -07:00
mbecker20 0efb2966b0 set build version to 0.0.0 on Copy 2024-05-04 23:54:09 -07:00
mbecker20 1e8422a506 custom placeholder for AccountSelector 2024-05-04 23:10:46 -07:00
mbecker20 7bb386e6d0 Account Selector can set back to None 2024-05-04 18:57:42 -07:00
mbecker20 c464ca5612 resources clear their config after update completes 2024-05-04 18:11:23 -07:00
mbecker20 742630fdee create new resources navs to created resource 2024-05-04 17:54:21 -07:00
mbecker20 154fd899fe fix all the links 2024-05-04 17:49:06 -07:00
mbecker20 aa25f9d4c9 servers init disabled 2024-05-04 17:33:05 -07:00
mbecker20 eeed94c8fd === PERIPHERY INSTALLER === 2024-05-04 16:45:39 -07:00
mbecker20 b2f771199d default repos path 2024-05-04 16:39:20 -07:00
mbecker20 4bceb97a66 comment out fields that are just being set to the defautl 2024-05-04 16:25:55 -07:00
mbecker20 8734e6fc4c clean up periphery example config 2024-05-04 16:22:01 -07:00
152 changed files with 5263 additions and 4364 deletions
Generated
+92 -81
View File
@@ -32,7 +32,7 @@ dependencies = [
[[package]]
name = "alert_logger"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"axum 0.7.5",
@@ -111,15 +111,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
[[package]]
name = "async-recursion"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
@@ -180,9 +180,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2a89e0000cde82447155d64eeb71720b933b4396a6fbbebad3f8b4f88ca7b54"
checksum = "baaa0be6ee7d90b775ae6ccb6d2ba182b91219ec2001f92338773a094246af1d"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -223,9 +223,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4963ac9ff2d33a4231b3806c1c69f578f221a9cabb89ad2bde62ce2b442c8a7"
checksum = "785da4a15e7b166b505fd577e4560c7a7cd8fbdf842eb1336cbcbf8944ce56f1"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -246,9 +246,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.34.0"
version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b58453a3033a5c2e1631abec61b6915c507a726d628a0c2d7b81578d81be1b3"
checksum = "a449fb7b9cab253e5ad0e6173188b2c5103e62183b1c099ec610fafb498dbe7c"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -270,9 +270,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.20.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fcc572fd5c58489ec205ec3e4e5f7d63018898a485cbf922a462af496bc300"
checksum = "ca3d6c4cba4e009391b72b0fcf12aff04ea3c9c3aa2ecaafa330326a8bd7e601"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -292,9 +292,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.20.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b6275fa8684a1192754221173b1f7a7c1260d6b0571cc2b8af09468eb0cffe5"
checksum = "73400dc239d14f63d932f4ca7b55af5e9ef1f857f7d70655249ccc287adb2570"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -314,9 +314,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.20.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30acd58272fd567e4853c5075d838be1626b59057e0249c9be5a1a7eb13bf70f"
checksum = "10f8858308af76fba3e5ffcf1bb56af5471574d2bdfaf0159470c25bc2f760e5"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -337,9 +337,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d6f29688a4be9895c0ba8bef861ad0c0dac5c15e9618b9b7a6c233990fc263"
checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -371,9 +371,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
version = "0.60.7"
version = "0.60.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f10fa66956f01540051b0aa7ad54574640f748f9839e843442d99b970d3aff9"
checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.3.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de34bcfa1fb3c82a80e252a753db34a6658e07f23d3a5b3fc96919518fa7a3f5"
checksum = "c9ac79e9f3a4d576f3cd4a470a0275b138d9e7b11b1cd514a6858ae0a79dd5bb"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -436,9 +436,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.4.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cc56a5c96ec741de6c5e6bf1ce6948be969d6506dfa9c39cffc284e31e4979b"
checksum = "04ec42c2f5c0e7796a2848dde4d9f3bf8ce12ccbb3d5aa40c52fa0cdd61a1c47"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -453,9 +453,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.1.8"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe14dceea1e70101d38fbf2a99e6a34159477c0fb95e68e05c66bd7ae4c3729"
checksum = "baf98d97bba6ddaba180f1b1147e202d8fe04940403a95a3f826c790f931bbd1"
dependencies = [
"base64-simd",
"bytes",
@@ -479,9 +479,9 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
version = "0.60.7"
version = "0.60.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872c68cf019c0e4afc5de7753c4f7288ce4b71663212771bf5e4542eb9346ca9"
checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55"
dependencies = [
"xmlparser",
]
@@ -920,6 +920,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
]
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -2007,7 +2017,7 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "logger"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"monitor_client",
@@ -2084,7 +2094,7 @@ dependencies = [
[[package]]
name = "migrator"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"chrono",
@@ -2210,9 +2220,27 @@ dependencies = [
"zstd",
]
[[package]]
name = "monitor_cli"
version = "1.1.0"
dependencies = [
"anyhow",
"clap",
"colored",
"futures",
"monitor_client",
"partial_derive2",
"serde",
"strum 0.26.2",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "monitor_client"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2227,7 +2255,7 @@ dependencies = [
"mongo_indexed",
"mungos",
"partial_derive2",
"reqwest 0.12.3",
"reqwest 0.12.4",
"resolver_api",
"serde",
"serde_json",
@@ -2244,7 +2272,7 @@ dependencies = [
[[package]]
name = "monitor_core"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -2267,9 +2295,10 @@ dependencies = [
"monitor_client",
"mungos",
"parse_csl",
"partial_derive2",
"periphery_client",
"rand",
"reqwest 0.12.3",
"reqwest 0.12.4",
"resolver_api",
"serde",
"serde_json",
@@ -2290,7 +2319,7 @@ dependencies = [
[[package]]
name = "monitor_periphery"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"async-trait",
@@ -2321,7 +2350,7 @@ dependencies = [
[[package]]
name = "monitor_sync"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"clap",
@@ -2332,23 +2361,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "monrun"
version = "1.0.0"
dependencies = [
"anyhow",
"clap",
"futures",
"monitor_client",
"partial_derive2",
"serde",
"strum 0.26.2",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "mungos"
version = "0.5.4"
@@ -2614,18 +2626,18 @@ checksum = "ffa94c2e5674923c67d7f3dfce1279507b191e10eb064881b46ed3e1256e5ca6"
[[package]]
name = "partial_derive2"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bf4a15b30c642aa810f6a33cc8c0dfddd2fa46918ca068fde958882d6b18b"
checksum = "57fd88f09814d56f73ea68256ebdd2cdd75a4a3b48d41c63bbc27c46c6733588"
dependencies = [
"partial_derive2_derive",
]
[[package]]
name = "partial_derive2_derive"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e49f7410574cdfb56362c529cb2893a246e147072d5e993c46341f02891f9cd1"
checksum = "bdc7cb36e2cd4e6e7a53e3d35fd3321e1bd83382e0207901bff7ffb779cf7619"
dependencies = [
"proc-macro2",
"quote",
@@ -2649,11 +2661,11 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "periphery_client"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"monitor_client",
"reqwest 0.12.3",
"reqwest 0.12.4",
"resolver_api",
"serde",
"serde_json",
@@ -2865,9 +2877,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
dependencies = [
"base64 0.22.0",
"bytes",
@@ -3168,9 +3180,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
@@ -3186,9 +3198,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
@@ -3573,9 +3585,9 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "sysinfo"
version = "0.30.11"
version = "0.30.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87341a165d73787554941cd5ef55ad728011566fe714e987d1b976c15dbc3a83"
checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae"
dependencies = [
"cfg-if",
"core-foundation-sys",
@@ -3648,7 +3660,7 @@ dependencies = [
[[package]]
name = "tests"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"dotenv",
@@ -3666,18 +3678,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.58"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
@@ -3832,9 +3844,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.10"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [
"bytes",
"futures-core",
@@ -3842,7 +3854,6 @@ dependencies = [
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
@@ -4141,9 +4152,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typeshare"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99877a66770b25072a67101e80790fd7fe9ed9eff8c69ffb81e9bf5cbea7733f"
checksum = "04f17399b76c2e743d58eac0635d7686e9c00f48cd4776f00695d9882a7d3187"
dependencies = [
"chrono",
"serde",
@@ -4153,9 +4164,9 @@ dependencies = [
[[package]]
name = "typeshare-annotation"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecce25dea8aeaadc44909f4c1226d22d84512fccd07d22447ecbad176bc09545"
checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f"
dependencies = [
"quote",
"syn 2.0.53",
@@ -4205,7 +4216,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "update_logger"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"logger",
+13 -13
View File
@@ -3,14 +3,13 @@ resolver = "2"
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
[workspace.package]
version = "1.0.0"
version = "1.1.0"
edition = "2021"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
[workspace.dependencies]
# LOCAL
monitor_macros = { path = "lib/macros" }
monitor_client = { path = "client/core/rs" }
periphery_client = { path = "client/periphery/rs" }
logger = { path = "lib/logger" }
@@ -24,7 +23,7 @@ derive_empty_traits = "0.1.0"
merge_config_files = "0.1.5"
termination_signal = "0.1.3"
async_timing_util = "0.1.14"
partial_derive2 = "0.3.2"
partial_derive2 = "0.4.1"
derive_variants = "0.1.3"
mongo_indexed = "0.2.2"
resolver_api = "0.1.9"
@@ -34,12 +33,12 @@ svi = "0.1.4"
# ASYNC
tokio = { version = "1.37.0", features = ["full"] }
reqwest = { version = "0.12.3", features = ["json"] }
tokio-util = "0.7.10"
reqwest = { version = "0.12.4", features = ["json"] }
tokio-util = "0.7.11"
futures = "0.3.30"
futures-util = "0.3.30"
async-trait = "0.1.80"
async-recursion = "1.1.0"
async-recursion = "1.1.1"
# SERVER
axum = { version = "0.7.5", features = ["ws", "json"] }
@@ -49,14 +48,14 @@ tower-http = { version = "0.5.2", features = ["fs", "cors"] }
tokio-tungstenite = "0.21.0"
# SER/DE
serde = { version = "1.0.198", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
strum = { version = "0.26.2", features = ["derive"] }
serde_json = "1.0.116"
toml = "0.8.12"
# ERROR
anyhow = "1.0.82"
thiserror = "1.0.58"
anyhow = "1.0.83"
thiserror = "1.0.60"
# LOGGING
opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio"] }
@@ -83,12 +82,13 @@ hex = "0.4.3"
# SYSTEM
bollard = "0.16.1"
sysinfo = "0.30.11"
sysinfo = "0.30.12"
# CLOUD
aws-config = "1.2.0"
aws-sdk-ec2 = "1.34.0"
aws-config = "1.3.0"
aws-sdk-ec2 = "1.40.0"
# MISC
derive_builder = "0.20.0"
typeshare = "1.0.2"
colored = "2.1.0"
typeshare = "1.0.3"
+7 -1
View File
@@ -1,10 +1,15 @@
[package]
name = "monrun"
name = "monitor_cli"
description = "Command line tool to sync monitor resources and execute file defined procedures"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
[[bin]]
name = "monitor"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@@ -13,6 +18,7 @@ monitor_client.workspace = true
# mogh
partial_derive2.workspace = true
# external
colored.workspace = true
anyhow.workspace = true
tokio.workspace = true
serde.workspace = true
+21
View File
@@ -0,0 +1,21 @@
# monitor CLI
Monitor CLI is a command line tool to sync monitor resources and execute file defined procedures.
## Examples
```sh
## Sync resources in a single file
monitor sync ./resources/deployments.toml
## Sync resources gathered across multiple files in a directory
monitor sync ./resources
## Path defaults to './resources', in this case you can just use:
monitor sync
```
```sh
## Execute a TOML defined procedure
monitor exec ./execution/execution.toml
```
+49
View File
@@ -0,0 +1,49 @@
[[build]]
name = "monitor_core"
description = "Public monitor core build"
tags = ["monitor"]
[build.config]
builder_id = "mogh-builder"
repo = "mbecker20/monitor"
branch = "main"
docker_account = "mbecker2020"
build_path = "."
dockerfile_path = "bin/core/Dockerfile"
[[build]]
name = "monitor_core_dev"
description = ""
tags = ["monitor", "dev"]
[build.config]
builder_id = "mogh-builder"
repo = "mbecker20/monitor"
branch = "main"
docker_account = "mbecker2020"
build_path = "."
dockerfile_path = "bin/core/Dockerfile"
## BUILDER
[[builder]]
name = "mogh-builder"
description = ""
tags = []
[builder.config]
type = "Aws"
[builder.config.params]
region = "us-east-2"
instance_type = "c5.2xlarge"
volume_gb = 20
port = 8120
ami_id = "ami-0005a05fa63a080ab"
subnet_id = "subnet-02ae5ad480eacc4bc"
security_group_ids = ["sg-049d98c819f9ace58", "sg-006c0ca638af8eb44"]
key_pair_name = "mogh-key"
assign_public_ip = true
use_public_ip = false
github_accounts = []
docker_accounts = []
+181
View File
@@ -0,0 +1,181 @@
## MONITOR PROXY
[[deployment]]
name = "monitor-proxy"
description = "An NGINX proxy for mogh.tech"
tags = ["monitor"]
config.server_id = "monitor-01"
config.network = "host"
config.restart = "on-failure"
config.image.type = "Image"
config.image.params.image = "jc21/nginx-proxy-manager"
[[deployment.config.volumes]]
local = "/data/nginx/data"
container = "/data"
[[deployment.config.volumes]]
local = "/data/nginx/letsencrypt"
container = "/etc/letsencrypt"
## MONITOR MONGO
[[deployment]]
name = "monitor-mongo"
description = ""
tags = ["monitor"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "no"
[deployment.config.image]
type = "Image"
params.image = "mongo"
## MONITOR CORE
[[deployment]]
name = "monitor-core"
description = ""
tags = ["monitor"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "no"
[deployment.config.image]
type = "Image"
params.image = "mbecker2020/monitor_core"
## GRAFANA
[[deployment]]
name = "grafana"
description = ""
tags = ["logging"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "unless-stopped"
extra_args = ["--user root"]
[deployment.config.image]
type = "Image"
params.image = "grafana/grafana"
[[deployment.config.volumes]]
local = "/data/grafana"
container = "/var/lib/grafana"
[[deployment.config.environment]]
variable = "GF_SERVER_HTTP_PORT"
value = "3080"
[[deployment.config.labels]]
variable = "vector"
value = "key-value"
## LOKI
[[deployment]]
name = "loki"
description = ""
tags = ["logging"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "unless-stopped"
extra_args = ["--user root"]
[deployment.config.image]
type = "Image"
params.image = "grafana/loki"
[[deployment.config.volumes]]
local = "/data/loki"
container = "/loki"
[[deployment]]
name = "tempo"
description = ""
tags = ["logging"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "unless-stopped"
command = "-server.http-listen-port=3200 -server.grpc-listen-port=9096 --storage.trace.backend=local --storage.trace.local.path=/tmp/tempo/traces --storage.trace.wal.path=/tmp/tempo/wal"
extra_args = ["--user root"]
[deployment.config.image]
type = "Image"
params.image = "grafana/tempo"
[[deployment.config.volumes]]
local = "/data/tempo"
container = "/tmp/tempo"
[[deployment.config.labels]]
variable = "vector"
value = "key-value"
## VECTOR
[[deployment]]
name = "vector"
description = ""
tags = ["logging"]
[deployment.config]
server_id = "monitor-01"
network = "host"
restart = "unless-stopped"
command = "--config /etc/vector/*.toml"
extra_args = ["--user root"]
[deployment.config.image]
type = "Image"
params.image = "timberio/vector:latest-debian"
[[deployment.config.volumes]]
local = "/home/ubuntu/.config/vector"
container = "/etc/vector"
[[deployment.config.volumes]]
local = "/data/vector"
container = "/var/lib/vector"
[[deployment.config.volumes]]
local = "/var/run/docker.sock"
container = "/var/run/docker.sock"
[[deployment.config.labels]]
variable = "vector"
value = "key-value"
## MONITOR CORE DEV
[[deployment]]
name = "monitor-core-dev"
description = ""
tags = ["monitor", "dev"]
[deployment.config]
server_id = "monitor-01"
redeploy_on_build = true
network = "host"
restart = "no"
[deployment.config.image]
type = "Build"
params.build_id = "monitor_core"
[[deployment.config.volumes]]
local = "/home/ubuntu/.config/monitor/dev.core.config.toml"
container = "/config/config.toml"
[[deployment.config.volumes]]
local = "/etc/monitor/repos/monitor-dev-frontend/frontend/dist"
container = "/frontend"
[[deployment.config.labels]]
variable = "vector"
value = "rust"
+8
View File
@@ -0,0 +1,8 @@
[[procedure]]
name = "test-procedure"
description = ""
tags = []
[procedure.config]
procedure_type = "Sequence"
executions = []
+18
View File
@@ -0,0 +1,18 @@
[[repo]]
name = "monitor-dev-frontend"
description = "Used as frontend for monitor-core-dev"
tags = ["monitor", "dev"]
[repo.config]
server_id = "monitor-01"
repo = "mbecker20/monitor"
branch = "main"
github_account = ""
[repo.config.on_clone]
path = ""
command = ""
[repo.config.on_pull]
path = "frontend"
command = "sh on_pull.sh"
+51
View File
@@ -0,0 +1,51 @@
[[server]]
name = "monitor-01"
description = ""
tags = ["monitor"]
[server.config]
address = "http://localhost:8120"
enabled = true
stats_monitoring = true
auto_prune = true
send_unreachable_alerts = true
send_cpu_alerts = true
send_mem_alerts = true
send_disk_alerts = true
region = "us-east-2"
## TEMPLATE
[[server_template]]
name = "mogh-template"
description = ""
tags = []
[server_template.config]
type = "Aws"
[server_template.config.params]
region = "us-east-2"
instance_type = "t3.medium"
ami_id = "ami-0005a05fa63a080ab"
subnet_id = "subnet-02ae5ad480eacc4bc"
key_pair_name = "mogh-key"
assign_public_ip = true
use_public_ip = false
port = 8120
user_data = ""
security_group_ids = ["sg-049d98c819f9ace58", "sg-006c0ca638af8eb44"]
[[server_template.config.params.volumes]]
device_name = "/dev/sda1"
size_gb = 20
volume_type = "gp2"
iops = 0
throughput = 0
[[server_template.config.params.volumes]]
device_name = "/dev/sdb"
size_gb = 10
volume_type = "gp3"
iops = 0
throughput = 0
@@ -5,6 +5,7 @@ use std::{io::Read, path::PathBuf, str::FromStr, sync::OnceLock};
use anyhow::Context;
use clap::{Parser, Subcommand};
use colored::Colorize;
use monitor_client::{api::read, MonitorClient};
use serde::{de::DeserializeOwned, Deserialize};
@@ -20,12 +21,15 @@ fn cli_args() -> &'static CliArgs {
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct CliArgs {
/// Sync or Exec
#[command(subcommand)]
command: Command,
/// The path to a creds file.
#[arg(long, default_value_t = String::from("./creds.toml"))]
creds: String,
/// Log less (just resource names).
#[arg(long, default_value_t = false)]
verbose: bool,
quiet: bool,
}
#[derive(Debug, Clone, Subcommand)]
@@ -89,8 +93,8 @@ fn parse_toml_file<T: DeserializeOwned>(
toml::from_str(&contents).context("failed to parse toml contents")
}
fn wait_for_enter(message: &str) -> anyhow::Result<()> {
println!("\nPress ENTER to {message}\n");
fn wait_for_enter(press_enter_to: &str) -> anyhow::Result<()> {
println!("\nPress {} to {}\n", "ENTER".green(), press_enter_to.bold());
let buffer = &mut [0u8];
std::io::stdin()
.read_exact(buffer)
+33 -4
View File
@@ -7,7 +7,7 @@ use monitor_client::{
builder::BuilderListItem, deployment::DeploymentListItem,
procedure::ProcedureListItem, repo::RepoListItem,
server::ServerListItem, server_template::ServerTemplateListItem,
user::User, user_group::UserGroup,
tag::Tag, user::User, user_group::UserGroup,
},
};
@@ -219,6 +219,22 @@ pub fn id_to_procedure() -> &'static HashMap<String, ProcedureListItem>
})
}
pub fn name_to_server_template(
) -> &'static HashMap<String, ServerTemplateListItem> {
static NAME_TO_SERVER_TEMPLATE: OnceLock<
HashMap<String, ServerTemplateListItem>,
> = OnceLock::new();
NAME_TO_SERVER_TEMPLATE.get_or_init(|| {
futures::executor::block_on(
monitor_client().read(read::ListServerTemplates::default()),
)
.expect("failed to get server templates from monitor")
.into_iter()
.map(|procedure| (procedure.name.clone(), procedure))
.collect()
})
}
pub fn id_to_server_template(
) -> &'static HashMap<String, ServerTemplateListItem> {
static ID_TO_SERVER_TEMPLATE: OnceLock<
@@ -228,7 +244,7 @@ pub fn id_to_server_template(
futures::executor::block_on(
monitor_client().read(read::ListServerTemplates::default()),
)
.expect("failed to get procedures from monitor")
.expect("failed to get server templates from monitor")
.into_iter()
.map(|procedure| (procedure.id.clone(), procedure))
.collect()
@@ -242,7 +258,7 @@ pub fn name_to_user_group() -> &'static HashMap<String, UserGroup> {
futures::executor::block_on(
monitor_client().read(read::ListUserGroups::default()),
)
.expect("failed to get procedures from monitor")
.expect("failed to get user groups from monitor")
.into_iter()
.map(|user_group| (user_group.name.clone(), user_group))
.collect()
@@ -256,9 +272,22 @@ pub fn id_to_user() -> &'static HashMap<String, User> {
futures::executor::block_on(
monitor_client().read(read::ListUsers::default()),
)
.expect("failed to get procedures from monitor")
.expect("failed to get users from monitor")
.into_iter()
.map(|user| (user.id.clone(), user))
.collect()
})
}
pub fn id_to_tag() -> &'static HashMap<String, Tag> {
static ID_TO_TAG: OnceLock<HashMap<String, Tag>> = OnceLock::new();
ID_TO_TAG.get_or_init(|| {
futures::executor::block_on(
monitor_client().read(read::ListTags::default()),
)
.expect("failed to get tags from monitor")
.into_iter()
.map(|tag| (tag.id.clone(), tag))
.collect()
})
}
@@ -3,7 +3,7 @@ use std::path::Path;
use monitor_client::entities::{
alerter::Alerter, build::Build, builder::Builder,
deployment::Deployment, procedure::Procedure, repo::Repo,
server::Server,
server::Server, server_template::ServerTemplate,
};
use crate::{sync::resources::ResourceSync, wait_for_enter};
@@ -17,8 +17,10 @@ pub async fn run_sync(path: &Path) -> anyhow::Result<()> {
let resources = resource_file::read_resources(path)?;
println!("{resources:#?}");
info!("computing sync actions...");
let (server_template_creates, server_template_updates) =
ServerTemplate::get_updates(resources.server_templates).await?;
let (server_creates, server_updates) =
Server::get_updates(resources.servers).await?;
let (deployment_creates, deployment_updates) =
@@ -36,9 +38,37 @@ pub async fn run_sync(path: &Path) -> anyhow::Result<()> {
let (user_group_creates, user_group_updates) =
user_group::get_updates(resources.user_groups).await?;
wait_for_enter("CONTINUE")?;
if server_template_creates.is_empty()
&& server_template_updates.is_empty()
&& server_creates.is_empty()
&& server_updates.is_empty()
&& deployment_creates.is_empty()
&& deployment_updates.is_empty()
&& build_creates.is_empty()
&& build_updates.is_empty()
&& builder_creates.is_empty()
&& builder_updates.is_empty()
&& alerter_creates.is_empty()
&& alerter_updates.is_empty()
&& repo_creates.is_empty()
&& repo_updates.is_empty()
&& procedure_creates.is_empty()
&& procedure_updates.is_empty()
&& user_group_creates.is_empty()
&& user_group_updates.is_empty()
{
info!("nothing to do. exiting.");
return Ok(());
}
wait_for_enter("run sync")?;
// No deps
ServerTemplate::run_updates(
server_template_creates,
server_template_updates,
)
.await;
Server::run_updates(server_creates, server_updates).await;
Alerter::run_updates(alerter_creates, alerter_updates).await;
@@ -31,6 +31,7 @@ fn read_resources_recursive(
}
};
info!("adding resources from {path:?}");
resources.server_templates.extend(more.server_templates);
resources.servers.extend(more.servers);
resources.builds.extend(more.builds);
resources.deployments.extend(more.deployments);
@@ -7,8 +7,7 @@ use monitor_client::{
},
entities::{
alerter::{
Alerter, AlerterConfig, AlerterInfo, AlerterListItemInfo,
PartialAlerterConfig,
Alerter, AlerterConfig, AlerterConfigDiff, AlerterInfo, AlerterListItemInfo, PartialAlerterConfig
},
resource::{Resource, ResourceListItem},
toml::ResourceToml,
@@ -22,9 +21,10 @@ use crate::{maps::name_to_alerter, monitor_client};
use super::ResourceSync;
impl ResourceSync for Alerter {
type Config = AlerterConfig;
type Info = AlerterInfo;
type PartialConfig = PartialAlerterConfig;
type FullConfig = AlerterConfig;
type FullInfo = AlerterInfo;
type ConfigDiff = AlerterConfigDiff;
type ListItemInfo = AlerterListItemInfo;
fn display() -> &'static str {
@@ -68,14 +68,14 @@ impl ResourceSync for Alerter {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetAlerter { alerter: id }).await
}
async fn minimize_update(
original: Self::FullConfig,
async fn get_diff(
original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
Ok(original.partial_diff(update))
}
}
@@ -7,8 +7,8 @@ use monitor_client::{
},
entities::{
build::{
Build, BuildConfig, BuildInfo, BuildListItemInfo,
PartialBuildConfig,
Build, BuildConfig, BuildConfigDiff, BuildInfo,
BuildListItemInfo, PartialBuildConfig,
},
resource::{Resource, ResourceListItem},
toml::ResourceToml,
@@ -25,9 +25,10 @@ use crate::{
use super::ResourceSync;
impl ResourceSync for Build {
type Config = BuildConfig;
type Info = BuildInfo;
type PartialConfig = PartialBuildConfig;
type FullConfig = BuildConfig;
type FullInfo = BuildInfo;
type ConfigDiff = BuildConfigDiff;
type ListItemInfo = BuildListItemInfo;
fn display() -> &'static str {
@@ -71,14 +72,14 @@ impl ResourceSync for Build {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetBuild { build: id }).await
}
async fn minimize_update(
mut original: Self::FullConfig,
async fn get_diff(
mut original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
// need to replace the builder id with name
original.builder_id = id_to_builder()
.get(&original.builder_id)
@@ -7,7 +7,7 @@ use monitor_client::{
},
entities::{
builder::{
Builder, BuilderConfig, BuilderListItemInfo,
Builder, BuilderConfig, BuilderConfigDiff, BuilderListItemInfo,
PartialBuilderConfig,
},
resource::{Resource, ResourceListItem},
@@ -25,9 +25,10 @@ use crate::{
use super::ResourceSync;
impl ResourceSync for Builder {
type Config = BuilderConfig;
type Info = ();
type PartialConfig = PartialBuilderConfig;
type FullConfig = BuilderConfig;
type FullInfo = ();
type ConfigDiff = BuilderConfigDiff;
type ListItemInfo = BuilderListItemInfo;
fn display() -> &'static str {
@@ -71,14 +72,14 @@ impl ResourceSync for Builder {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetBuilder { builder: id }).await
}
async fn minimize_update(
mut original: Self::FullConfig,
async fn get_diff(
mut original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
// need to replace server builder id with name
if let BuilderConfig::Server(config) = &mut original {
config.server_id = id_to_server()
@@ -4,8 +4,9 @@ use monitor_client::{
api::{read::GetDeployment, write},
entities::{
deployment::{
Deployment, DeploymentConfig, DeploymentImage,
DeploymentListItemInfo, PartialDeploymentConfig,
Deployment, DeploymentConfig, DeploymentConfigDiff,
DeploymentImage, DeploymentListItemInfo,
PartialDeploymentConfig,
},
resource::{Resource, ResourceListItem},
toml::ResourceToml,
@@ -22,9 +23,10 @@ use crate::{
use super::ResourceSync;
impl ResourceSync for Deployment {
type Config = DeploymentConfig;
type Info = ();
type PartialConfig = PartialDeploymentConfig;
type FullConfig = DeploymentConfig;
type FullInfo = ();
type ConfigDiff = DeploymentConfigDiff;
type ListItemInfo = DeploymentListItemInfo;
fn display() -> &'static str {
@@ -68,16 +70,16 @@ impl ResourceSync for Deployment {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client()
.read(GetDeployment { deployment: id })
.await
}
async fn minimize_update(
mut original: Self::FullConfig,
async fn get_diff(
mut original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
// need to replace the server id with name
original.server_id = id_to_server()
.get(&original.server_id)
+310
View File
@@ -0,0 +1,310 @@
use std::collections::HashMap;
use colored::Colorize;
use monitor_client::{
api::write::{UpdateDescription, UpdateTagsOnResource},
entities::{
resource::{Resource, ResourceListItem},
toml::ResourceToml,
update::ResourceTarget,
},
};
use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff};
use crate::{cli_args, maps::id_to_tag, monitor_client};
pub mod alerter;
pub mod build;
pub mod builder;
pub mod deployment;
pub mod procedure;
pub mod repo;
pub mod server;
pub mod server_template;
type ToUpdate<T> = Vec<ToUpdateItem<T>>;
type ToCreate<T> = Vec<ResourceToml<T>>;
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>);
pub struct ToUpdateItem<T> {
pub id: String,
pub resource: ResourceToml<T>,
pub update_description: bool,
pub update_tags: bool,
}
pub trait ResourceSync {
type Config: Clone
+ Send
+ PartialDiff<Self::PartialConfig, Self::ConfigDiff>
+ 'static;
type Info: Default;
type PartialConfig: std::fmt::Debug
+ Clone
+ Send
+ From<Self::ConfigDiff>
+ 'static;
type ConfigDiff: Diff + MaybeNone;
type ListItemInfo: 'static;
fn display() -> &'static str;
fn resource_target(id: String) -> ResourceTarget;
fn name_to_resource(
) -> &'static HashMap<String, ResourceListItem<Self::ListItemInfo>>;
/// Creates the resource and returns created id.
async fn create(
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<String>;
/// Updates the resource at id with the partial config.
async fn update(
id: String,
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<()>;
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::Config, Self::Info>>;
/// Diffs the declared toml (partial) against the full existing config.
/// Removes all fields from toml (partial) that haven't changed.
async fn get_diff(
original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::ConfigDiff>;
async fn get_updates(
resources: Vec<ResourceToml<Self::PartialConfig>>,
) -> anyhow::Result<UpdatesResult<Self::PartialConfig>> {
let map = Self::name_to_resource();
let mut to_create = ToCreate::<Self::PartialConfig>::new();
let mut to_update = ToUpdate::<Self::PartialConfig>::new();
let quiet = cli_args().quiet;
for mut resource in resources {
match map.get(&resource.name).map(|s| s.id.clone()) {
Some(id) => {
// Get the full original config for the resource.
let original = Self::get(id.clone()).await?;
let diff =
Self::get_diff(original.config, resource.config).await?;
let original_tags = original
.tags
.iter()
.filter_map(|id| {
id_to_tag().get(id).map(|t| t.name.clone())
})
.collect::<Vec<_>>();
// Only proceed if there are any fields to update,
// or a change to tags / description
if diff.is_none()
&& resource.description == original.description
&& resource.tags == original_tags
{
continue;
}
if !quiet {
println!(
"\n{}: {}: '{}'\n-------------------",
"UPDATE".blue(),
Self::display(),
resource.name.bold(),
);
let mut lines = Vec::<String>::new();
if resource.description != original.description {
lines.push(format!(
"{}: 'description'\n{}: {}\n{}: {}",
"field".dimmed(),
"from".dimmed(),
original.description.red(),
"to".dimmed(),
resource.description.green()
))
}
if resource.tags != original_tags {
let from = format!("{:?}", original_tags).red();
let to = format!("{:?}", resource.tags).green();
lines.push(format!(
"{}: 'tags'\n{}: {from}\n{}: {to}",
"field".dimmed(),
"from".dimmed(),
"to".dimmed(),
));
}
lines.extend(diff.iter_field_diffs().map(
|FieldDiff { field, from, to }| {
format!(
"{}: '{field}'\n{}: {}\n{}: {}",
"field".dimmed(),
"from".dimmed(),
from.red(),
"to".dimmed(),
to.green()
)
},
));
println!("{}", lines.join("\n-------------------\n"));
}
// Minimizes updates through diffing.
resource.config = diff.into();
let update = ToUpdateItem {
id,
update_description: resource.description
!= original.description,
update_tags: resource.tags != original_tags,
resource,
};
to_update.push(update);
}
None => {
if !quiet {
println!(
"{}: {}: {}: {resource:#?}",
"CREATE".green(),
Self::display(),
resource.name.bold().green(),
)
}
to_create.push(resource);
}
}
}
if quiet && !to_create.is_empty() {
println!(
"\n{}s {}: {:#?}",
Self::display(),
"TO CREATE".green(),
to_create.iter().map(|item| item.name.as_str())
);
}
if quiet && !to_update.is_empty() {
println!(
"\n{}s {}: {:#?}",
Self::display(),
"TO UPDATE".blue(),
to_update
.iter()
.map(|update| update.resource.name.as_str())
.collect::<Vec<_>>()
);
}
Ok((to_create, to_update))
}
async fn run_updates(
to_create: ToCreate<Self::PartialConfig>,
to_update: ToUpdate<Self::PartialConfig>,
) {
let log_after = !to_update.is_empty() || !to_create.is_empty();
for resource in to_create {
let name = resource.name.clone();
let tags = resource.tags.clone();
let description = resource.description.clone();
let id = match Self::create(resource).await {
Ok(id) => id,
Err(e) => {
warn!(
"failed to create {} {name} | {e:#}",
Self::display(),
);
continue;
}
};
Self::update_tags(id.clone(), &name, tags).await;
Self::update_description(id, &name, description).await;
info!("{} {name} created", Self::display());
}
for ToUpdateItem {
id,
resource,
update_description,
update_tags,
} in to_update
{
// Update resource
let name = resource.name.clone();
let tags = resource.tags.clone();
let description = resource.description.clone();
if update_description {
Self::update_description(id.clone(), &name, description)
.await;
}
if update_tags {
Self::update_tags(id.clone(), &name, tags).await;
}
if let Err(e) = Self::update(id, resource).await {
warn!(
"failed to update config on {} {name} | {e:#}",
Self::display()
);
} else {
info!("updated {} {name} config", Self::display());
}
info!("{} {name} updated", Self::display());
}
if log_after {
info!(
"============ {}s synced ✅ ============",
Self::display()
);
}
}
async fn update_tags(id: String, name: &str, tags: Vec<String>) {
// Update tags
if let Err(e) = monitor_client()
.write(UpdateTagsOnResource {
target: Self::resource_target(id),
tags,
})
.await
{
warn!(
"failed to update tags on {} {name} | {e:#}",
Self::display(),
);
} else {
info!("updated {} {name} tags", Self::display());
}
}
async fn update_description(
id: String,
name: &str,
description: String,
) {
if let Err(e) = monitor_client()
.write(UpdateDescription {
target: Self::resource_target(id.clone()),
description,
})
.await
{
warn!("failed to update resource {id} description | {e:#}");
} else {
info!("updated {} {name} description", Self::display());
}
}
}
@@ -8,15 +8,14 @@ use monitor_client::{
},
entities::{
procedure::{
PartialProcedureConfig, Procedure, ProcedureConfig,
ProcedureListItemInfo,
PartialProcedureConfig, Procedure, ProcedureConfig, ProcedureConfigDiff, ProcedureListItemInfo
},
resource::{Resource, ResourceListItem},
toml::ResourceToml,
update::ResourceTarget,
},
};
use partial_derive2::PartialDiff;
use partial_derive2::{MaybeNone, PartialDiff};
use crate::{
maps::{
@@ -24,14 +23,16 @@ use crate::{
id_to_server, name_to_procedure,
},
monitor_client,
sync::resources::ToUpdateItem,
};
use super::{ResourceSync, ToCreate, ToUpdate};
impl ResourceSync for Procedure {
type Config = ProcedureConfig;
type Info = ();
type PartialConfig = PartialProcedureConfig;
type FullConfig = ProcedureConfig;
type FullInfo = ();
type ConfigDiff = ProcedureConfigDiff;
type ListItemInfo = ProcedureListItemInfo;
fn display() -> &'static str {
@@ -83,28 +84,43 @@ impl ResourceSync for Procedure {
for i in 0..10 {
let mut to_pull = Vec::new();
for (id, resource) in &to_update {
for ToUpdateItem {
id,
resource,
update_description,
update_tags,
} in &to_update
{
// Update resource
let name = resource.name.clone();
let tags = resource.tags.clone();
let description = resource.description.clone();
if let Err(e) =
Self::update(id.clone(), resource.clone()).await
{
if i == 9 {
warn!(
"failed to update {} {name} | {e:#}",
Self::display()
);
if *update_description {
Self::update_description(id.clone(), &name, description)
.await;
}
if *update_tags {
Self::update_tags(id.clone(), &name, tags).await;
}
if !resource.config.is_none() {
if let Err(e) =
Self::update(id.clone(), resource.clone()).await
{
if i == 9 {
warn!(
"failed to update {} {name} | {e:#}",
Self::display()
);
}
}
}
Self::update_tags(id.clone(), &name, tags).await;
Self::update_description(id.clone(), description).await;
info!("{} {name} updated", Self::display());
// have to clone out so to_update is mutable
to_pull.push(id.clone());
}
to_update.retain(|(id, _)| !to_pull.contains(id));
//
to_update.retain(|resource| !to_pull.contains(&resource.id));
let mut to_pull = Vec::new();
for resource in &to_create {
@@ -124,7 +140,7 @@ impl ResourceSync for Procedure {
}
};
Self::update_tags(id.clone(), &name, tags).await;
Self::update_description(id, description).await;
Self::update_description(id, &name, description).await;
info!("{} {name} created", Self::display());
to_pull.push(name);
}
@@ -143,14 +159,14 @@ impl ResourceSync for Procedure {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetProcedure { procedure: id }).await
}
async fn minimize_update(
mut original: Self::FullConfig,
async fn get_diff(
mut original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
for execution in &mut original.executions {
match &mut execution.execution {
Execution::None(_) => {}
@@ -7,7 +7,8 @@ use monitor_client::{
},
entities::{
repo::{
PartialRepoConfig, Repo, RepoConfig, RepoInfo, RepoListItemInfo,
PartialRepoConfig, Repo, RepoConfig, RepoConfigDiff, RepoInfo,
RepoListItemInfo,
},
resource::{Resource, ResourceListItem},
toml::ResourceToml,
@@ -24,9 +25,10 @@ use crate::{
use super::ResourceSync;
impl ResourceSync for Repo {
type Config = RepoConfig;
type Info = RepoInfo;
type PartialConfig = PartialRepoConfig;
type FullConfig = RepoConfig;
type FullInfo = RepoInfo;
type ConfigDiff = RepoConfigDiff;
type ListItemInfo = RepoListItemInfo;
fn display() -> &'static str {
@@ -70,14 +72,14 @@ impl ResourceSync for Repo {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetRepo { repo: id }).await
}
async fn minimize_update(
mut original: Self::FullConfig,
async fn get_diff(
mut original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
// Need to replace server id with name
original.server_id = id_to_server()
.get(&original.server_id)
@@ -8,7 +8,8 @@ use monitor_client::{
entities::{
resource::{Resource, ResourceListItem},
server::{
PartialServerConfig, Server, ServerConfig, ServerListItemInfo,
PartialServerConfig, Server, ServerConfig, ServerConfigDiff,
ServerListItemInfo,
},
toml::ResourceToml,
update::ResourceTarget,
@@ -21,10 +22,11 @@ use crate::{maps::name_to_server, monitor_client};
use super::ResourceSync;
impl ResourceSync for Server {
type ListItemInfo = ServerListItemInfo;
type FullConfig = ServerConfig;
type FullInfo = ();
type Config = ServerConfig;
type Info = ();
type PartialConfig = PartialServerConfig;
type ConfigDiff = ServerConfigDiff;
type ListItemInfo = ServerListItemInfo;
fn display() -> &'static str {
"server"
@@ -67,14 +69,14 @@ impl ResourceSync for Server {
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>> {
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client().read(GetServer { server: id }).await
}
async fn minimize_update(
original: Self::FullConfig,
async fn get_diff(
original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig> {
) -> anyhow::Result<Self::ConfigDiff> {
Ok(original.partial_diff(update))
}
}
@@ -0,0 +1,85 @@
use std::collections::HashMap;
use monitor_client::{
api::{
read::GetServerTemplate,
write::{CreateServerTemplate, UpdateServerTemplate},
},
entities::{
resource::{Resource, ResourceListItem},
server_template::{
PartialServerTemplateConfig, ServerTemplate, ServerTemplateConfig, ServerTemplateConfigDiff, ServerTemplateListItemInfo
},
toml::ResourceToml,
update::ResourceTarget,
},
};
use partial_derive2::PartialDiff;
use crate::{maps::name_to_server_template, monitor_client};
use super::ResourceSync;
impl ResourceSync for ServerTemplate {
type Config = ServerTemplateConfig;
type Info = ();
type PartialConfig = PartialServerTemplateConfig;
type ConfigDiff = ServerTemplateConfigDiff;
type ListItemInfo = ServerTemplateListItemInfo;
fn display() -> &'static str {
"server template"
}
fn resource_target(id: String) -> ResourceTarget {
ResourceTarget::ServerTemplate(id)
}
fn name_to_resource(
) -> &'static HashMap<String, ResourceListItem<Self::ListItemInfo>>
{
name_to_server_template()
}
async fn create(
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<String> {
monitor_client()
.write(CreateServerTemplate {
name: resource.name,
config: resource.config,
})
.await
.map(|res| res.id)
}
async fn update(
id: String,
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<()> {
monitor_client()
.write(UpdateServerTemplate {
id,
config: resource.config,
})
.await?;
Ok(())
}
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
monitor_client()
.read(GetServerTemplate {
server_template: id,
})
.await
}
async fn get_diff(
original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::ConfigDiff> {
Ok(original.partial_diff(update))
}
}
+7 -6
View File
@@ -13,19 +13,20 @@ path = "src/main.rs"
[dependencies]
# local
monitor_client.workspace = true
periphery_client.workspace = true
monitor_client.workspace = true
logger.workspace = true
# mogh
async_timing_util.workspace = true
merge_config_files.workspace = true
parse_csl.workspace = true
termination_signal.workspace = true
resolver_api.workspace = true
mungos.workspace = true
async_timing_util.workspace = true
partial_derive2.workspace = true
mongo_indexed.workspace = true
slack.workspace = true
resolver_api.workspace = true
parse_csl.workspace = true
mungos.workspace = true
serror.workspace = true
slack.workspace = true
# external
tokio.workspace = true
tokio-util.workspace = true
+6 -6
View File
@@ -46,9 +46,9 @@ use crate::{
channel::build_cancel_channel,
periphery_client,
query::get_deployment_state,
resource::StateResource,
update::{add_update, make_update, update_update},
},
resource,
state::{action_states, db_client, State},
};
@@ -60,7 +60,7 @@ impl Resolve<RunBuild, User> for State {
RunBuild { build }: RunBuild,
user: User,
) -> anyhow::Result<Update> {
let mut build = Build::get_resource_check_permissions(
let mut build = resource::get_check_permissions::<Build>(
&build,
&user,
PermissionLevel::Execute,
@@ -262,7 +262,7 @@ impl Resolve<CancelBuild, User> for State {
CancelBuild { build }: CancelBuild,
user: User,
) -> anyhow::Result<CancelBuildResponse> {
let build = Build::get_resource_check_permissions(
let build = resource::get_check_permissions::<Build>(
&build,
&user,
PermissionLevel::Execute,
@@ -323,13 +323,13 @@ async fn get_build_builder(
return Err(anyhow!("build has not configured a builder"));
}
let builder =
Builder::get_resource(&build.config.builder_id).await?;
resource::get::<Builder>(&build.config.builder_id).await?;
match builder.config {
BuilderConfig::Server(config) => {
if config.server_id.is_empty() {
return Err(anyhow!("builder has not configured a server"));
}
let server = Server::get_resource(&config.server_id).await?;
let server = resource::get::<Server>(&config.server_id).await?;
let periphery = periphery_client(&server)?;
Ok((
periphery,
@@ -355,7 +355,7 @@ async fn get_aws_builder(
let instance_name = format!(
"BUILDER-{}-v{}",
build.name,
build.config.version.to_string()
build.config.version
);
let Ec2Instance { instance_id, ip } = launch_ec2_instance(
&instance_name,
+13 -12
View File
@@ -24,10 +24,10 @@ use crate::{
helpers::{
periphery_client,
query::get_server_with_status,
resource::StateResource,
update::{add_update, make_update, update_update},
},
monitor::update_cache_for_server,
resource,
state::{action_states, db_client, State},
};
@@ -43,12 +43,13 @@ impl Resolve<Deploy, User> for State {
}: Deploy,
user: User,
) -> anyhow::Result<Update> {
let mut deployment = Deployment::get_resource_check_permissions(
&deployment,
&user,
PermissionLevel::Execute,
)
.await?;
let mut deployment =
resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Execute,
)
.await?;
// get the action state for the deployment (or insert default).
let action_state = action_states()
@@ -77,7 +78,7 @@ impl Resolve<Deploy, User> for State {
let version = match deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let build = Build::get_resource(&build_id).await?;
let build = resource::get::<Build>(&build_id).await?;
let image_name = get_image_name(&build);
let version = if version.is_none() {
build.config.version
@@ -85,7 +86,7 @@ impl Resolve<Deploy, User> for State {
version
};
deployment.config.image = DeploymentImage::Image {
image: format!("{image_name}:{}", version.to_string()),
image: format!("{image_name}:{version}"),
};
if deployment.config.docker_account.is_empty() {
deployment.config.docker_account =
@@ -143,7 +144,7 @@ impl Resolve<StartContainer, User> for State {
StartContainer { deployment }: StartContainer,
user: User,
) -> anyhow::Result<Update> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Execute,
@@ -222,7 +223,7 @@ impl Resolve<StopContainer, User> for State {
}: StopContainer,
user: User,
) -> anyhow::Result<Update> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Execute,
@@ -381,7 +382,7 @@ impl Resolve<RemoveContainer, User> for State {
}: RemoveContainer,
user: User,
) -> anyhow::Result<Update> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Execute,
+2 -4
View File
@@ -13,10 +13,8 @@ use tokio::sync::Mutex;
use crate::{
helpers::{
procedure::execute_procedure,
resource::StateResource,
update::{add_update, make_update, update_update},
},
state::{action_states, State},
}, resource, state::{action_states, State}
};
#[async_trait]
@@ -27,7 +25,7 @@ impl Resolve<RunProcedure, User> for State {
RunProcedure { procedure }: RunProcedure,
user: User,
) -> anyhow::Result<Update> {
let procedure = Procedure::get_resource_check_permissions(
let procedure = resource::get_check_permissions::<Procedure>(
&procedure,
&user,
PermissionLevel::Execute,
+8 -6
View File
@@ -23,9 +23,9 @@ use crate::{
config::core_config,
helpers::{
periphery_client,
resource::StateResource,
update::{add_update, update_update},
},
resource,
state::{action_states, db_client, State},
};
@@ -37,7 +37,7 @@ impl Resolve<CloneRepo, User> for State {
CloneRepo { repo }: CloneRepo,
user: User,
) -> anyhow::Result<Update> {
let repo = Repo::get_resource_check_permissions(
let repo = resource::get_check_permissions::<Repo>(
&repo,
&user,
PermissionLevel::Execute,
@@ -57,7 +57,8 @@ impl Resolve<CloneRepo, User> for State {
return Err(anyhow!("repo has no server attached"));
}
let server = Server::get_resource(&repo.config.server_id).await?;
let server =
resource::get::<Server>(&repo.config.server_id).await?;
let periphery = periphery_client(&server)?;
@@ -126,10 +127,10 @@ impl Resolve<PullRepo, User> for State {
PullRepo { repo }: PullRepo,
user: User,
) -> anyhow::Result<Update> {
let repo = Repo::get_resource_check_permissions(
let repo = resource::get_check_permissions::<Repo>(
&repo,
&user,
PermissionLevel::Write,
PermissionLevel::Execute,
)
.await?;
@@ -146,7 +147,8 @@ impl Resolve<PullRepo, User> for State {
return Err(anyhow!("repo has no server attached"));
}
let server = Server::get_resource(&repo.config.server_id).await?;
let server =
resource::get::<Server>(&repo.config.server_id).await?;
let periphery = periphery_client(&server)?;
+4 -4
View File
@@ -18,9 +18,9 @@ use serror::serialize_error_pretty;
use crate::{
helpers::{
periphery_client,
resource::StateResource,
update::{add_update, make_update, update_update},
},
resource,
state::{action_states, State},
};
@@ -32,7 +32,7 @@ impl Resolve<PruneDockerContainers, User> for State {
PruneDockerContainers { server }: PruneDockerContainers,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Execute,
@@ -89,7 +89,7 @@ impl Resolve<PruneDockerNetworks, User> for State {
PruneDockerNetworks { server }: PruneDockerNetworks,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Execute,
@@ -146,7 +146,7 @@ impl Resolve<PruneDockerImages, User> for State {
PruneDockerImages { server }: PruneDockerImages,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Execute,
+51 -23
View File
@@ -1,3 +1,4 @@
use anyhow::{anyhow, Context};
use axum::async_trait;
use monitor_client::{
api::{execute::LaunchServer, write::CreateServer},
@@ -10,15 +11,12 @@ use monitor_client::{
Operation,
},
};
use mungos::mongodb::bson::doc;
use resolver_api::Resolve;
use serror::serialize_error_pretty;
use crate::{
cloud::aws::launch_ec2_instance,
helpers::{
resource::StateResource,
update::{add_update, make_update, update_update},
},
state::State,
cloud::aws::launch_ec2_instance, helpers::update::{add_update, make_update, update_update}, resource, state::{db_client, State}
};
#[async_trait]
@@ -32,20 +30,40 @@ impl Resolve<LaunchServer, User> for State {
}: LaunchServer,
user: User,
) -> anyhow::Result<Update> {
let template = ServerTemplate::get_resource_check_permissions(
// validate name isn't already taken by another server
if db_client()
.await
.servers
.find_one(
doc! {
"name": &name
},
None,
)
.await
.context("failed to query db for servers")?
.is_some()
{
return Err(anyhow!("name is already taken"));
}
let template = resource::get_check_permissions::<ServerTemplate>(
&server_template,
&user,
PermissionLevel::Execute,
)
.await?;
let mut update =
make_update(&template, Operation::LaunchServer, &user);
update.in_progress();
update.push_simple_log(
"launching server",
format!("{:#?}", template.config),
);
update.id = add_update(update.clone()).await?;
match template.config {
let config = match template.config {
ServerTemplateConfig::Aws(config) => {
let region = config.region.clone();
let instance = launch_ec2_instance(&name, config).await;
@@ -66,22 +84,32 @@ impl Resolve<LaunchServer, User> for State {
instance.ip
),
);
let _ = self
.resolve(
CreateServer {
name,
config: PartialServerConfig {
address: format!("http://{}:8120", instance.ip)
.into(),
region: region.into(),
..Default::default()
},
},
user,
)
.await;
PartialServerConfig {
address: format!("http://{}:8120", instance.ip).into(),
region: region.into(),
..Default::default()
}
}
}
};
match self.resolve(CreateServer { name, config }, user).await {
Ok(server) => {
update.push_simple_log(
"create server",
format!("created server {} ({})", server.name, server.id),
);
}
Err(e) => {
update.push_error_log(
"create server",
format!(
"failed to create server\n\n{}",
serialize_error_pretty(&e)
),
);
}
};
update.finalize();
update_update(update.clone()).await?;
Ok(update)
+12 -6
View File
@@ -4,7 +4,7 @@ use monitor_client::{
api::read::{
GetAlert, GetAlertResponse, ListAlerts, ListAlertsResponse,
},
entities::{deployment::Deployment, server::Server, user::User},
entities::{update::ResourceTargetVariant, user::User},
};
use mungos::{
by_id::find_one_by_id,
@@ -14,7 +14,7 @@ use mungos::{
use resolver_api::Resolve;
use crate::{
helpers::resource::StateResource,
helpers::query::get_resource_ids_for_non_admin,
state::{db_client, State},
};
@@ -29,10 +29,16 @@ impl Resolve<ListAlerts, User> for State {
) -> anyhow::Result<ListAlertsResponse> {
let mut query = query.unwrap_or_default();
if !user.admin {
let server_ids =
Server::get_resource_ids_for_non_admin(&user.id).await?;
let deployment_ids =
Deployment::get_resource_ids_for_non_admin(&user.id).await?;
let server_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Server,
)
.await?;
let deployment_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Deployment,
)
.await?;
query.extend(doc! {
"$or": [
{ "target.type": "Server", "target.id": { "$in": &server_ids } },
+4 -5
View File
@@ -15,9 +15,8 @@ use mungos::mongodb::bson::{doc, oid::ObjectId};
use resolver_api::Resolve;
use crate::{
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{db_client, State},
};
@@ -28,7 +27,7 @@ impl Resolve<GetAlerter, User> for State {
GetAlerter { alerter }: GetAlerter,
user: User,
) -> anyhow::Result<Alerter> {
Alerter::get_resource_check_permissions(
resource::get_check_permissions::<Alerter>(
&alerter,
&user,
PermissionLevel::Read,
@@ -44,7 +43,7 @@ impl Resolve<ListAlerters, User> for State {
ListAlerters { query }: ListAlerters,
user: User,
) -> anyhow::Result<Vec<AlerterListItem>> {
Alerter::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Alerter>(query, &user).await
}
}
+6 -7
View File
@@ -25,9 +25,8 @@ use resolver_api::{Resolve, ResolveToString};
use crate::{
config::core_config,
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{action_states, db_client, State},
};
@@ -38,7 +37,7 @@ impl Resolve<GetBuild, User> for State {
GetBuild { build }: GetBuild,
user: User,
) -> anyhow::Result<Build> {
Build::get_resource_check_permissions(
resource::get_check_permissions::<Build>(
&build,
&user,
PermissionLevel::Read,
@@ -54,7 +53,7 @@ impl Resolve<ListBuilds, User> for State {
ListBuilds { query }: ListBuilds,
user: User,
) -> anyhow::Result<Vec<BuildListItem>> {
Build::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Build>(query, &user).await
}
}
@@ -65,7 +64,7 @@ impl Resolve<GetBuildActionState, User> for State {
GetBuildActionState { build }: GetBuildActionState,
user: User,
) -> anyhow::Result<BuildActionState> {
let build = Build::get_resource_check_permissions(
let build = resource::get_check_permissions::<Build>(
&build,
&user,
PermissionLevel::Read,
@@ -196,7 +195,7 @@ impl Resolve<GetBuildVersions, User> for State {
}: GetBuildVersions,
user: User,
) -> anyhow::Result<Vec<BuildVersionResponseItem>> {
let build = Build::get_resource_check_permissions(
let build = resource::get_check_permissions::<Build>(
&build,
&user,
PermissionLevel::Read,
+5 -6
View File
@@ -16,9 +16,8 @@ use resolver_api::Resolve;
use crate::{
config::core_config,
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{db_client, State},
};
@@ -29,7 +28,7 @@ impl Resolve<GetBuilder, User> for State {
GetBuilder { builder }: GetBuilder,
user: User,
) -> anyhow::Result<Builder> {
Builder::get_resource_check_permissions(
resource::get_check_permissions::<Builder>(
&builder,
&user,
PermissionLevel::Read,
@@ -45,7 +44,7 @@ impl Resolve<ListBuilders, User> for State {
ListBuilders { query }: ListBuilders,
user: User,
) -> anyhow::Result<Vec<BuilderListItem>> {
Builder::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Builder>(query, &user).await
}
}
@@ -92,7 +91,7 @@ impl Resolve<GetBuilderAvailableAccounts, User> for State {
GetBuilderAvailableAccounts { builder }: GetBuilderAvailableAccounts,
user: User,
) -> anyhow::Result<GetBuilderAvailableAccountsResponse> {
let builder = Builder::get_resource_check_permissions(
let builder = resource::get_check_permissions::<Builder>(
&builder,
&user,
PermissionLevel::Read,
+13 -13
View File
@@ -24,9 +24,9 @@ use resolver_api::Resolve;
use crate::{
helpers::{
periphery_client,
resource::{get_resource_ids_for_non_admin, StateResource},
periphery_client, query::get_resource_ids_for_non_admin,
},
resource,
state::{action_states, db_client, deployment_status_cache, State},
};
@@ -37,7 +37,7 @@ impl Resolve<GetDeployment, User> for State {
GetDeployment { deployment }: GetDeployment,
user: User,
) -> anyhow::Result<Deployment> {
Deployment::get_resource_check_permissions(
resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -53,7 +53,7 @@ impl Resolve<ListDeployments, User> for State {
ListDeployments { query }: ListDeployments,
user: User,
) -> anyhow::Result<Vec<DeploymentListItem>> {
Deployment::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Deployment>(query, &user).await
}
}
@@ -64,7 +64,7 @@ impl Resolve<GetDeploymentContainer, User> for State {
GetDeploymentContainer { deployment }: GetDeploymentContainer,
user: User,
) -> anyhow::Result<GetDeploymentContainerResponse> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -95,7 +95,7 @@ impl Resolve<GetLog, User> for State {
name,
config: DeploymentConfig { server_id, .. },
..
} = Deployment::get_resource_check_permissions(
} = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -104,7 +104,7 @@ impl Resolve<GetLog, User> for State {
if server_id.is_empty() {
return Ok(Log::default());
}
let server = Server::get_resource(&server_id).await?;
let server = resource::get::<Server>(&server_id).await?;
periphery_client(&server)?
.request(api::container::GetContainerLog {
name,
@@ -130,7 +130,7 @@ impl Resolve<SearchLog, User> for State {
name,
config: DeploymentConfig { server_id, .. },
..
} = Deployment::get_resource_check_permissions(
} = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -139,7 +139,7 @@ impl Resolve<SearchLog, User> for State {
if server_id.is_empty() {
return Ok(Log::default());
}
let server = Server::get_resource(&server_id).await?;
let server = resource::get::<Server>(&server_id).await?;
periphery_client(&server)?
.request(api::container::GetContainerLogSearch {
name,
@@ -162,7 +162,7 @@ impl Resolve<GetDeploymentStats, User> for State {
name,
config: DeploymentConfig { server_id, .. },
..
} = Deployment::get_resource_check_permissions(
} = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -171,7 +171,7 @@ impl Resolve<GetDeploymentStats, User> for State {
if server_id.is_empty() {
return Err(anyhow!("deployment has no server attached"));
}
let server = Server::get_resource(&server_id).await?;
let server = resource::get::<Server>(&server_id).await?;
periphery_client(&server)?
.request(api::container::GetContainerStats { name })
.await
@@ -186,7 +186,7 @@ impl Resolve<GetDeploymentActionState, User> for State {
GetDeploymentActionState { deployment }: GetDeploymentActionState,
user: User,
) -> anyhow::Result<DeploymentActionState> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read,
@@ -263,7 +263,7 @@ impl Resolve<ListCommonExtraArgs, User> for State {
user: User,
) -> anyhow::Result<ListCommonExtraArgsResponse> {
let deployments =
Deployment::list_resources_for_user(query, &user)
resource::list_full_for_user::<Deployment>(query, &user)
.await
.context("failed to get resources matching query")?;
+1 -1
View File
@@ -66,7 +66,7 @@ enum ReadRequest {
// ==== SERVER TEMPLATE ====
GetServerTemplate(GetServerTemplate),
ListServerTemplates(ListServerTemplates),
GetServerTemplateSummary(GetServerTemplatesSummary),
GetServerTemplatesSummary(GetServerTemplatesSummary),
// ==== SERVER ====
GetServersSummary(GetServersSummary),
+1 -1
View File
@@ -12,7 +12,7 @@ use mungos::{find::find_collect, mongodb::bson::doc};
use resolver_api::Resolve;
use crate::{
helpers::resource::get_user_permission_on_resource,
helpers::query::get_user_permission_on_resource,
state::{db_client, State},
};
+5 -6
View File
@@ -18,9 +18,8 @@ use mungos::mongodb::bson::{doc, oid::ObjectId};
use resolver_api::Resolve;
use crate::{
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{action_states, db_client, State},
};
@@ -31,7 +30,7 @@ impl Resolve<GetProcedure, User> for State {
GetProcedure { procedure }: GetProcedure,
user: User,
) -> anyhow::Result<GetProcedureResponse> {
Procedure::get_resource_check_permissions(
resource::get_check_permissions::<Procedure>(
&procedure,
&user,
PermissionLevel::Read,
@@ -47,7 +46,7 @@ impl Resolve<ListProcedures, User> for State {
ListProcedures { query }: ListProcedures,
user: User,
) -> anyhow::Result<ListProceduresResponse> {
Procedure::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Procedure>(query, &user).await
}
}
@@ -94,7 +93,7 @@ impl Resolve<GetProcedureActionState, User> for State {
GetProcedureActionState { procedure }: GetProcedureActionState,
user: User,
) -> anyhow::Result<GetProcedureActionStateResponse> {
let procedure = Procedure::get_resource_check_permissions(
let procedure = resource::get_check_permissions::<Procedure>(
&procedure,
&user,
PermissionLevel::Read,
+5 -6
View File
@@ -15,9 +15,8 @@ use mungos::mongodb::bson::{doc, oid::ObjectId};
use resolver_api::Resolve;
use crate::{
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{action_states, db_client, State},
};
@@ -28,7 +27,7 @@ impl Resolve<GetRepo, User> for State {
GetRepo { repo }: GetRepo,
user: User,
) -> anyhow::Result<Repo> {
Repo::get_resource_check_permissions(
resource::get_check_permissions::<Repo>(
&repo,
&user,
PermissionLevel::Read,
@@ -44,7 +43,7 @@ impl Resolve<ListRepos, User> for State {
ListRepos { query }: ListRepos,
user: User,
) -> anyhow::Result<Vec<RepoListItem>> {
Repo::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Repo>(query, &user).await
}
}
@@ -55,7 +54,7 @@ impl Resolve<GetRepoActionState, User> for State {
GetRepoActionState { repo }: GetRepoActionState,
user: User,
) -> anyhow::Result<RepoActionState> {
let repo = Repo::get_resource_check_permissions(
let repo = resource::get_check_permissions::<Repo>(
&repo,
&user,
PermissionLevel::Read,
+37 -31
View File
@@ -2,17 +2,22 @@ use async_trait::async_trait;
use monitor_client::{
api::read::{FindResources, FindResourcesResponse},
entities::{
build, deployment, procedure, repo, server,
update::ResourceTargetVariant::{self, *},
build::Build, deployment::Deployment, procedure::Procedure,
repo::Repo, server::Server, update::ResourceTargetVariant,
user::User,
},
};
use resolver_api::Resolve;
use crate::{helpers::resource::StateResource, state::State};
use crate::{resource, state::State};
const FIND_RESOURCE_TYPES: [ResourceTargetVariant; 5] =
[Server, Build, Deployment, Repo, Procedure];
const FIND_RESOURCE_TYPES: [ResourceTargetVariant; 5] = [
ResourceTargetVariant::Server,
ResourceTargetVariant::Build,
ResourceTargetVariant::Deployment,
ResourceTargetVariant::Repo,
ResourceTargetVariant::Procedure,
];
#[async_trait]
impl Resolve<FindResources, User> for State {
@@ -27,49 +32,50 @@ impl Resolve<FindResources, User> for State {
} else {
resources
.into_iter()
.filter(|r| !matches!(r, System | Builder | Alerter))
.filter(|r| {
!matches!(
r,
ResourceTargetVariant::System
| ResourceTargetVariant::Builder
| ResourceTargetVariant::Alerter
)
})
.collect()
};
for resource_type in resource_types {
match resource_type {
Server => {
res.servers =
server::Server::query_resource_list_items_for_user(
query.clone(),
&user,
)
.await?;
ResourceTargetVariant::Server => {
res.servers = resource::list_for_user_using_document::<
Server,
>(query.clone(), &user)
.await?;
}
Deployment => {
res.deployments =
deployment::Deployment::query_resource_list_items_for_user(
query.clone(),
&user,
)
.await?;
ResourceTargetVariant::Deployment => {
res.deployments = resource::list_for_user_using_document::<
Deployment,
>(query.clone(), &user)
.await?;
}
Build => {
ResourceTargetVariant::Build => {
res.builds =
build::Build::query_resource_list_items_for_user(
resource::list_for_user_using_document::<Build>(
query.clone(),
&user,
)
.await?;
}
Repo => {
res.repos = repo::Repo::query_resource_list_items_for_user(
ResourceTargetVariant::Repo => {
res.repos = resource::list_for_user_using_document::<Repo>(
query.clone(),
&user,
)
.await?;
}
Procedure => {
res.procedures =
procedure::Procedure::query_resource_list_items_for_user(
query.clone(),
&user,
)
.await?;
ResourceTargetVariant::Procedure => {
res.procedures = resource::list_for_user_using_document::<
Procedure,
>(query.clone(), &user)
.await?;
}
_ => {}
}
+24 -23
View File
@@ -29,7 +29,10 @@ use resolver_api::{Resolve, ResolveToString};
use tokio::sync::Mutex;
use crate::{
config::core_config, helpers::{periphery_client, resource::StateResource}, state::{action_states, db_client, server_status_cache, State}
config::core_config,
helpers::periphery_client,
resource,
state::{action_states, db_client, server_status_cache, State},
};
#[async_trait]
@@ -39,11 +42,9 @@ impl Resolve<GetServersSummary, User> for State {
GetServersSummary {}: GetServersSummary,
user: User,
) -> anyhow::Result<GetServersSummaryResponse> {
let servers = Server::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?;
let servers =
resource::list_for_user::<Server>(Default::default(), &user)
.await?;
let mut res = GetServersSummaryResponse::default();
for server in servers {
res.total += 1;
@@ -70,7 +71,7 @@ impl Resolve<GetPeripheryVersion, User> for State {
req: GetPeripheryVersion,
user: User,
) -> anyhow::Result<GetPeripheryVersionResponse> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&req.server,
&user,
PermissionLevel::Read,
@@ -92,7 +93,7 @@ impl Resolve<GetServer, User> for State {
req: GetServer,
user: User,
) -> anyhow::Result<Server> {
Server::get_resource_check_permissions(
resource::get_check_permissions::<Server>(
&req.server,
&user,
PermissionLevel::Read,
@@ -108,7 +109,7 @@ impl Resolve<ListServers, User> for State {
ListServers { query }: ListServers,
user: User,
) -> anyhow::Result<Vec<ServerListItem>> {
Server::list_resource_list_items_for_user(query, &user).await
resource::list_for_user::<Server>(query, &user).await
}
}
@@ -119,7 +120,7 @@ impl Resolve<GetServerStatus, User> for State {
GetServerStatus { server }: GetServerStatus,
user: User,
) -> anyhow::Result<GetServerStatusResponse> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -143,7 +144,7 @@ impl Resolve<GetServerActionState, User> for State {
GetServerActionState { server }: GetServerActionState,
user: User,
) -> anyhow::Result<ServerActionState> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -175,7 +176,7 @@ impl ResolveToString<GetSystemInformation, User> for State {
GetSystemInformation { server }: GetSystemInformation,
user: User,
) -> anyhow::Result<String> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -211,7 +212,7 @@ impl ResolveToString<GetSystemStats, User> for State {
GetSystemStats { server }: GetSystemStats,
user: User,
) -> anyhow::Result<String> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -245,7 +246,7 @@ impl ResolveToString<GetSystemProcesses, User> for State {
GetSystemProcesses { server }: GetSystemProcesses,
user: User,
) -> anyhow::Result<String> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -286,7 +287,7 @@ impl Resolve<GetHistoricalServerStats, User> for State {
}: GetHistoricalServerStats,
user: User,
) -> anyhow::Result<GetHistoricalServerStatsResponse> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -336,7 +337,7 @@ impl Resolve<GetDockerImages, User> for State {
GetDockerImages { server }: GetDockerImages,
user: User,
) -> anyhow::Result<Vec<ImageSummary>> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -355,7 +356,7 @@ impl Resolve<GetDockerNetworks, User> for State {
GetDockerNetworks { server }: GetDockerNetworks,
user: User,
) -> anyhow::Result<Vec<DockerNetwork>> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -374,7 +375,7 @@ impl Resolve<GetDockerContainers, User> for State {
GetDockerContainers { server }: GetDockerContainers,
user: User,
) -> anyhow::Result<Vec<ContainerSummary>> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
@@ -393,13 +394,13 @@ impl Resolve<GetAvailableAccounts, User> for State {
GetAvailableAccounts { server }: GetAvailableAccounts,
user: User,
) -> anyhow::Result<GetAvailableAccountsResponse> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
)
.await?;
let GetAccountsResponse { github, docker } =
periphery_client(&server)?
.request(api::GetAccounts {})
@@ -407,10 +408,10 @@ impl Resolve<GetAvailableAccounts, User> for State {
.context("failed to get accounts from periphery")?;
let mut github_set = HashSet::<String>::new();
github_set.extend(core_config().github_accounts.keys().cloned());
github_set.extend(github);
let mut github = github_set.into_iter().collect::<Vec<_>>();
github.sort();
@@ -434,7 +435,7 @@ impl Resolve<GetAvailableSecrets, User> for State {
GetAvailableSecrets { server }: GetAvailableSecrets,
user: User,
) -> anyhow::Result<GetAvailableSecretsResponse> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Read,
+3 -7
View File
@@ -17,10 +17,7 @@ use mungos::mongodb::bson::{doc, oid::ObjectId};
use resolver_api::Resolve;
use crate::{
helpers::resource::{
get_resource_ids_for_non_admin, StateResource,
},
state::{db_client, State},
helpers::query::get_resource_ids_for_non_admin, resource, state::{db_client, State}
};
#[async_trait]
@@ -30,7 +27,7 @@ impl Resolve<GetServerTemplate, User> for State {
GetServerTemplate { server_template }: GetServerTemplate,
user: User,
) -> anyhow::Result<GetServerTemplateResponse> {
ServerTemplate::get_resource_check_permissions(
resource::get_check_permissions::<ServerTemplate>(
&server_template,
&user,
PermissionLevel::Read,
@@ -46,8 +43,7 @@ impl Resolve<ListServerTemplates, User> for State {
ListServerTemplates { query }: ListServerTemplates,
user: User,
) -> anyhow::Result<ListServerTemplatesResponse> {
ServerTemplate::list_resource_list_items_for_user(query, &user)
.await
resource::list_for_user::<ServerTemplate>(query, &user).await
}
}
+56 -67
View File
@@ -33,9 +33,8 @@ use mungos::find::find_collect;
use resolver_api::Resolve;
use crate::{
helpers::{
query::get_user_user_group_ids, resource::StateResource,
},
helpers::query::get_user_user_group_ids,
resource,
state::{db_client, State},
};
@@ -49,34 +48,25 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
let mut targets = Vec::<ResourceTarget>::new();
targets.extend(
Alerter::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Alerter(resource.id)),
resource::list_for_user::<Alerter>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Alerter(resource.id)),
);
targets.extend(
Builder::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Builder(resource.id)),
resource::list_for_user::<Builder>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Builder(resource.id)),
);
targets.extend(
Server::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Server(resource.id)),
resource::list_for_user::<Server>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Server(resource.id)),
);
targets.extend(
Deployment::list_resource_list_items_for_user(
resource::list_for_user::<Deployment>(
Default::default(),
&user,
)
@@ -85,31 +75,31 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
.map(|resource| ResourceTarget::Deployment(resource.id)),
);
targets.extend(
Build::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Build(resource.id)),
resource::list_for_user::<Build>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Build(resource.id)),
);
targets.extend(
Repo::list_resource_list_items_for_user(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Repo(resource.id)),
resource::list_for_user::<Repo>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Repo(resource.id)),
);
targets.extend(
Procedure::list_resource_list_items_for_user(
resource::list_for_user::<Procedure>(Default::default(), &user)
.await?
.into_iter()
.map(|resource| ResourceTarget::Procedure(resource.id)),
);
targets.extend(
resource::list_for_user::<ServerTemplate>(
Default::default(),
&user,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Procedure(resource.id)),
.map(|resource| ResourceTarget::ServerTemplate(resource.id)),
);
let user_groups = if user.admin {
@@ -152,7 +142,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
for target in targets {
match target {
ResourceTarget::Alerter(id) => {
let alerter = Alerter::get_resource_check_permissions(
let alerter = resource::get_check_permissions::<Alerter>(
&id,
&user,
PermissionLevel::Read,
@@ -161,19 +151,18 @@ impl Resolve<ExportResourcesToToml, User> for State {
res.alerters.push(convert_resource(alerter, &names.tags))
}
ResourceTarget::ServerTemplate(id) => {
let template =
ServerTemplate::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Read,
)
.await?;
let template = resource::get_check_permissions::<
ServerTemplate,
>(
&id, &user, PermissionLevel::Read
)
.await?;
res
.server_templates
.push(convert_resource(template, &names.tags))
}
ResourceTarget::Server(id) => {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&id,
&user,
PermissionLevel::Read,
@@ -182,12 +171,13 @@ impl Resolve<ExportResourcesToToml, User> for State {
res.servers.push(convert_resource(server, &names.tags))
}
ResourceTarget::Builder(id) => {
let mut builder = Builder::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Read,
)
.await?;
let mut builder =
resource::get_check_permissions::<Builder>(
&id,
&user,
PermissionLevel::Read,
)
.await?;
// replace server id of builder
if let BuilderConfig::Server(config) = &mut builder.config {
config.server_id.clone_from(
@@ -197,7 +187,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
res.builders.push(convert_resource(builder, &names.tags))
}
ResourceTarget::Build(id) => {
let mut build = Build::get_resource_check_permissions(
let mut build = resource::get_check_permissions::<Build>(
&id,
&user,
PermissionLevel::Read,
@@ -213,13 +203,12 @@ impl Resolve<ExportResourcesToToml, User> for State {
res.builds.push(convert_resource(build, &names.tags))
}
ResourceTarget::Deployment(id) => {
let mut deployment =
Deployment::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Read,
)
.await?;
let mut deployment = resource::get_check_permissions::<
Deployment,
>(
&id, &user, PermissionLevel::Read
)
.await?;
// replace deployment server with name
deployment.config.server_id.clone_from(
names
@@ -240,7 +229,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
.push(convert_resource(deployment, &names.tags))
}
ResourceTarget::Repo(id) => {
let mut repo = Repo::get_resource_check_permissions(
let mut repo = resource::get_check_permissions::<Repo>(
&id,
&user,
PermissionLevel::Read,
@@ -270,7 +259,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
.await
.context("failed to add user groups")?;
let toml = toml::to_string_pretty(&res)
let toml = toml::to_string(&res)
.context("failed to serialize resources to toml")?;
Ok(ExportResourcesToTomlResponse { toml })
@@ -283,7 +272,7 @@ async fn add_procedure(
user: &User,
names: &ResourceNames,
) -> anyhow::Result<()> {
let mut procedure = Procedure::get_resource_check_permissions(
let mut procedure = resource::get_check_permissions::<Procedure>(
id,
user,
PermissionLevel::Read,
+55 -24
View File
@@ -14,7 +14,9 @@ use monitor_client::{
repo::Repo,
server::Server,
server_template::ServerTemplate,
update::{ResourceTarget, Update, UpdateListItem},
update::{
ResourceTarget, ResourceTargetVariant, Update, UpdateListItem,
},
user::User,
},
};
@@ -26,7 +28,8 @@ use mungos::{
use resolver_api::Resolve;
use crate::{
helpers::resource::StateResource,
helpers::query::get_resource_ids_for_non_admin,
resource,
state::{db_client, State},
};
@@ -42,20 +45,47 @@ impl Resolve<ListUpdates, User> for State {
let query = if user.admin {
query
} else {
let server_ids =
Server::get_resource_ids_for_non_admin(&user.id).await?;
let deployment_ids =
Deployment::get_resource_ids_for_non_admin(&user.id).await?;
let build_ids =
Build::get_resource_ids_for_non_admin(&user.id).await?;
let repo_ids =
Repo::get_resource_ids_for_non_admin(&user.id).await?;
let procedure_ids =
Procedure::get_resource_ids_for_non_admin(&user.id).await?;
let builder_ids =
Builder::get_resource_ids_for_non_admin(&user.id).await?;
let alerter_ids =
Alerter::get_resource_ids_for_non_admin(&user.id).await?;
let server_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Server,
)
.await?;
let deployment_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Deployment,
)
.await?;
let build_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Build,
)
.await?;
let repo_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Repo,
)
.await?;
let procedure_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Procedure,
)
.await?;
let builder_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Builder,
)
.await?;
let alerter_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::Alerter,
)
.await?;
let server_template_ids = get_resource_ids_for_non_admin(
&user.id,
ResourceTargetVariant::ServerTemplate,
)
.await?;
let mut query = query.unwrap_or_default();
query.extend(doc! {
"$or": [
@@ -66,6 +96,7 @@ impl Resolve<ListUpdates, User> for State {
{ "target.type": "Procedure", "target.id": { "$in": &procedure_ids } },
{ "target.type": "Builder", "target.id": { "$in": &builder_ids } },
{ "target.type": "Alerter", "target.id": { "$in": &alerter_ids } },
{ "target.type": "ServerTemplate", "target.id": { "$in": &server_template_ids } },
]
});
query.into()
@@ -145,7 +176,7 @@ impl Resolve<GetUpdate, User> for State {
))
}
ResourceTarget::Server(id) => {
Server::get_resource_check_permissions(
resource::get_check_permissions::<Server>(
id,
&user,
PermissionLevel::Read,
@@ -153,7 +184,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Deployment(id) => {
Deployment::get_resource_check_permissions(
resource::get_check_permissions::<Deployment>(
id,
&user,
PermissionLevel::Read,
@@ -161,7 +192,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Build(id) => {
Build::get_resource_check_permissions(
resource::get_check_permissions::<Build>(
id,
&user,
PermissionLevel::Read,
@@ -169,7 +200,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Repo(id) => {
Repo::get_resource_check_permissions(
resource::get_check_permissions::<Repo>(
id,
&user,
PermissionLevel::Read,
@@ -177,7 +208,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Builder(id) => {
Builder::get_resource_check_permissions(
resource::get_check_permissions::<Builder>(
id,
&user,
PermissionLevel::Read,
@@ -185,7 +216,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Alerter(id) => {
Alerter::get_resource_check_permissions(
resource::get_check_permissions::<Alerter>(
id,
&user,
PermissionLevel::Read,
@@ -193,7 +224,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::Procedure(id) => {
Procedure::get_resource_check_permissions(
resource::get_check_permissions::<Procedure>(
id,
&user,
PermissionLevel::Read,
@@ -201,7 +232,7 @@ impl Resolve<GetUpdate, User> for State {
.await?;
}
ResourceTarget::ServerTemplate(id) => {
ServerTemplate::get_resource_check_permissions(
resource::get_check_permissions::<ServerTemplate>(
id,
&user,
PermissionLevel::Read,
+7 -178
View File
@@ -1,33 +1,15 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::write::{
CopyAlerter, CreateAlerter, DeleteAlerter, UpdateAlerter,
},
entities::{
alerter::{Alerter, AlerterInfo},
monitor_timestamp,
permission::PermissionLevel,
user::User,
Operation,
alerter::Alerter, permission::PermissionLevel, user::User,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_bson},
};
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update},
},
state::{db_client, State},
};
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateAlerter, User> for State {
@@ -37,57 +19,7 @@ impl Resolve<CreateAlerter, User> for State {
CreateAlerter { name, config }: CreateAlerter,
user: User,
) -> anyhow::Result<Alerter> {
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
let start_ts = monitor_timestamp();
let is_default = db_client()
.await
.alerters
.find_one(None, None)
.await?
.is_none();
let alerter = Alerter {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: AlerterInfo { is_default },
};
let alerter_id = db_client()
.await
.alerters
.insert_one(alerter, None)
.await
.context("failed to add alerter to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let alerter = Alerter::get_resource(&alerter_id).await?;
create_permission(&user, &alerter, PermissionLevel::Write).await;
let mut update =
make_update(&alerter, Operation::CreateAlerter, &user);
update.push_simple_log(
"create alerter",
format!(
"created alerter\nid: {}\nname: {}",
alerter.id, alerter.name
),
);
update
.push_simple_log("config", format!("{:#?}", alerter.config));
update.finalize();
add_update(update).await?;
Ok(alerter)
resource::create::<Alerter>(&name, config, &user).await
}
}
@@ -101,57 +33,14 @@ impl Resolve<CopyAlerter, User> for State {
) -> anyhow::Result<Alerter> {
let Alerter {
config,
description,
..
} = Alerter::get_resource_check_permissions(
} = resource::get_check_permissions::<Alerter>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let start_ts = monitor_timestamp();
let alerter = Alerter {
id: Default::default(),
name,
updated_at: start_ts,
description,
config,
tags: Default::default(),
info: Default::default(),
};
let alerter_id = db_client()
.await
.alerters
.insert_one(alerter, None)
.await
.context("failed to add alerter to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let alerter = Alerter::get_resource(&alerter_id).await?;
create_permission(&user, &alerter, PermissionLevel::Write).await;
let mut update =
make_update(&alerter, Operation::CreateAlerter, &user);
update.push_simple_log(
"create alerter",
format!(
"created alerter\nid: {}\nname: {}",
alerter.id, alerter.name
),
);
update
.push_simple_log("config", format!("{:#?}", alerter.config));
update.finalize();
add_update(update).await?;
Ok(alerter)
resource::create::<Alerter>(&name, config.into(), &user).await
}
}
@@ -163,34 +52,7 @@ impl Resolve<DeleteAlerter, User> for State {
DeleteAlerter { id }: DeleteAlerter,
user: User,
) -> anyhow::Result<Alerter> {
let alerter = Alerter::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&alerter, Operation::DeleteAlerter, &user);
delete_one_by_id(&db_client().await.alerters, &id, None)
.await
.context("failed to delete alerter from database")?;
delete_all_permissions_on_resource(&alerter).await;
update.push_simple_log(
"delete alerter",
format!("deleted alerter {}", alerter.name),
);
update.finalize();
add_update(update).await?;
remove_from_recently_viewed(&alerter).await?;
Ok(alerter)
resource::delete::<Alerter>(&id, &user).await
}
}
@@ -202,39 +64,6 @@ impl Resolve<UpdateAlerter, User> for State {
UpdateAlerter { id, config }: UpdateAlerter,
user: User,
) -> anyhow::Result<Alerter> {
let alerter = Alerter::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&alerter, Operation::UpdateAlerter, &user);
update.push_simple_log(
"alerter update",
serde_json::to_string_pretty(&config)?,
);
let config = alerter.config.merge_partial(config);
update_one_by_id(
&db_client().await.alerters,
&id,
mungos::update::Update::FlattenSet(
doc! { "config": to_bson(&config)? },
),
None,
)
.await
.with_context(|| format!("failed to update alerter {id}"))?;
let alerter = Alerter::get_resource(&id).await?;
update.finalize();
add_update(update).await?;
Ok(alerter)
resource::update::<Alerter>(&id, config, &user).await
}
}
+9 -253
View File
@@ -1,101 +1,21 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::write::*,
entities::{
build::Build,
builder::Builder,
monitor_timestamp,
permission::PermissionLevel,
to_monitor_name,
update::{Log, UpdateStatus},
user::User,
Operation,
},
};
use mungos::{
by_id::update_one_by_id,
mongodb::bson::{doc, oid::ObjectId, to_document},
entities::{build::Build, permission::PermissionLevel, user::User},
};
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, empty_or_only_spaces,
remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update, update_update},
},
state::{action_states, db_client, State},
};
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateBuild, User> for State {
#[instrument(name = "CreateBuild", skip(self, user))]
async fn resolve(
&self,
CreateBuild { name, mut config }: CreateBuild,
CreateBuild { name, config }: CreateBuild,
user: User,
) -> anyhow::Result<Build> {
if !user.admin && !user.create_build_permissions {
return Err(anyhow!(
"User does not have create build permissions."
));
}
let name = to_monitor_name(&name);
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
if let Some(builder_id) = &config.builder_id {
let builder = Builder::get_resource_check_permissions(builder_id, &user, PermissionLevel::Read)
.await
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
config.builder_id = Some(builder.id)
}
let start_ts = monitor_timestamp();
let build = Build {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: Default::default(),
};
let build_id = db_client()
.await
.builds
.insert_one(build, None)
.await
.context("failed to add build to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let build = Build::get_resource(&build_id).await?;
create_permission(&user, &build, PermissionLevel::Write).await;
let mut update =
make_update(&build, Operation::CreateBuild, &user);
update.push_simple_log(
"create build",
format!(
"created build\nid: {}\nname: {}",
build.id, build.name
),
);
update.push_simple_log("config", format!("{:#?}", build.config));
update.finalize();
add_update(update).await?;
Ok(build)
resource::create::<Build>(&name, config, &user).await
}
}
@@ -107,70 +27,16 @@ impl Resolve<CopyBuild, User> for State {
CopyBuild { name, id }: CopyBuild,
user: User,
) -> anyhow::Result<Build> {
if !user.admin && !user.create_build_permissions {
return Err(anyhow!(
"User does not have create build permissions."
));
}
let name = to_monitor_name(&name);
let Build {
config,
description,
tags,
..
} = Build::get_resource_check_permissions(
} = resource::get_check_permissions::<Build>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Builder::get_resource_check_permissions(&config.builder_id, &user, PermissionLevel::Read)
.await
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
let start_ts = monitor_timestamp();
let build = Build {
id: Default::default(),
name,
updated_at: start_ts,
description,
tags,
config,
info: Default::default(),
};
let build_id = db_client()
.await
.builds
.insert_one(build, None)
.await
.context("failed to add build to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let build = Build::get_resource(&build_id).await?;
create_permission(&user, &build, PermissionLevel::Write).await;
let mut update =
make_update(&build, Operation::CreateBuild, &user);
update.push_simple_log(
"create build",
format!(
"created build\nid: {}\nname: {}",
build.id, build.name
),
);
update.push_simple_log(
"config",
serde_json::to_string_pretty(&build)?,
);
update.finalize();
add_update(update).await?;
Ok(build)
resource::create::<Build>(&name, config.into(), &user).await
}
}
@@ -182,55 +48,7 @@ impl Resolve<DeleteBuild, User> for State {
DeleteBuild { id }: DeleteBuild,
user: User,
) -> anyhow::Result<Build> {
if action_states()
.build
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("build busy"));
}
let build = Build::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&build, Operation::DeleteBuild, &user);
update.status = UpdateStatus::InProgress;
update.id = add_update(update.clone()).await?;
let res = db_client()
.await
.builds
.delete_one(doc! { "_id": ObjectId::from_str(&id)? }, None)
.await
.context("failed to delete build from database");
delete_all_permissions_on_resource(&build).await;
let log = match res {
Ok(_) => Log::simple(
"delete build",
format!("deleted build {}", build.name),
),
Err(e) => Log::error(
"delete build",
format!("failed to delete build\n{e:#?}"),
),
};
update.logs.push(log);
update.finalize();
update_update(update).await?;
remove_from_recently_viewed(&build).await?;
Ok(build)
resource::delete::<Build>(&id, &user).await
}
}
@@ -239,71 +57,9 @@ impl Resolve<UpdateBuild, User> for State {
#[instrument(name = "UpdateBuild", skip(self, user))]
async fn resolve(
&self,
UpdateBuild { id, mut config }: UpdateBuild,
UpdateBuild { id, config }: UpdateBuild,
user: User,
) -> anyhow::Result<Build> {
if action_states()
.build
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("build busy"));
}
let build = Build::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
if let Some(builder_id) = &config.builder_id {
let builder = Builder::get_resource_check_permissions(builder_id, &user, PermissionLevel::Read)
.await
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
config.builder_id = Some(builder.id)
}
if let Some(build_args) = &mut config.build_args {
build_args.retain(|v| {
!empty_or_only_spaces(&v.variable)
&& !empty_or_only_spaces(&v.value)
})
}
if let Some(extra_args) = &mut config.extra_args {
extra_args.retain(|v| !empty_or_only_spaces(v))
}
let config_doc = to_document(&config)
.context("failed to serialize config to bson document")?;
update_one_by_id(
&db_client().await.builds,
&build.id,
mungos::update::Update::FlattenSet(
doc! { "config": config_doc },
),
None,
)
.await
.context("failed to update build on database")?;
let mut update =
make_update(&build, Operation::UpdateBuild, &user);
update.push_simple_log(
"build update",
serde_json::to_string_pretty(&config)
.context("failed to serialize config to json")?,
);
update.finalize();
add_update(update).await?;
let build = Build::get_resource(&build.id).await?;
Ok(build)
resource::update::<Build>(&id, config, &user).await
}
}
+12 -231
View File
@@ -1,116 +1,23 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::write::*,
entities::{
builder::{
Builder, PartialBuilderConfig, PartialServerBuilderConfig,
},
monitor_timestamp,
permission::PermissionLevel,
server::Server,
update::{Log, ResourceTarget, Update},
user::User,
Operation,
builder::Builder, permission::PermissionLevel, user::User,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_document},
};
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update},
},
state::{db_client, State},
};
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialBuilderConfig,
user: &User,
) -> anyhow::Result<()> {
match config {
PartialBuilderConfig::Server(PartialServerBuilderConfig {
server_id: Some(server_id),
}) if !server_id.is_empty() => {
let server = Server::get_resource_check_permissions(
server_id,
user,
PermissionLevel::Write,
)
.await?;
*server_id = server.id;
}
_ => {}
}
Ok(())
}
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateBuilder, User> for State {
#[instrument(name = "CreateBuilder", skip(self, user))]
async fn resolve(
&self,
CreateBuilder { name, mut config }: CreateBuilder,
CreateBuilder { name, config }: CreateBuilder,
user: User,
) -> anyhow::Result<Builder> {
let start_ts = monitor_timestamp();
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
validate_config(&mut config, &user).await?;
let builder = Builder {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: Default::default(),
};
let builder_id = db_client()
.await
.builders
.insert_one(builder, None)
.await
.context("failed to add builder to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let builder = Builder::get_resource(&builder_id).await?;
create_permission(&user, &builder, PermissionLevel::Write).await;
let update = Update {
target: ResourceTarget::Builder(builder_id),
operation: Operation::CreateBuilder,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create builder",
format!(
"created builder\nid: {}\nname: {}",
builder.id, builder.name
),
),
Log::simple("config", format!("{:#?}", builder.config)),
],
..Default::default()
};
add_update(update).await?;
Ok(builder)
resource::create::<Builder>(&name, config, &user).await
}
}
@@ -122,61 +29,13 @@ impl Resolve<CopyBuilder, User> for State {
CopyBuilder { name, id }: CopyBuilder,
user: User,
) -> anyhow::Result<Builder> {
let Builder {
config,
description,
..
} = Builder::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
let Builder { config, .. } = resource::get_check_permissions::<
Builder,
>(
&id, &user, PermissionLevel::Write
)
.await?;
let start_ts = monitor_timestamp();
let builder = Builder {
id: Default::default(),
name,
updated_at: start_ts,
description,
tags: Default::default(),
config,
info: (),
};
let builder_id = db_client()
.await
.builders
.insert_one(builder, None)
.await
.context("failed to add builder to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let builder = Builder::get_resource(&builder_id).await?;
create_permission(&user, &builder, PermissionLevel::Write).await;
let update = Update {
target: ResourceTarget::Builder(builder_id),
operation: Operation::CreateBuilder,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create builder",
format!(
"created builder\nid: {}\nname: {}",
builder.id, builder.name
),
),
Log::simple("config", format!("{:#?}", builder.config)),
],
..Default::default()
};
add_update(update).await?;
Ok(builder)
resource::create::<Builder>(&name, config.into(), &user).await
}
}
@@ -188,46 +47,7 @@ impl Resolve<DeleteBuilder, User> for State {
DeleteBuilder { id }: DeleteBuilder,
user: User,
) -> anyhow::Result<Builder> {
let builder = Builder::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
// remove the builder from any attached builds
db_client()
.await
.builds
.update_many(
doc! { "config.builder.params.builder_id": &id },
mungos::update::Update::Set(
doc! { "config.builder.params.builder_id": "" },
),
None,
)
.await?;
delete_one_by_id(&db_client().await.builders, &id, None)
.await
.context("failed to delete builder from database")?;
delete_all_permissions_on_resource(&builder).await;
let mut update =
make_update(&builder, Operation::DeleteBuilder, &user);
update.push_simple_log(
"delete builder",
format!("deleted builder {}", builder.name),
);
update.finalize();
add_update(update).await?;
remove_from_recently_viewed(&builder).await?;
Ok(builder)
resource::delete::<Builder>(&id, &user).await
}
}
@@ -236,48 +56,9 @@ impl Resolve<UpdateBuilder, User> for State {
#[instrument(name = "UpdateBuilder", skip(self, user))]
async fn resolve(
&self,
UpdateBuilder { id, mut config }: UpdateBuilder,
UpdateBuilder { id, config }: UpdateBuilder,
user: User,
) -> anyhow::Result<Builder> {
let builder = Builder::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
validate_config(&mut config, &user).await?;
let mut update = Update {
target: ResourceTarget::Builder(id.clone()),
operation: Operation::UpdateBuilder,
start_ts: monitor_timestamp(),
logs: vec![Log::simple(
"builder update",
serde_json::to_string_pretty(&config)
.context("failed to serialize config update")?,
)],
operator: user.id.clone(),
..Default::default()
};
let config = builder.config.merge_partial(config);
update_one_by_id(
&db_client().await.builders,
&id,
mungos::update::Update::FlattenSet(
doc! { "config": to_document(&config)? },
),
None,
)
.await?;
let builder = Builder::get_resource(&id).await?;
update.finalize();
add_update(update).await?;
Ok(builder)
resource::update::<Builder>(&id, config, &user).await
}
}
+21 -332
View File
@@ -1,146 +1,41 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::write::*,
entities::{
all_logs_success,
build::Build,
deployment::{
Deployment, DeploymentImage, DockerContainerState,
PartialDeploymentConfig,
},
deployment::{Deployment, DockerContainerState},
monitor_timestamp,
permission::PermissionLevel,
server::Server,
to_monitor_name,
update::{Log, Update, UpdateStatus},
update::Update,
user::User,
Operation,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_document},
};
use mungos::{by_id::update_one_by_id, mongodb::bson::doc};
use periphery_client::api;
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, empty_or_only_spaces, periphery_client,
periphery_client,
query::get_deployment_state,
remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update, update_update},
update::{add_update, make_update},
},
resource,
state::{action_states, db_client, State},
};
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialDeploymentConfig,
user: &User,
) -> anyhow::Result<()> {
if let Some(server_id) = &config.server_id {
if !server_id.is_empty() {
let server = Server::get_resource_check_permissions(server_id, user, PermissionLevel::Write)
.await
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
config.server_id = Some(server.id);
}
}
if let Some(DeploymentImage::Build { build_id, version }) =
&config.image
{
if !build_id.is_empty() {
let build = Build::get_resource_check_permissions(build_id, user, PermissionLevel::Read)
.await
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
config.image = Some(DeploymentImage::Build {
build_id: build.id,
version: version.clone(),
});
}
}
if let Some(volumes) = &mut config.volumes {
volumes.retain(|v| {
!empty_or_only_spaces(&v.local)
&& !empty_or_only_spaces(&v.container)
})
}
if let Some(ports) = &mut config.ports {
ports.retain(|v| {
!empty_or_only_spaces(&v.local)
&& !empty_or_only_spaces(&v.container)
})
}
if let Some(environment) = &mut config.environment {
environment.retain(|v| {
!empty_or_only_spaces(&v.variable)
&& !empty_or_only_spaces(&v.value)
})
}
if let Some(extra_args) = &mut config.extra_args {
extra_args.retain(|v| !empty_or_only_spaces(v))
}
Ok(())
}
#[async_trait]
impl Resolve<CreateDeployment, User> for State {
#[instrument(name = "CreateDeployment", skip(self, user))]
async fn resolve(
&self,
CreateDeployment { name, mut config }: CreateDeployment,
CreateDeployment { name, config }: CreateDeployment,
user: User,
) -> anyhow::Result<Deployment> {
let name = to_monitor_name(&name);
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
validate_config(&mut config, &user).await?;
let start_ts = monitor_timestamp();
let deployment = Deployment {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: (),
};
let deployment_id = db_client()
.await
.deployments
.insert_one(&deployment, None)
.await
.context("failed to add deployment to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let deployment = Deployment::get_resource(&deployment_id).await?;
create_permission(&user, &deployment, PermissionLevel::Write)
.await;
let mut update =
make_update(&deployment, Operation::CreateDeployment, &user);
update.push_simple_log(
"create deployment",
format!(
"created deployment\nid: {}\nname: {}",
deployment.id, deployment.name
),
);
update
.push_simple_log("config", format!("{:#?}", deployment.config));
update.finalize();
add_update(update).await?;
Ok(deployment)
resource::create::<Deployment>(&name, config, &user).await
}
}
@@ -152,71 +47,14 @@ impl Resolve<CopyDeployment, User> for State {
CopyDeployment { name, id }: CopyDeployment,
user: User,
) -> anyhow::Result<Deployment> {
let name = to_monitor_name(&name);
let Deployment {
config,
description,
tags,
..
} = Deployment::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
if !config.server_id.is_empty() {
Server::get_resource_check_permissions(&config.server_id, &user, PermissionLevel::Write)
.await
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
}
if let DeploymentImage::Build { build_id, .. } = &config.image {
if !build_id.is_empty() {
Build::get_resource_check_permissions(build_id, &user, PermissionLevel::Read)
.await
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
}
}
let start_ts = monitor_timestamp();
let deployment = Deployment {
id: Default::default(),
name,
updated_at: start_ts,
description,
tags,
config,
info: (),
};
let deployment_id = db_client()
.await
.deployments
.insert_one(&deployment, None)
.await
.context("failed to add deployment to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let deployment = Deployment::get_resource(&deployment_id).await?;
create_permission(&user, &deployment, PermissionLevel::Write)
.await;
let mut update =
make_update(&deployment, Operation::CreateDeployment, &user);
update.push_simple_log(
"create deployment",
format!(
"created deployment\nid: {}\nname: {}",
deployment.id, deployment.name
),
);
update
.push_simple_log("config", format!("{:#?}", deployment.config));
update.finalize();
add_update(update).await?;
Ok(deployment)
let Deployment { config, .. } =
resource::get_check_permissions::<Deployment>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
resource::create::<Deployment>(&name, config.into(), &user).await
}
}
@@ -228,109 +66,7 @@ impl Resolve<DeleteDeployment, User> for State {
DeleteDeployment { id }: DeleteDeployment,
user: User,
) -> anyhow::Result<Deployment> {
let deployment = Deployment::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
// get the action state for the deployment (or insert default).
let action_state = action_states()
.deployment
.get_or_insert_default(&deployment.id)
.await;
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let _action_guard =
action_state.update(|state| state.deleting = true)?;
let state = get_deployment_state(&deployment)
.await
.context("failed to get container state")?;
let mut update =
make_update(&deployment, Operation::DeleteDeployment, &user);
update.in_progress();
update.id = add_update(update.clone()).await?;
if !matches!(
state,
DockerContainerState::NotDeployed
| DockerContainerState::Unknown
) {
// container needs to be destroyed
let server =
Server::get_resource(&deployment.config.server_id).await;
if let Err(e) = server {
update.logs.push(Log::error(
"remove container",
format!(
"failed to retrieve server at {} from db | {e:#?}",
deployment.config.server_id
),
));
} else if let Ok(server) = server {
match periphery_client(&server) {
Ok(periphery) => match periphery
.request(api::container::RemoveContainer {
name: deployment.name.clone(),
signal: deployment.config.termination_signal.into(),
time: deployment.config.termination_timeout.into(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.push_error_log(
"remove container",
format!(
"failed to remove container on periphery | {e:#?}"
),
),
},
Err(e) => update.push_error_log(
"remove container",
format!(
"failed to remove container on periphery | {e:#?}"
),
),
};
}
}
let res = delete_one_by_id(
&db_client().await.deployments,
&deployment.id,
None,
)
.await
.context("failed to delete deployment from mongo");
let log = match res {
Ok(_) => Log::simple(
"delete deployment",
format!("deleted deployment {}", deployment.name),
),
Err(e) => Log::error(
"delete deployment",
format!("failed to delete deployment\n{e:#?}"),
),
};
delete_all_permissions_on_resource(&deployment).await;
update.logs.push(log);
update.end_ts = Some(monitor_timestamp());
update.status = UpdateStatus::Complete;
update.success = all_logs_success(&update.logs);
update_update(update).await?;
remove_from_recently_viewed(&deployment).await?;
Ok(deployment)
resource::delete::<Deployment>(&id, &user).await
}
}
@@ -339,57 +75,10 @@ impl Resolve<UpdateDeployment, User> for State {
#[instrument(name = "UpdateDeployment", skip(self, user))]
async fn resolve(
&self,
UpdateDeployment { id, mut config }: UpdateDeployment,
UpdateDeployment { id, config }: UpdateDeployment,
user: User,
) -> anyhow::Result<Deployment> {
if action_states()
.deployment
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("deployment busy"));
}
let deployment = Deployment::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&deployment, Operation::UpdateDeployment, &user);
validate_config(&mut config, &user).await?;
let config_doc = to_document(&config)
.context("failed to serialize config to bson")?;
update_one_by_id(
&db_client().await.deployments,
&id,
mungos::update::Update::FlattenSet(
doc! { "config": config_doc },
),
None,
)
.await
.context("failed to update server on mongo")?;
update.push_simple_log(
"deployment update",
serde_json::to_string_pretty(&config)
.context("failed to serialize config to json")?,
);
update.finalize();
add_update(update).await?;
let deployment: Deployment =
Deployment::get_resource(&id).await?;
Ok(deployment)
resource::update::<Deployment>(&id, config, &user).await
}
}
@@ -401,7 +90,7 @@ impl Resolve<RenameDeployment, User> for State {
RenameDeployment { id, name }: RenameDeployment,
user: User,
) -> anyhow::Result<Update> {
let deployment = Deployment::get_resource_check_permissions(
let deployment = resource::get_check_permissions::<Deployment>(
&id,
&user,
PermissionLevel::Write,
@@ -445,7 +134,7 @@ impl Resolve<RenameDeployment, User> for State {
if container_state != DockerContainerState::NotDeployed {
let server =
Server::get_resource(&deployment.config.server_id).await?;
resource::get::<Server>(&deployment.config.server_id).await?;
let log = periphery_client(&server)?
.request(api::container::RenameContainer {
curr_name: deployment.name.clone(),
+49 -12
View File
@@ -11,7 +11,7 @@ use monitor_client::{
};
use resolver_api::Resolve;
use crate::{helpers::resource::StateResource, state::State};
use crate::{resource, state::State};
#[async_trait]
impl Resolve<UpdateDescription, User> for State {
@@ -31,31 +31,68 @@ impl Resolve<UpdateDescription, User> for State {
))
}
ResourceTarget::Server(id) => {
Server::update_description(&id, &description, &user).await?;
resource::update_description::<Server>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Deployment(id) => {
Deployment::update_description(&id, &description, &user)
.await?;
resource::update_description::<Deployment>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Build(id) => {
Build::update_description(&id, &description, &user).await?;
resource::update_description::<Build>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Repo(id) => {
Repo::update_description(&id, &description, &user).await?;
resource::update_description::<Repo>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Builder(id) => {
Builder::update_description(&id, &description, &user).await?;
resource::update_description::<Builder>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Alerter(id) => {
Alerter::update_description(&id, &description, &user).await?;
resource::update_description::<Alerter>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::Procedure(id) => {
Procedure::update_description(&id, &description, &user)
.await?;
resource::update_description::<Procedure>(
&id,
&description,
&user,
)
.await?;
}
ResourceTarget::ServerTemplate(id) => {
ServerTemplate::update_description(&id, &description, &user)
.await?;
resource::update_description::<ServerTemplate>(
&id,
&description,
&user,
)
.await?;
}
}
Ok(UpdateDescriptionResponse {})
+16 -356
View File
@@ -1,232 +1,26 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::{execute::Execution, write::*},
api::write::*,
entities::{
build::Build,
deployment::Deployment,
monitor_timestamp,
permission::PermissionLevel,
procedure::{PartialProcedureConfig, Procedure},
repo::Repo,
server::Server,
update::Log,
user::User,
Operation,
permission::PermissionLevel, procedure::Procedure, user::User,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_document},
};
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update, update_update},
},
state::{action_states, db_client, State},
};
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateProcedure, User> for State {
#[instrument(name = "CreateProcedure", skip(self, user))]
async fn resolve(
&self,
CreateProcedure { name, mut config }: CreateProcedure,
CreateProcedure { name, config }: CreateProcedure,
user: User,
) -> anyhow::Result<CreateProcedureResponse> {
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
let start_ts = monitor_timestamp();
validate_procedure_config(&mut config, &user, None).await?;
let procedure = Procedure {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
info: Default::default(),
config: config.into(),
};
let procedure_id = db_client()
.await
.procedures
.insert_one(procedure, None)
.await
.context("failed to add procedure to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let procedure = Procedure::get_resource(&procedure_id).await?;
create_permission(&user, &procedure, PermissionLevel::Write)
.await;
let mut update =
make_update(&procedure, Operation::CreateProcedure, &user);
update.push_simple_log(
"create procedure",
format!(
"created procedure\nid: {}\nname: {}",
procedure.id, procedure.name
),
);
update
.push_simple_log("config", format!("{:#?}", procedure.config));
update.finalize();
add_update(update).await?;
Ok(procedure)
resource::create::<Procedure>(&name, config, &user).await
}
}
#[instrument]
async fn validate_procedure_config(
config: &mut PartialProcedureConfig,
user: &User,
id: Option<&str>,
) -> anyhow::Result<()> {
let Some(executions) = &mut config.executions else {
return Ok(());
};
for exec in executions {
match &mut exec.execution {
Execution::None(_) => {}
Execution::RunProcedure(params) => {
let procedure = Procedure::get_resource_check_permissions(
&params.procedure,
user,
PermissionLevel::Execute,
)
.await?;
match id {
Some(id) if procedure.id == id => {
return Err(anyhow!(
"Cannot have self-referential procedure"
))
}
_ => {}
}
params.procedure = procedure.id;
}
Execution::RunBuild(params) => {
let build = Build::get_resource_check_permissions(
&params.build,
user,
PermissionLevel::Execute,
)
.await?;
params.build = build.id;
}
Execution::Deploy(params) => {
let deployment = Deployment::get_resource_check_permissions(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StartContainer(params) => {
let deployment = Deployment::get_resource_check_permissions(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StopContainer(params) => {
let deployment = Deployment::get_resource_check_permissions(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StopAllContainers(params) => {
let server = Server::get_resource_check_permissions(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::RemoveContainer(params) => {
let deployment = Deployment::get_resource_check_permissions(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::CloneRepo(params) => {
let repo = Repo::get_resource_check_permissions(
&params.repo,
user,
PermissionLevel::Execute,
)
.await?;
params.repo = repo.id;
}
Execution::PullRepo(params) => {
let repo = Repo::get_resource_check_permissions(
&params.repo,
user,
PermissionLevel::Execute,
)
.await?;
params.repo = repo.id;
}
Execution::PruneDockerNetworks(params) => {
let server = Server::get_resource_check_permissions(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::PruneDockerImages(params) => {
let server = Server::get_resource_check_permissions(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::PruneDockerContainers(params) => {
let server = Server::get_resource_check_permissions(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
}
}
Ok(())
}
#[async_trait]
impl Resolve<CopyProcedure, User> for State {
#[instrument(name = "CopyProcedure", skip(self, user))]
@@ -235,62 +29,14 @@ impl Resolve<CopyProcedure, User> for State {
CopyProcedure { name, id }: CopyProcedure,
user: User,
) -> anyhow::Result<CopyProcedureResponse> {
let Procedure {
config,
description,
tags,
..
} = Procedure::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let start_ts = monitor_timestamp();
let build = Procedure {
id: Default::default(),
name,
updated_at: start_ts,
description,
tags,
config,
info: Default::default(),
};
let procedure_id = db_client()
.await
.procedures
.insert_one(build, None)
.await
.context("failed to add build to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let procedure = Procedure::get_resource(&procedure_id).await?;
create_permission(&user, &procedure, PermissionLevel::Write)
.await;
let mut update =
make_update(&procedure, Operation::CreateProcedure, &user);
update.push_simple_log(
"create procedure",
format!(
"created procedure\nid: {}\nname: {}",
procedure.id, procedure.name
),
);
update.push_simple_log(
"config",
serde_json::to_string_pretty(&procedure)?,
);
update.finalize();
add_update(update).await?;
Ok(procedure)
let Procedure { config, .. } =
resource::get_check_permissions::<Procedure>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
resource::create::<Procedure>(&name, config.into(), &user).await
}
}
@@ -299,49 +45,10 @@ impl Resolve<UpdateProcedure, User> for State {
#[instrument(name = "UpdateProcedure", skip(self, user))]
async fn resolve(
&self,
UpdateProcedure { id, mut config }: UpdateProcedure,
UpdateProcedure { id, config }: UpdateProcedure,
user: User,
) -> anyhow::Result<UpdateProcedureResponse> {
let procedure = Procedure::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
validate_procedure_config(
&mut config,
&user,
Some(&procedure.id),
)
.await?;
update_one_by_id(
&db_client().await.procedures,
&procedure.id,
mungos::update::Update::FlattenSet(
doc! { "config": to_document(&config)? },
),
None,
)
.await
.context("failed to update procedure on database")?;
let mut update =
make_update(&procedure, Operation::UpdateProcedure, &user);
update.push_simple_log(
"procedure update",
serde_json::to_string_pretty(&config)?,
);
update.finalize();
add_update(update).await?;
let procedure = Procedure::get_resource(&procedure.id).await?;
Ok(procedure)
resource::update::<Procedure>(&id, config, &user).await
}
}
@@ -353,53 +60,6 @@ impl Resolve<DeleteProcedure, User> for State {
DeleteProcedure { id }: DeleteProcedure,
user: User,
) -> anyhow::Result<DeleteProcedureResponse> {
// needs to pull its id from all container procedures
if action_states()
.procedure
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("procedure busy"));
}
let procedure = Procedure::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&procedure, Operation::DeleteProcedure, &user);
update.in_progress();
update.id = add_update(update.clone()).await?;
let res =
delete_one_by_id(&db_client().await.procedures, &id, None)
.await
.context("failed to delete build from database");
delete_all_permissions_on_resource(&procedure).await;
let log = match res {
Ok(_) => Log::simple(
"delete procedure",
format!("deleted procedure {}", procedure.name),
),
Err(e) => Log::error(
"delete procedure",
format!("failed to delete procedure\n{e:#?}"),
),
};
update.logs.push(log);
update.finalize();
update_update(update).await?;
remove_from_recently_viewed(&procedure).await?;
Ok(procedure)
resource::delete::<Procedure>(&id, &user).await
}
}
+16 -311
View File
@@ -1,132 +1,21 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::{execute, write::*},
entities::{
monitor_timestamp,
permission::PermissionLevel,
repo::{PartialRepoConfig, Repo},
server::Server,
to_monitor_name,
update::{Log, ResourceTarget, Update},
user::User,
Operation,
},
api::write::*,
entities::{permission::PermissionLevel, repo::Repo, user::User},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_bson},
};
use periphery_client::api;
use resolver_api::Resolve;
use serror::serialize_error_pretty;
use crate::{
helpers::{
create_permission, periphery_client, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update, update_update},
},
state::{action_states, db_client, State},
};
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialRepoConfig,
user: &User,
) -> anyhow::Result<()> {
match &config.server_id {
Some(server_id) if !server_id.is_empty() => {
let server = Server::get_resource_check_permissions(
server_id,
user,
PermissionLevel::Write,
)
.await
.context("cannot create repo on this server. user must have update permissions on the server.")?;
config.server_id = Some(server.id);
}
_ => {}
}
Ok(())
}
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateRepo, User> for State {
#[instrument(name = "CreateRepo", skip(self, user))]
async fn resolve(
&self,
CreateRepo { name, mut config }: CreateRepo,
CreateRepo { name, config }: CreateRepo,
user: User,
) -> anyhow::Result<Repo> {
let name = to_monitor_name(&name);
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
validate_config(&mut config, &user).await?;
let start_ts = monitor_timestamp();
let repo = Repo {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: Default::default(),
};
let repo_id = db_client()
.await
.repos
.insert_one(repo, None)
.await
.context("failed to add repo to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let repo = Repo::get_resource(&repo_id).await?;
create_permission(&user, &repo, PermissionLevel::Write).await;
let update = Update {
target: ResourceTarget::Repo(repo_id),
operation: Operation::CreateRepo,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create repo",
format!(
"created repo\nid: {}\nname: {}",
repo.id, repo.name
),
),
Log::simple("config", format!("{:#?}", repo.config)),
],
..Default::default()
};
add_update(update).await?;
if !repo.config.repo.is_empty()
&& !repo.config.server_id.is_empty()
{
let _ = self
.resolve(
execute::CloneRepo {
repo: repo.id.clone(),
},
user,
)
.await;
}
Ok(repo)
resource::create::<Repo>(&name, config, &user).await
}
}
@@ -138,59 +27,14 @@ impl Resolve<CopyRepo, User> for State {
CopyRepo { name, id }: CopyRepo,
user: User,
) -> anyhow::Result<Repo> {
let Repo {
config,
description,
tags,
..
} = Repo::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
if !config.server_id.is_empty() {
Server::get_resource_check_permissions(
&config.server_id,
&user,
PermissionLevel::Write,
)
.await
.context("cannot create repo on this server. user must have update permissions on the server.")?;
}
let start_ts = monitor_timestamp();
let repo = Repo {
id: Default::default(),
name,
updated_at: start_ts,
description,
tags,
config,
info: Default::default(),
};
let repo_id = db_client()
.await
.repos
.insert_one(repo, None)
.await
.context("failed to add repo to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let repo = Repo::get_resource(&repo_id).await?;
create_permission(&user, &repo, PermissionLevel::Write).await;
let mut update = make_update(&repo, Operation::CreateRepo, &user);
update.push_simple_log(
"create repo",
format!("created repo\nid: {}\nname: {}", repo.id, repo.name),
);
update.push_simple_log("config", format!("{:#?}", repo.config));
update.finalize();
add_update(update).await?;
Ok(repo)
let Repo { config, .. } =
resource::get_check_permissions::<Repo>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
resource::create::<Repo>(&name, config.into(), &user).await
}
}
@@ -202,76 +46,7 @@ impl Resolve<DeleteRepo, User> for State {
DeleteRepo { id }: DeleteRepo,
user: User,
) -> anyhow::Result<Repo> {
let repo = Repo::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
// get the action state for the repo (or insert default).
let action_state =
action_states().repo.get_or_insert_default(&repo.id).await;
// This will set action state back to default when dropped.
// Will also check to ensure repo not already busy before updating.
let _action_guard =
action_state.update(|state| state.deleting = true)?;
let periphery = if repo.config.server_id.is_empty() {
None
} else {
let server =
Server::get_resource(&repo.config.server_id).await?;
let periphery = periphery_client(&server)?;
Some(periphery)
};
let mut update = make_update(&repo, Operation::DeleteRepo, &user);
update.in_progress();
update.id = add_update(update.clone()).await?;
let res =
delete_one_by_id(&db_client().await.repos, &repo.id, None)
.await
.context("failed to delete repo from database");
delete_all_permissions_on_resource(&repo).await;
let log = match res {
Ok(_) => Log::simple(
"delete repo",
format!("deleted repo {}", repo.name),
),
Err(e) => Log::error(
"delete repo",
format!("failed to delete repo\n{e:#?}"),
),
};
update.logs.push(log);
if let Some(periphery) = periphery {
match periphery
.request(api::git::DeleteRepo {
name: repo.name.clone(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.logs.push(Log::error(
"delete repo on periphery",
serialize_error_pretty(&e),
)),
}
}
update.finalize();
update_update(update).await?;
remove_from_recently_viewed(&repo).await?;
Ok(repo)
resource::delete::<Repo>(&id, &user).await
}
}
@@ -280,79 +55,9 @@ impl Resolve<UpdateRepo, User> for State {
#[instrument(name = "UpdateRepo", skip(self, user))]
async fn resolve(
&self,
UpdateRepo { id, mut config }: UpdateRepo,
UpdateRepo { id, config }: UpdateRepo,
user: User,
) -> anyhow::Result<Repo> {
validate_config(&mut config, &user).await?;
let repo = Repo::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
// get the action state for the repo (or insert default).
let action_state =
action_states().repo.get_or_insert_default(&repo.id).await;
// This will set action state back to default when dropped.
// Will also check to ensure repo not already busy before updating.
let _action_guard =
action_state.update(|state| state.updating = true)?;
update_one_by_id(
&db_client().await.repos,
&repo.id,
mungos::update::Update::FlattenSet(
doc! { "config": to_bson(&config)? },
),
None,
)
.await
.context("failed to update repo on database")?;
let mut update = make_update(&repo, Operation::UpdateRepo, &user);
update.in_progress();
update.push_simple_log(
"repo update",
serde_json::to_string_pretty(&config).unwrap(),
);
update.id = add_update(update.clone()).await?;
if let Some(new_server_id) = config.server_id {
if !repo.config.server_id.is_empty()
&& new_server_id != repo.config.server_id
{
let old_server: anyhow::Result<Server> =
Server::get_resource(&repo.config.server_id).await;
let periphery =
old_server.and_then(|server| periphery_client(&server));
match periphery {
Ok(periphery) => match periphery
.request(api::git::DeleteRepo { name: repo.name })
.await
{
Ok(mut log) => {
log.stage = String::from("cleanup previous server");
update.logs.push(log);
}
Err(e) => update.push_error_log(
"cleanup previous server",
format!("failed to cleanup previous server | {e:#?}"),
),
},
Err(e) => update.push_error_log(
"cleanup previous server",
format!("failed to cleanup previous server | {e:#?}"),
),
}
}
}
update.finalize();
update_update(update).await?;
Repo::get_resource(&repo.id).await
resource::update::<Repo>(&id, config, &user).await
}
}
+12 -200
View File
@@ -1,7 +1,4 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_timing_util::unix_timestamp_ms;
use anyhow::Context;
use async_trait::async_trait;
use monitor_client::{
api::write::*,
@@ -9,27 +6,23 @@ use monitor_client::{
monitor_timestamp,
permission::PermissionLevel,
server::Server,
update::{Log, ResourceTarget, Update, UpdateStatus},
update::{Update, UpdateStatus},
user::User,
Operation,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_bson},
};
use mungos::{by_id::update_one_by_id, mongodb::bson::doc};
use periphery_client::api;
use resolver_api::Resolve;
use serror::serialize_error_pretty;
use crate::{
helpers::{
create_permission, periphery_client, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
periphery_client,
update::{add_update, make_update, update_update},
},
monitor::update_cache_for_server,
state::{action_states, db_client, server_status_cache, State},
resource,
state::{db_client, State},
};
#[async_trait]
@@ -40,61 +33,7 @@ impl Resolve<CreateServer, User> for State {
CreateServer { name, config }: CreateServer,
user: User,
) -> anyhow::Result<Server> {
if !user.admin && !user.create_server_permissions {
return Err(anyhow!(
"user does not have create server permissions"
));
}
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
let start_ts = monitor_timestamp();
let server = Server {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: (),
};
let server_id = db_client()
.await
.servers
.insert_one(&server, None)
.await
.context("failed to add server to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let server = Server::get_resource(&server_id).await?;
create_permission(&user, &server, PermissionLevel::Write).await;
let update = Update {
target: ResourceTarget::Server(server_id),
operation: Operation::CreateServer,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create server",
format!(
"created server\nid: {}\nname: {}",
server.id, server.name
),
),
Log::simple("config", format!("{:#?}", server.config)),
],
..Default::default()
};
add_update(update).await?;
update_cache_for_server(&server).await;
Ok(server)
resource::create::<Server>(&name, config, &user).await
}
}
@@ -106,91 +45,7 @@ impl Resolve<DeleteServer, User> for State {
DeleteServer { id }: DeleteServer,
user: User,
) -> anyhow::Result<Server> {
if action_states()
.server
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("server busy"));
}
let server = Server::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
db_client()
.await
.builders
.update_many(
doc! { "config.params.server_id": &id },
doc! { "$set": { "config.params.server_id": "" } },
None,
)
.await
.context("failed to detach server from builders")?;
db_client()
.await
.deployments
.update_many(
doc! { "config.server_id": &id },
doc! { "$set": { "config.server_id": "" } },
None,
)
.await
.context("failed to detach server from deployments")?;
db_client()
.await
.repos
.update_many(
doc! { "config.server_id": &id },
doc! { "$set": { "config.server_id": "" } },
None,
)
.await
.context("failed to detach server from repos")?;
db_client()
.await
.alerts
.update_many(
doc! { "target.type": "Server", "target.id": &id },
doc! { "$set": {
"resolved": true,
"resolved_ts": unix_timestamp_ms() as i64
} },
None,
)
.await
.context("failed to detach server from repos")?;
delete_one_by_id(&db_client().await.servers, &id, None)
.await
.context("failed to delete server from mongo")?;
delete_all_permissions_on_resource(&server).await;
let mut update =
make_update(&server, Operation::DeleteServer, &user);
update.push_simple_log(
"delete server",
format!("deleted server {}", server.name),
);
update.finalize();
add_update(update).await?;
server_status_cache().remove(&id).await;
remove_from_recently_viewed(&server).await?;
Ok(server)
resource::delete::<Server>(&id, &user).await
}
}
@@ -202,50 +57,7 @@ impl Resolve<UpdateServer, User> for State {
UpdateServer { id, config }: UpdateServer,
user: User,
) -> anyhow::Result<Server> {
if action_states()
.server
.get(&id)
.await
.unwrap_or_default()
.busy()?
{
return Err(anyhow!("server busy"));
}
let server = Server::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update =
make_update(&server, Operation::UpdateServer, &user);
update_one_by_id(
&db_client().await.servers,
&id,
mungos::update::Update::FlattenSet(
doc! { "config": to_bson(&config)? },
),
None,
)
.await
.context("failed to update server on mongo")?;
update.push_simple_log(
"server update",
serde_json::to_string_pretty(&config)?,
);
let new_server = Server::get_resource(&id).await?;
update_cache_for_server(&new_server).await;
update.finalize();
add_update(update).await?;
Ok(new_server)
resource::update::<Server>(&id, config, &user).await
}
}
@@ -257,7 +69,7 @@ impl Resolve<RenameServer, User> for State {
RenameServer { id, name }: RenameServer,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&id,
&user,
PermissionLevel::Write,
@@ -287,7 +99,7 @@ impl Resolve<CreateNetwork, User> for State {
CreateNetwork { server, name }: CreateNetwork,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Write,
@@ -325,7 +137,7 @@ impl Resolve<DeleteNetwork, User> for State {
DeleteNetwork { server, name }: DeleteNetwork,
user: User,
) -> anyhow::Result<Update> {
let server = Server::get_resource_check_permissions(
let server = resource::get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Write,
+14 -197
View File
@@ -1,6 +1,3 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use axum::async_trait;
use monitor_client::{
api::write::{
@@ -8,24 +5,13 @@ use monitor_client::{
UpdateServerTemplate,
},
entities::{
monitor_timestamp, permission::PermissionLevel,
server_template::ServerTemplate, user::User, Operation,
permission::PermissionLevel, server_template::ServerTemplate,
user::User,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, oid::ObjectId, to_document},
};
use resolver_api::Resolve;
use crate::{
helpers::{
create_permission, remove_from_recently_viewed,
resource::{delete_all_permissions_on_resource, StateResource},
update::{add_update, make_update},
},
state::{db_client, State},
};
use crate::{resource, state::State};
#[async_trait]
impl Resolve<CreateServerTemplate, User> for State {
@@ -34,60 +20,7 @@ impl Resolve<CreateServerTemplate, User> for State {
CreateServerTemplate { name, config }: CreateServerTemplate,
user: User,
) -> anyhow::Result<ServerTemplate> {
if !user.admin {
return Err(anyhow!("only admins can create server templates"));
}
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
let server_template = ServerTemplate {
id: Default::default(),
name,
updated_at: monitor_timestamp(),
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: (),
};
let server_template_id = db_client()
.await
.server_templates
.insert_one(server_template, None)
.await
.context("failed to add server_template to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let server_template =
ServerTemplate::get_resource(&server_template_id).await?;
create_permission(
&user,
&server_template,
PermissionLevel::Write,
)
.await;
let mut update = make_update(
&server_template,
Operation::CreateServerTemplate,
&user,
);
update.push_simple_log(
"create server template",
format!(
"created server template\nid: {}\nname: {}",
server_template.id, server_template.name
),
);
update.push_simple_log(
"config",
format!("{:#?}", server_template.config),
);
update.finalize();
add_update(update).await?;
Ok(server_template)
resource::create::<ServerTemplate>(&name, config, &user).await
}
}
@@ -98,64 +31,15 @@ impl Resolve<CopyServerTemplate, User> for State {
CopyServerTemplate { name, id }: CopyServerTemplate,
user: User,
) -> anyhow::Result<ServerTemplate> {
let ServerTemplate {
config,
description,
..
} = ServerTemplate::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let server_template = ServerTemplate {
id: Default::default(),
name,
updated_at: monitor_timestamp(),
description,
tags: Default::default(),
config,
info: (),
};
let server_template_id = db_client()
let ServerTemplate { config, .. } =
resource::get_check_permissions::<ServerTemplate>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
resource::create::<ServerTemplate>(&name, config.into(), &user)
.await
.server_templates
.insert_one(server_template, None)
.await
.context("failed to add server_template to db")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let server_template =
ServerTemplate::get_resource(&server_template_id).await?;
create_permission(
&user,
&server_template,
PermissionLevel::Write,
)
.await;
let mut update = make_update(
&server_template,
Operation::CreateServerTemplate,
&user,
);
update.push_simple_log(
"create server template",
format!(
"created server template\nid: {}\nname: {}",
server_template.id, server_template.name
),
);
update.push_simple_log(
"config",
format!("{:#?}", server_template.config),
);
update.finalize();
add_update(update).await?;
Ok(server_template)
}
}
@@ -166,37 +50,7 @@ impl Resolve<DeleteServerTemplate, User> for State {
DeleteServerTemplate { id }: DeleteServerTemplate,
user: User,
) -> anyhow::Result<ServerTemplate> {
let server_template =
ServerTemplate::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
delete_one_by_id(&db_client().await.server_templates, &id, None)
.await
.context("failed to delete server templates from database")?;
delete_all_permissions_on_resource(&server_template).await;
let mut update = make_update(
&server_template,
Operation::DeleteServerTemplate,
&user,
);
update.push_simple_log(
"delete server template",
format!("deleted server template {}", server_template.name),
);
update.finalize();
add_update(update).await?;
remove_from_recently_viewed(&server_template).await?;
Ok(server_template)
resource::delete::<ServerTemplate>(&id, &user).await
}
}
@@ -207,43 +61,6 @@ impl Resolve<UpdateServerTemplate, User> for State {
UpdateServerTemplate { id, config }: UpdateServerTemplate,
user: User,
) -> anyhow::Result<ServerTemplate> {
let server_template =
ServerTemplate::get_resource_check_permissions(
&id,
&user,
PermissionLevel::Write,
)
.await?;
let mut update = make_update(
&server_template,
Operation::UpdateServerTemplate,
&user,
);
update.push_simple_log(
"server template update",
serde_json::to_string_pretty(&config)
.context("failed to serialize config update")?,
);
let config = server_template.config.merge_partial(config);
let config = to_document(&config)
.context("failed to serialize update to bson document")?;
update_one_by_id(
&db_client().await.server_templates,
&id,
mungos::update::Update::FlattenSet(doc! { "config": config }),
None,
)
.await?;
let server_template = ServerTemplate::get_resource(&id).await?;
update.finalize();
add_update(update).await?;
Ok(server_template)
resource::update::<ServerTemplate>(&id, config, &user).await
}
}
+26 -27
View File
@@ -22,10 +22,8 @@ use mungos::{
use resolver_api::Resolve;
use crate::{
helpers::{
query::{get_tag, get_tag_check_owner},
resource::StateResource,
},
helpers::query::{get_tag, get_tag_check_owner},
resource,
state::{db_client, State},
};
@@ -99,13 +97,14 @@ impl Resolve<DeleteTag, User> for State {
let tag = get_tag_check_owner(&id, &user).await?;
tokio::try_join!(
Server::remove_tag_from_resources(&id,),
Deployment::remove_tag_from_resources(&id,),
Build::remove_tag_from_resources(&id,),
Repo::remove_tag_from_resources(&id,),
Builder::remove_tag_from_resources(&id,),
Alerter::remove_tag_from_resources(&id,),
Procedure::remove_tag_from_resources(&id,),
resource::remove_tag_from_all::<Server>(&id),
resource::remove_tag_from_all::<Deployment>(&id),
resource::remove_tag_from_all::<Build>(&id),
resource::remove_tag_from_all::<Repo>(&id),
resource::remove_tag_from_all::<Builder>(&id),
resource::remove_tag_from_all::<Alerter>(&id),
resource::remove_tag_from_all::<Procedure>(&id),
resource::remove_tag_from_all::<ServerTemplate>(&id),
)?;
delete_one_by_id(&db_client().await.tags, &id, None).await?;
@@ -125,76 +124,76 @@ impl Resolve<UpdateTagsOnResource, User> for State {
match target {
ResourceTarget::System(_) => return Err(anyhow!("")),
ResourceTarget::Build(id) => {
Build::get_resource_check_permissions(
resource::get_check_permissions::<Build>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Build::update_tags_on_resource(&id, tags, user).await?;
resource::update_tags::<Build>(&id, tags, user).await?;
}
ResourceTarget::Builder(id) => {
Builder::get_resource_check_permissions(
resource::get_check_permissions::<Builder>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Builder::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Builder>(&id, tags, user).await?
}
ResourceTarget::Deployment(id) => {
Deployment::get_resource_check_permissions(
resource::get_check_permissions::<Deployment>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Deployment::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Deployment>(&id, tags, user).await?
}
ResourceTarget::Server(id) => {
Server::get_resource_check_permissions(
resource::get_check_permissions::<Server>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Server::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Server>(&id, tags, user).await?
}
ResourceTarget::Repo(id) => {
Repo::get_resource_check_permissions(
resource::get_check_permissions::<Repo>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Repo::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Repo>(&id, tags, user).await?
}
ResourceTarget::Alerter(id) => {
Alerter::get_resource_check_permissions(
resource::get_check_permissions::<Alerter>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Alerter::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Alerter>(&id, tags, user).await?
}
ResourceTarget::Procedure(id) => {
Procedure::get_resource_check_permissions(
resource::get_check_permissions::<Procedure>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
Procedure::update_tags_on_resource(&id, tags, user).await?
resource::update_tags::<Procedure>(&id, tags, user).await?
}
ResourceTarget::ServerTemplate(id) => {
ServerTemplate::get_resource_check_permissions(
resource::get_check_permissions::<ServerTemplate>(
&id,
&user,
PermissionLevel::Write,
)
.await?;
ServerTemplate::update_tags_on_resource(&id, tags, user)
resource::update_tags::<ServerTemplate>(&id, tags, user)
.await?
}
};
+15 -12
View File
@@ -42,7 +42,7 @@ async fn create_ec2_client(region: String) -> Client {
&core_config().aws.secret_access_key,
);
let region = Region::new(region);
let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
let config = aws_config::defaults(BehaviorVersion::v2024_03_28())
.region(region)
.load()
.await;
@@ -64,6 +64,7 @@ pub async fn launch_ec2_instance(
key_pair_name,
assign_public_ip,
use_public_ip,
user_data,
port: _,
} = config;
let instance_type = handle_unknown_instance_type(
@@ -90,23 +91,25 @@ pub async fn launch_ec2_instance(
.build(),
)
.min_count(1)
.max_count(1);
.max_count(1)
.user_data(user_data);
for volume in volumes {
let mut ebs = EbsBlockDevice::builder()
let ebs = EbsBlockDevice::builder()
.volume_size(volume.size_gb)
.set_iops(volume.iops)
.set_throughput(volume.throughput);
if let Some(volume_type) = &volume.volume_type {
ebs = ebs.volume_type(
VolumeType::from_str(volume_type)
.volume_type(
VolumeType::from_str(volume.volume_type.as_ref())
.context("invalid volume type")?,
);
}
)
.set_iops((volume.iops != 0).then_some(volume.iops))
.set_throughput(
(volume.throughput != 0).then_some(volume.throughput),
)
.build();
req = req.block_device_mappings(
BlockDeviceMapping::builder()
.set_device_name(volume.device_name.clone().into())
.set_ebs(ebs.build().into())
.set_device_name(volume.device_name.into())
.set_ebs(ebs.into())
.build(),
)
}
+8 -9
View File
@@ -20,9 +20,10 @@ pub mod channel;
pub mod procedure;
pub mod prune;
pub mod query;
pub mod resource;
pub mod update;
// pub mod resource;
pub fn empty_or_only_spaces(word: &str) -> bool {
if word.is_empty() {
return true;
@@ -40,15 +41,13 @@ pub fn random_duration(min_ms: u64, max_ms: u64) -> Duration {
}
#[instrument]
pub async fn remove_from_recently_viewed<T>(
resource: T,
) -> anyhow::Result<()>
pub async fn remove_from_recently_viewed<T>(resource: T)
where
T: Into<ResourceTarget> + std::fmt::Debug,
{
let resource: ResourceTarget = resource.into();
let (ty, id) = resource.extract_variant_id();
db_client()
if let Err(e) = db_client()
.await
.users
.update_many(
@@ -64,10 +63,10 @@ where
None,
)
.await
.context(
"failed to remove resource from users recently viewed",
)?;
Ok(())
.context("failed to remove resource from users recently viewed")
{
warn!("{e:#}");
}
}
//
+67 -7
View File
@@ -1,10 +1,12 @@
use std::str::FromStr;
use std::{collections::HashSet, str::FromStr};
use anyhow::{anyhow, Context};
use monitor_client::entities::{
deployment::{Deployment, DockerContainerState},
permission::PermissionLevel,
server::{Server, ServerStatus},
tag::Tag,
update::ResourceTargetVariant,
user::{admin_service_user, User},
};
use mungos::{
@@ -13,9 +15,7 @@ use mungos::{
mongodb::bson::{doc, oid::ObjectId, Document},
};
use crate::state::db_client;
use super::resource::StateResource;
use crate::{resource, state::db_client};
#[instrument(level = "debug")]
pub async fn get_user(user_id: &str) -> anyhow::Result<User> {
@@ -32,7 +32,7 @@ pub async fn get_user(user_id: &str) -> anyhow::Result<User> {
pub async fn get_server_with_status(
server_id_or_name: &str,
) -> anyhow::Result<(Server, ServerStatus)> {
let server = Server::get_resource(server_id_or_name).await?;
let server = resource::get::<Server>(server_id_or_name).await?;
if !server.config.enabled {
return Ok((server, ServerStatus::Disabled));
}
@@ -72,8 +72,6 @@ pub async fn get_deployment_state(
Ok(state)
}
// TAG
#[instrument(level = "debug")]
pub async fn get_tag(id_or_name: &str) -> anyhow::Result<Tag> {
let query = match ObjectId::from_str(id_or_name) {
@@ -120,6 +118,9 @@ pub async fn get_user_user_group_ids(
Ok(res)
}
/// Returns Vec of all queries on permissions that match against the user
/// or any user groups that the user is a part of.
/// Result used with Mongodb '$or'.
#[instrument(level = "debug")]
pub async fn user_target_query(
user_id: &str,
@@ -138,3 +139,62 @@ pub async fn user_target_query(
user_target_query.extend(user_groups);
Ok(user_target_query)
}
#[instrument(level = "debug")]
pub async fn get_user_permission_on_resource(
user_id: &str,
resource_variant: ResourceTargetVariant,
resource_id: &str,
) -> anyhow::Result<PermissionLevel> {
let permission = find_collect(
&db_client().await.permissions,
doc! {
"$or": user_target_query(user_id).await?,
"resource_target.type": resource_variant.as_ref(),
"resource_target.id": resource_id
},
None,
)
.await
.context("failed to query db for permissions")?
.into_iter()
// get the max permission user has between personal / any user groups
.fold(PermissionLevel::None, |level, permission| {
if permission.level > level {
permission.level
} else {
level
}
});
Ok(permission)
}
#[instrument(level = "debug")]
pub async fn get_resource_ids_for_non_admin(
user_id: &str,
resource_type: ResourceTargetVariant,
) -> anyhow::Result<Vec<String>> {
let permissions = find_collect(
&db_client().await.permissions,
doc! {
"$or": user_target_query(user_id).await?,
"resource_target.type": resource_type.as_ref(),
"level": { "$in": ["Read", "Execute", "Write"] }
},
None,
)
.await
.context("failed to query permissions on db")?
.into_iter()
.map(|p| p.resource_target.extract_variant_id().1.to_string())
// collect into hashset first to remove any duplicates
.collect::<HashSet<_>>();
Ok(permissions.into_iter().collect())
}
pub fn id_or_name_filter(id_or_name: &str) -> Document {
match ObjectId::from_str(id_or_name) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": id_or_name },
}
}
-737
View File
@@ -1,737 +0,0 @@
use std::{collections::HashSet, str::FromStr};
use anyhow::{anyhow, Context};
use futures::future::join_all;
use monitor_client::{
api::write::CreateTag,
entities::{
alerter::{
Alerter, AlerterConfig, AlerterConfigVariant, AlerterInfo,
AlerterListItem, AlerterListItemInfo, AlerterQuerySpecifics,
},
build::{
Build, BuildConfig, BuildInfo, BuildListItem,
BuildListItemInfo, BuildQuerySpecifics,
},
builder::{
Builder, BuilderConfig, BuilderConfigVariant, BuilderListItem,
BuilderListItemInfo, BuilderQuerySpecifics,
},
deployment::{
Deployment, DeploymentConfig, DeploymentImage,
DeploymentListItem, DeploymentListItemInfo,
DeploymentQuerySpecifics,
},
permission::PermissionLevel,
procedure::{
Procedure, ProcedureConfig, ProcedureListItem,
ProcedureListItemInfo, ProcedureQuerySpecifics,
},
repo::{
Repo, RepoConfig, RepoInfo, RepoListItem, RepoListItemInfo,
RepoQuerySpecifics,
},
resource::{AddFilters, Resource, ResourceQuery},
server::{
Server, ServerConfig, ServerListItem, ServerListItemInfo,
ServerQuerySpecifics,
},
server_template::{
ServerTemplate, ServerTemplateConfig,
ServerTemplateConfigVariant, ServerTemplateListItem,
ServerTemplateListItemInfo, ServerTemplateQuerySpecifics,
},
update::{ResourceTarget, ResourceTargetVariant},
user::User,
},
};
use mungos::{
find::find_collect,
mongodb::{
bson::{doc, oid::ObjectId, Document},
Collection,
},
};
use resolver_api::Resolve;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
helpers::query::user_target_query,
state::State,
state::{db_client, deployment_status_cache, server_status_cache},
};
use super::query::get_tag;
pub trait StateResource {
type ListItem: Serialize + Send;
type Config: Send
+ Sync
+ Unpin
+ Serialize
+ DeserializeOwned
+ 'static;
type Info: Send
+ Sync
+ Unpin
+ Default
+ Serialize
+ DeserializeOwned
+ 'static;
type QuerySpecifics: AddFilters + Default + std::fmt::Debug;
fn name() -> &'static str;
fn resource_target_variant() -> ResourceTargetVariant;
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>>;
async fn to_list_item(
resource: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem>;
async fn get_resource(
id_or_name: &str,
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
let filter = match ObjectId::from_str(id_or_name) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": id_or_name },
};
Self::coll()
.await
.find_one(filter, None)
.await
.context("failed to query db for resource")?
.with_context(|| {
format!(
"did not find any {} matching {id_or_name}",
Self::name()
)
})
}
async fn get_resource_check_permissions(
id_or_name: &str,
user: &User,
permission_level: PermissionLevel,
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
let resource = Self::get_resource(id_or_name).await?;
if user.admin {
return Ok(resource);
}
let permissions =
Self::get_user_permission_on_resource(&user.id, &resource.id)
.await?;
if permissions >= permission_level {
Ok(resource)
} else {
Err(anyhow!(
"user does not have required permissions on this {}",
Self::name()
))
}
}
async fn get_user_permission_on_resource(
user_id: &str,
resource_id: &str,
) -> anyhow::Result<PermissionLevel> {
get_user_permission_on_resource(
user_id,
Self::resource_target_variant(),
resource_id,
)
.await
}
async fn get_resource_ids_for_non_admin(
user_id: &str,
) -> anyhow::Result<Vec<String>> {
get_resource_ids_for_non_admin(
user_id,
Self::resource_target_variant(),
)
.await
}
async fn list_resource_list_items_for_user(
mut query: ResourceQuery<Self::QuerySpecifics>,
user: &User,
) -> anyhow::Result<Vec<Self::ListItem>> {
validate_resource_query_tags(&mut query).await;
let mut filters = Document::new();
query.add_filters(&mut filters);
Self::query_resource_list_items_for_user(filters, user).await
}
async fn query_resource_list_items_for_user(
filters: Document,
user: &User,
) -> anyhow::Result<Vec<Self::ListItem>> {
let list = Self::query_resources_for_user(filters, user)
.await?
.into_iter()
.map(|resource| Self::to_list_item(resource));
let list = join_all(list)
.await
.into_iter()
.collect::<anyhow::Result<Vec<_>>>()
.context(format!(
"failed to convert {} list item",
Self::name()
))?;
Ok(list)
}
async fn list_resources_for_user(
mut query: ResourceQuery<Self::QuerySpecifics>,
user: &User,
) -> anyhow::Result<Vec<Resource<Self::Config, Self::Info>>> {
validate_resource_query_tags(&mut query).await;
let mut filters = Document::new();
query.add_filters(&mut filters);
Self::query_resources_for_user(filters, user).await
}
async fn query_resources_for_user(
mut filters: Document,
user: &User,
) -> anyhow::Result<Vec<Resource<Self::Config, Self::Info>>> {
if !user.admin {
let ids = Self::get_resource_ids_for_non_admin(&user.id)
.await?
.into_iter()
.flat_map(|id| ObjectId::from_str(&id))
.collect::<Vec<_>>();
filters.insert("_id", doc! { "$in": ids });
}
find_collect(Self::coll().await, filters, None)
.await
.with_context(|| {
format!("failed to pull {}s from mongo", Self::name())
})
}
async fn update_description(
id_or_name: &str,
description: &str,
user: &User,
) -> anyhow::Result<()> {
Self::get_resource_check_permissions(
id_or_name,
user,
PermissionLevel::Write,
)
.await?;
let filter = match ObjectId::from_str(id_or_name) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": id_or_name },
};
Self::coll()
.await
.update_one(
filter,
doc! { "$set": { "description": description } },
None,
)
.await?;
Ok(())
}
async fn update_tags_on_resource(
id_or_name: &str,
tags: Vec<String>,
user: User,
) -> anyhow::Result<()> {
let futures = tags.iter().map(|tag| async {
match get_tag(tag).await {
Ok(tag) => Ok(tag.id),
Err(_) => State
.resolve(
CreateTag {
name: tag.to_string(),
},
user.clone(),
)
.await
.map(|tag| tag.id),
}
});
let tags = join_all(futures)
.await
.into_iter()
.flatten()
.collect::<Vec<_>>();
Self::coll()
.await
.update_one(
id_or_name_filter(id_or_name),
doc! { "$set": { "tags": tags } },
None,
)
.await?;
Ok(())
}
async fn remove_tag_from_resources(
tag_id: &str,
) -> anyhow::Result<()> {
Self::coll()
.await
.update_many(
doc! {},
doc! { "$pull": { "tags": tag_id } },
None,
)
.await
.context("failed to remove tag from resources")?;
Ok(())
}
}
fn id_or_name_filter(id_or_name: &str) -> Document {
match ObjectId::from_str(id_or_name) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": id_or_name },
}
}
impl StateResource for Server {
type ListItem = ServerListItem;
type Config = ServerConfig;
type Info = ();
type QuerySpecifics = ServerQuerySpecifics;
fn name() -> &'static str {
"server"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Server
}
async fn coll() -> &'static Collection<Server> {
&db_client().await.servers
}
async fn to_list_item(
server: Server,
) -> anyhow::Result<ServerListItem> {
let status = server_status_cache().get(&server.id).await;
Ok(ServerListItem {
name: server.name,
created_at: ObjectId::from_str(&server.id)?
.timestamp()
.timestamp_millis(),
id: server.id,
tags: server.tags,
resource_type: ResourceTargetVariant::Server,
info: ServerListItemInfo {
status: status.map(|s| s.status).unwrap_or_default(),
region: server.config.region,
send_unreachable_alerts: server
.config
.send_unreachable_alerts,
send_cpu_alerts: server.config.send_cpu_alerts,
send_mem_alerts: server.config.send_mem_alerts,
send_disk_alerts: server.config.send_disk_alerts,
},
})
}
}
impl StateResource for Deployment {
type ListItem = DeploymentListItem;
type Config = DeploymentConfig;
type Info = ();
type QuerySpecifics = DeploymentQuerySpecifics;
fn name() -> &'static str {
"deployment"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Deployment
}
async fn coll() -> &'static Collection<Deployment> {
&db_client().await.deployments
}
async fn to_list_item(
deployment: Deployment,
) -> anyhow::Result<DeploymentListItem> {
let status = deployment_status_cache().get(&deployment.id).await;
let (image, build_id) = match deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let build = Build::get_resource(&build_id).await?;
let version = if version.is_none() {
build.config.version.to_string()
} else {
version.to_string()
};
(format!("{}:{version}", build.name), Some(build_id))
}
DeploymentImage::Image { image } => (image, None),
};
Ok(DeploymentListItem {
name: deployment.name,
created_at: ObjectId::from_str(&deployment.id)?
.timestamp()
.timestamp_millis(),
id: deployment.id,
tags: deployment.tags,
resource_type: ResourceTargetVariant::Deployment,
info: DeploymentListItemInfo {
state: status
.as_ref()
.map(|s| s.curr.state)
.unwrap_or_default(),
status: status.as_ref().and_then(|s| {
s.curr.container.as_ref().and_then(|c| c.status.to_owned())
}),
image,
server_id: deployment.config.server_id,
build_id,
},
})
}
}
impl StateResource for Build {
type ListItem = BuildListItem;
type Config = BuildConfig;
type Info = BuildInfo;
type QuerySpecifics = BuildQuerySpecifics;
fn name() -> &'static str {
"build"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Build
}
async fn coll() -> &'static Collection<Build> {
&db_client().await.builds
}
async fn to_list_item(
build: Build,
) -> anyhow::Result<BuildListItem> {
Ok(BuildListItem {
name: build.name,
created_at: ObjectId::from_str(&build.id)?
.timestamp()
.timestamp_millis(),
id: build.id,
tags: build.tags,
resource_type: ResourceTargetVariant::Build,
info: BuildListItemInfo {
last_built_at: build.info.last_built_at,
version: build.config.version,
repo: build.config.repo,
branch: build.config.branch,
},
})
}
}
impl StateResource for Repo {
type ListItem = RepoListItem;
type Config = RepoConfig;
type Info = RepoInfo;
type QuerySpecifics = RepoQuerySpecifics;
fn name() -> &'static str {
"repo"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Repo
}
async fn coll() -> &'static Collection<Repo> {
&db_client().await.repos
}
async fn to_list_item(repo: Repo) -> anyhow::Result<RepoListItem> {
Ok(RepoListItem {
name: repo.name,
created_at: ObjectId::from_str(&repo.id)?
.timestamp()
.timestamp_millis(),
id: repo.id,
tags: repo.tags,
resource_type: ResourceTargetVariant::Repo,
info: RepoListItemInfo {
last_pulled_at: repo.info.last_pulled_at,
repo: repo.config.repo,
branch: repo.config.branch,
},
})
}
}
impl StateResource for Builder {
type ListItem = BuilderListItem;
type Config = BuilderConfig;
type Info = ();
type QuerySpecifics = BuilderQuerySpecifics;
fn name() -> &'static str {
"builder"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Builder
}
async fn coll() -> &'static Collection<Builder> {
&db_client().await.builders
}
async fn to_list_item(
builder: Builder,
) -> anyhow::Result<BuilderListItem> {
let (builder_type, instance_type) = match builder.config {
BuilderConfig::Server(config) => (
BuilderConfigVariant::Server.to_string(),
Some(config.server_id),
),
BuilderConfig::Aws(config) => (
BuilderConfigVariant::Aws.to_string(),
Some(config.instance_type),
),
};
Ok(BuilderListItem {
name: builder.name,
created_at: ObjectId::from_str(&builder.id)?
.timestamp()
.timestamp_millis(),
id: builder.id,
tags: builder.tags,
resource_type: ResourceTargetVariant::Builder,
info: BuilderListItemInfo {
builder_type,
instance_type,
},
})
}
}
impl StateResource for Alerter {
type ListItem = AlerterListItem;
type Config = AlerterConfig;
type Info = AlerterInfo;
type QuerySpecifics = AlerterQuerySpecifics;
fn name() -> &'static str {
"alerter"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Alerter
}
async fn coll() -> &'static Collection<Alerter> {
&db_client().await.alerters
}
async fn to_list_item(
alerter: Alerter,
) -> anyhow::Result<AlerterListItem> {
let (alerter_type, enabled) = match alerter.config {
AlerterConfig::Custom(config) => {
(AlerterConfigVariant::Custom.to_string(), config.enabled)
}
AlerterConfig::Slack(config) => {
(AlerterConfigVariant::Slack.to_string(), config.enabled)
}
};
Ok(AlerterListItem {
name: alerter.name,
created_at: ObjectId::from_str(&alerter.id)?
.timestamp()
.timestamp_millis(),
id: alerter.id,
tags: alerter.tags,
resource_type: ResourceTargetVariant::Alerter,
info: AlerterListItemInfo {
alerter_type: alerter_type.to_string(),
is_default: alerter.info.is_default,
enabled,
},
})
}
}
impl StateResource for Procedure {
type ListItem = ProcedureListItem;
type Config = ProcedureConfig;
type Info = ();
type QuerySpecifics = ProcedureQuerySpecifics;
fn name() -> &'static str {
"procedure"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Procedure
}
async fn coll() -> &'static Collection<Procedure> {
&db_client().await.procedures
}
async fn to_list_item(
procedure: Procedure,
) -> anyhow::Result<ProcedureListItem> {
Ok(ProcedureListItem {
name: procedure.name,
created_at: ObjectId::from_str(&procedure.id)?
.timestamp()
.timestamp_millis(),
id: procedure.id,
tags: procedure.tags,
resource_type: ResourceTargetVariant::Procedure,
info: ProcedureListItemInfo {
procedure_type: procedure.config.procedure_type,
},
})
}
}
impl StateResource for ServerTemplate {
type ListItem = ServerTemplateListItem;
type Config = ServerTemplateConfig;
type Info = ();
type QuerySpecifics = ServerTemplateQuerySpecifics;
fn name() -> &'static str {
"server_template"
}
fn resource_target_variant() -> ResourceTargetVariant {
ResourceTargetVariant::Alerter
}
async fn coll() -> &'static Collection<ServerTemplate> {
&db_client().await.server_templates
}
async fn to_list_item(
server_template: ServerTemplate,
) -> anyhow::Result<ServerTemplateListItem> {
let (template_type, instance_type) = match server_template.config
{
ServerTemplateConfig::Aws(config) => (
ServerTemplateConfigVariant::Aws.to_string(),
Some(config.instance_type),
),
};
Ok(ServerTemplateListItem {
name: server_template.name,
created_at: ObjectId::from_str(&server_template.id)?
.timestamp()
.timestamp_millis(),
id: server_template.id,
tags: server_template.tags,
resource_type: ResourceTargetVariant::ServerTemplate,
info: ServerTemplateListItemInfo {
provider: template_type.to_string(),
instance_type,
},
})
}
}
#[instrument(level = "debug")]
pub async fn get_user_permission_on_resource(
user_id: &str,
resource_variant: ResourceTargetVariant,
resource_id: &str,
) -> anyhow::Result<PermissionLevel> {
let permission = find_collect(
&db_client().await.permissions,
doc! {
"$or": user_target_query(user_id).await?,
"resource_target.type": resource_variant.as_ref(),
"resource_target.id": resource_id
},
None,
)
.await
.context("failed to query db for permissions")?
.into_iter()
// get the max permission user has between personal / any user groups
.fold(PermissionLevel::None, |level, permission| {
if permission.level > level {
permission.level
} else {
level
}
});
Ok(permission)
}
#[instrument]
pub async fn delete_all_permissions_on_resource<T>(target: T)
where
T: Into<ResourceTarget> + std::fmt::Debug,
{
let target: ResourceTarget = target.into();
let (variant, id) = target.extract_variant_id();
if let Err(e) = db_client()
.await
.permissions
.delete_many(
doc! {
"resource_target.type": variant.as_ref(),
"resource_target.id": &id
},
None,
)
.await
{
warn!("failed to delete_many permissions matching target {target:?} | {e:#}");
}
}
#[instrument(level = "debug")]
pub async fn get_resource_ids_for_non_admin(
user_id: &str,
resource_type: ResourceTargetVariant,
) -> anyhow::Result<Vec<String>> {
let permissions = find_collect(
&db_client().await.permissions,
doc! {
"$or": user_target_query(user_id).await?,
"resource_target.type": resource_type.as_ref(),
"level": { "$in": ["Read", "Execute", "Write"] }
},
None,
)
.await
.context("failed to query permissions on db")?
.into_iter()
.map(|p| p.resource_target.extract_variant_id().1.to_string())
// collect into hashset first to remove any duplicates
.collect::<HashSet<_>>();
Ok(permissions.into_iter().collect())
}
#[instrument(level = "debug")]
pub async fn validate_resource_query_tags<
T: Default + std::fmt::Debug,
>(
query: &mut ResourceQuery<T>,
) {
let futures = query.tags.iter().map(|tag| get_tag(tag));
let res = join_all(futures).await;
query.tags = res.into_iter().flatten().map(|tag| tag.id).collect();
}
+30 -30
View File
@@ -29,6 +29,36 @@ pub fn make_update(
}
}
#[instrument(level = "debug")]
pub async fn add_update(
mut update: Update,
) -> anyhow::Result<String> {
update.id = db_client()
.await
.updates
.insert_one(&update, None)
.await
.context("failed to insert update into db")?
.inserted_id
.as_object_id()
.context("inserted_id is not object id")?
.to_string();
let id = update.id.clone();
let update = update_list_item(update).await?;
let _ = send_update(update).await;
Ok(id)
}
#[instrument(level = "debug")]
pub async fn update_update(update: Update) -> anyhow::Result<()> {
update_one_by_id(&db_client().await.updates, &update.id, mungos::update::Update::Set(to_document(&update)?), None)
.await
.context("failed to update the update on db. the update build process was deleted")?;
let update = update_list_item(update).await?;
let _ = send_update(update).await;
Ok(())
}
#[instrument(level = "debug")]
async fn update_list_item(
update: Update,
@@ -63,33 +93,3 @@ async fn send_update(update: UpdateListItem) -> anyhow::Result<()> {
update_channel().sender.lock().await.send(update)?;
Ok(())
}
#[instrument(level = "debug")]
pub async fn add_update(
mut update: Update,
) -> anyhow::Result<String> {
update.id = db_client()
.await
.updates
.insert_one(&update, None)
.await
.context("failed to insert update into db")?
.inserted_id
.as_object_id()
.context("inserted_id is not object id")?
.to_string();
let id = update.id.clone();
let update = update_list_item(update).await?;
let _ = send_update(update).await;
Ok(id)
}
#[instrument(level = "debug")]
pub async fn update_update(update: Update) -> anyhow::Result<()> {
update_one_by_id(&db_client().await.updates, &update.id, mungos::update::Update::Set(to_document(&update)?), None)
.await
.context("failed to update the update on db. the update build process was deleted")?;
let update = update_list_item(update).await?;
let _ = send_update(update).await;
Ok(())
}
+50 -4
View File
@@ -1,3 +1,5 @@
use std::sync::{Arc, OnceLock};
use anyhow::{anyhow, Context};
use axum::{extract::Path, http::HeaderMap, routing::post, Router};
use hex::ToHex;
@@ -9,11 +11,13 @@ use monitor_client::{
use resolver_api::Resolve;
use serde::Deserialize;
use sha2::Sha256;
use tokio::sync::Mutex;
use tracing::Instrument;
use crate::{
config::core_config,
helpers::{random_duration, resource::StateResource},
helpers::{cache::Cache, random_duration},
resource,
state::State,
};
@@ -116,9 +120,15 @@ async fn handle_build_webhook(
headers: HeaderMap,
body: String,
) -> anyhow::Result<()> {
// Acquire and hold lock to make a task queue for
// subsequent listener calls on same resource.
// It would fail if we let it go through from action state busy.
let lock = build_locks().get_or_insert_default(&build_id).await;
let _lock = lock.lock().await;
verify_gh_signature(headers, &body).await?;
let request_branch = extract_branch(&body)?;
let build = Build::get_resource(&build_id).await?;
let build = resource::get::<Build>(&build_id).await?;
if request_branch != build.config.branch {
return Err(anyhow!("request branch does not match expected"));
}
@@ -136,9 +146,15 @@ async fn handle_repo_clone_webhook(
headers: HeaderMap,
body: String,
) -> anyhow::Result<()> {
// Acquire and hold lock to make a task queue for
// subsequent listener calls on same resource.
// It would fail if we let it go through from action state busy.
let lock = repo_locks().get_or_insert_default(&repo_id).await;
let _lock = lock.lock().await;
verify_gh_signature(headers, &body).await?;
let request_branch = extract_branch(&body)?;
let repo = Repo::get_resource(&repo_id).await?;
let repo = resource::get::<Repo>(&repo_id).await?;
if request_branch != repo.config.branch {
return Err(anyhow!("request branch does not match expected"));
}
@@ -156,9 +172,15 @@ async fn handle_repo_pull_webhook(
headers: HeaderMap,
body: String,
) -> anyhow::Result<()> {
// Acquire and hold lock to make a task queue for
// subsequent listener calls on same resource.
// It would fail if we let it go through from action state busy.
let lock = repo_locks().get_or_insert_default(&repo_id).await;
let _lock = lock.lock().await;
verify_gh_signature(headers, &body).await?;
let request_branch = extract_branch(&body)?;
let repo = Repo::get_resource(&repo_id).await?;
let repo = resource::get::<Repo>(&repo_id).await?;
if request_branch != repo.config.branch {
return Err(anyhow!("request branch does not match expected"));
}
@@ -177,6 +199,13 @@ async fn handle_procedure_webhook(
headers: HeaderMap,
body: String,
) -> anyhow::Result<()> {
// Acquire and hold lock to make a task queue for
// subsequent listener calls on same resource.
// It would fail if we let it go through from action state busy.
let lock =
procedure_locks().get_or_insert_default(&procedure_id).await;
let _lock = lock.lock().await;
verify_gh_signature(headers, &body).await?;
let request_branch = extract_branch(&body)?;
if request_branch != target_branch {
@@ -236,3 +265,20 @@ fn extract_branch(body: &str) -> anyhow::Result<String> {
.replace("refs/heads/", "");
Ok(branch)
}
type ListenerLockCache = Cache<String, Arc<Mutex<()>>>;
fn build_locks() -> &'static ListenerLockCache {
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
BUILD_LOCKS.get_or_init(Default::default)
}
fn repo_locks() -> &'static ListenerLockCache {
static REPO_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
REPO_LOCKS.get_or_init(Default::default)
}
fn procedure_locks() -> &'static ListenerLockCache {
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
BUILD_LOCKS.get_or_init(Default::default)
}
+1
View File
@@ -21,6 +21,7 @@ mod db;
mod helpers;
mod listener;
mod monitor;
mod resource;
mod state;
mod ws;
+3 -4
View File
@@ -8,9 +8,8 @@ use monitor_client::entities::{
};
use crate::{
helpers::{alert::send_alerts, resource::StateResource},
monitor::deployment_status_cache,
state::db_client,
helpers::alert::send_alerts, monitor::deployment_status_cache,
resource, state::db_client,
};
#[instrument(level = "debug")]
@@ -26,7 +25,7 @@ pub async fn alert_deployments(
let prev = v.prev.as_ref().unwrap().to_owned();
if v.curr.state != prev {
// send alert
let d = Deployment::get_resource(&v.curr.id).await;
let d = resource::get::<Deployment>(&v.curr.id).await;
if let Err(e) = d {
error!("failed to get deployment from db | {e:#?}");
continue;
+2 -2
View File
@@ -10,7 +10,7 @@ use monitor_client::entities::{
user::User,
};
use crate::helpers::resource::StateResource;
use crate::resource;
// called after cache update
#[instrument(level = "debug")]
@@ -35,7 +35,7 @@ async fn get_all_servers_map() -> anyhow::Result<(
HashMap<String, ServerListItem>,
HashMap<String, String>,
)> {
let servers = Server::list_resource_list_items_for_user(
let servers = resource::list_for_user::<Server>(
ResourceQuery::default(),
&User {
admin: true,
+146
View File
@@ -0,0 +1,146 @@
use monitor_client::entities::{
alerter::{
Alerter, AlerterConfig, AlerterConfigDiff, AlerterConfigVariant,
AlerterInfo, AlerterListItem, AlerterListItemInfo,
AlerterQuerySpecifics, PartialAlerterConfig,
},
resource::Resource,
update::{ResourceTargetVariant, Update},
user::User,
MergePartial, Operation,
};
use mungos::mongodb::{
bson::{to_document, Document},
Collection,
};
use crate::state::db_client;
impl super::MonitorResource for Alerter {
type Config = AlerterConfig;
type PartialConfig = PartialAlerterConfig;
type ConfigDiff = AlerterConfigDiff;
type Info = AlerterInfo;
type ListItem = AlerterListItem;
type QuerySpecifics = AlerterQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Alerter
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.alerters
}
async fn to_list_item(
alerter: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
let (alerter_type, enabled) = match alerter.config {
AlerterConfig::Custom(config) => {
(AlerterConfigVariant::Custom.to_string(), config.enabled)
}
AlerterConfig::Slack(config) => {
(AlerterConfigVariant::Slack.to_string(), config.enabled)
}
};
Ok(AlerterListItem {
name: alerter.name,
id: alerter.id,
tags: alerter.tags,
resource_type: ResourceTargetVariant::Alerter,
info: AlerterListItemInfo {
alerter_type: alerter_type.to_string(),
is_default: alerter.info.is_default,
enabled,
},
})
}
async fn busy(_id: &String) -> anyhow::Result<bool> {
Ok(false)
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateAlerter
}
fn user_can_create(user: &User) -> bool {
user.admin
}
async fn validate_create_config(
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn default_info() -> anyhow::Result<Self::Info> {
let is_default = db_client()
.await
.alerters
.find_one(None, None)
.await?
.is_none();
Ok(Self::Info { is_default })
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateAlerter
}
async fn validate_update_config(
_id: &str,
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
fn update_document(
original: Resource<Self::Config, Self::Info>,
config: Self::PartialConfig,
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
let config = original.config.merge_partial(config);
to_document(&config)
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteAlerter
}
async fn pre_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
+151
View File
@@ -0,0 +1,151 @@
use anyhow::Context;
use monitor_client::entities::{
build::{
Build, BuildConfig, BuildConfigDiff, BuildInfo, BuildListItem,
BuildListItemInfo, BuildQuerySpecifics, PartialBuildConfig,
},
builder::Builder,
permission::PermissionLevel,
resource::Resource,
update::{ResourceTargetVariant, Update},
user::User,
Operation,
};
use mungos::mongodb::Collection;
use crate::{
helpers::empty_or_only_spaces,
state::{action_states, db_client},
};
impl super::MonitorResource for Build {
type Config = BuildConfig;
type PartialConfig = PartialBuildConfig;
type ConfigDiff = BuildConfigDiff;
type Info = BuildInfo;
type ListItem = BuildListItem;
type QuerySpecifics = BuildQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Build
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.builds
}
async fn to_list_item(
build: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
Ok(BuildListItem {
name: build.name,
id: build.id,
tags: build.tags,
resource_type: ResourceTargetVariant::Build,
info: BuildListItemInfo {
last_built_at: build.info.last_built_at,
version: build.config.version,
repo: build.config.repo,
branch: build.config.branch,
},
})
}
async fn busy(id: &String) -> anyhow::Result<bool> {
action_states()
.build
.get(id)
.await
.unwrap_or_default()
.busy()
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateBuild
}
fn user_can_create(user: &User) -> bool {
user.admin || user.create_build_permissions
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateBuild
}
async fn validate_update_config(
_id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteBuild
}
async fn pre_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialBuildConfig,
user: &User,
) -> anyhow::Result<()> {
if let Some(builder_id) = &config.builder_id {
let builder = super::get_check_permissions::<Builder>(builder_id, user, PermissionLevel::Read)
.await
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
config.builder_id = Some(builder.id)
}
if let Some(build_args) = &mut config.build_args {
build_args.retain(|v| {
!empty_or_only_spaces(&v.variable)
&& !empty_or_only_spaces(&v.value)
})
}
if let Some(extra_args) = &mut config.extra_args {
extra_args.retain(|v| !empty_or_only_spaces(v))
}
Ok(())
}
+176
View File
@@ -0,0 +1,176 @@
use anyhow::Context;
use monitor_client::entities::{
builder::{
Builder, BuilderConfig, BuilderConfigDiff, BuilderConfigVariant,
BuilderListItem, BuilderListItemInfo, BuilderQuerySpecifics,
PartialBuilderConfig, PartialServerBuilderConfig,
},
permission::PermissionLevel,
resource::Resource,
server::Server,
update::{ResourceTargetVariant, Update},
user::User,
MergePartial, Operation,
};
use mungos::mongodb::{
bson::{doc, to_document, Document},
Collection,
};
use crate::state::db_client;
impl super::MonitorResource for Builder {
type Config = BuilderConfig;
type PartialConfig = PartialBuilderConfig;
type ConfigDiff = BuilderConfigDiff;
type Info = ();
type ListItem = BuilderListItem;
type QuerySpecifics = BuilderQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Builder
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.builders
}
async fn to_list_item(
builder: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
let (builder_type, instance_type) = match builder.config {
BuilderConfig::Server(config) => (
BuilderConfigVariant::Server.to_string(),
Some(config.server_id),
),
BuilderConfig::Aws(config) => (
BuilderConfigVariant::Aws.to_string(),
Some(config.instance_type),
),
};
Ok(BuilderListItem {
name: builder.name,
id: builder.id,
tags: builder.tags,
resource_type: ResourceTargetVariant::Builder,
info: BuilderListItemInfo {
builder_type,
instance_type,
},
})
}
async fn busy(_id: &String) -> anyhow::Result<bool> {
Ok(false)
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateBuilder
}
fn user_can_create(user: &User) -> bool {
user.admin
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateBuilder
}
async fn validate_update_config(
_id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
fn update_document(
original: Resource<Self::Config, Self::Info>,
config: Self::PartialConfig,
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
let config = original.config.merge_partial(config);
to_document(&config)
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteBuilder
}
async fn pre_delete(
resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
// remove the builder from any attached builds
db_client()
.await
.builds
.update_many(
doc! { "config.builder.params.builder_id": &resource.id },
mungos::update::Update::Set(
doc! { "config.builder.params.builder_id": "" },
),
None,
)
.await
.context("failed to update_many builds on database")?;
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialBuilderConfig,
user: &User,
) -> anyhow::Result<()> {
match config {
PartialBuilderConfig::Server(PartialServerBuilderConfig {
server_id: Some(server_id),
}) if !server_id.is_empty() => {
let server = super::get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
)
.await?;
*server_id = server.id;
}
_ => {}
}
Ok(())
}
+252
View File
@@ -0,0 +1,252 @@
use anyhow::Context;
use monitor_client::entities::{
build::Build,
deployment::{
Deployment, DeploymentConfig, DeploymentConfigDiff,
DeploymentImage, DeploymentListItem, DeploymentListItemInfo,
DeploymentQuerySpecifics, DockerContainerState,
PartialDeploymentConfig,
},
permission::PermissionLevel,
resource::Resource,
server::Server,
update::{ResourceTargetVariant, Update},
user::User,
Operation,
};
use mungos::mongodb::Collection;
use periphery_client::api::container::RemoveContainer;
use crate::{
helpers::{
empty_or_only_spaces, periphery_client,
query::get_deployment_state,
},
state::{action_states, db_client, deployment_status_cache},
};
use super::get_check_permissions;
impl super::MonitorResource for Deployment {
type Config = DeploymentConfig;
type PartialConfig = PartialDeploymentConfig;
type ConfigDiff = DeploymentConfigDiff;
type Info = ();
type ListItem = DeploymentListItem;
type QuerySpecifics = DeploymentQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Deployment
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.deployments
}
async fn to_list_item(
deployment: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
let status = deployment_status_cache().get(&deployment.id).await;
let (image, build_id) = match deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let build = super::get::<Build>(&build_id).await?;
let version = if version.is_none() {
build.config.version.to_string()
} else {
version.to_string()
};
(format!("{}:{version}", build.name), Some(build_id))
}
DeploymentImage::Image { image } => (image, None),
};
Ok(DeploymentListItem {
name: deployment.name,
id: deployment.id,
tags: deployment.tags,
resource_type: ResourceTargetVariant::Deployment,
info: DeploymentListItemInfo {
state: status
.as_ref()
.map(|s| s.curr.state)
.unwrap_or_default(),
status: status.as_ref().and_then(|s| {
s.curr.container.as_ref().and_then(|c| c.status.to_owned())
}),
image,
server_id: deployment.config.server_id,
build_id,
},
})
}
async fn busy(id: &String) -> anyhow::Result<bool> {
action_states()
.deployment
.get(id)
.await
.unwrap_or_default()
.busy()
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateDeployment
}
fn user_can_create(_user: &User) -> bool {
true
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateDeployment
}
async fn validate_update_config(
_id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteDeployment
}
async fn pre_delete(
deployment: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()> {
let state = get_deployment_state(deployment)
.await
.context("failed to get container state")?;
if !matches!(
state,
DockerContainerState::NotDeployed
| DockerContainerState::Unknown
) {
// container needs to be destroyed
let server =
super::get::<Server>(&deployment.config.server_id).await;
if let Err(e) = server {
update.push_error_log(
"remove container",
format!(
"failed to retrieve server at {} from db | {e:#?}",
deployment.config.server_id
),
);
} else if let Ok(server) = server {
match periphery_client(&server) {
Ok(periphery) => match periphery
.request(RemoveContainer {
name: deployment.name.clone(),
signal: deployment.config.termination_signal.into(),
time: deployment.config.termination_timeout.into(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.push_error_log(
"remove container",
format!(
"failed to remove container on periphery | {e:#?}"
),
),
},
Err(e) => update.push_error_log(
"remove container",
format!(
"failed to remove container on periphery | {e:#?}"
),
),
};
}
}
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialDeploymentConfig,
user: &User,
) -> anyhow::Result<()> {
if let Some(server_id) = &config.server_id {
if !server_id.is_empty() {
let server = get_check_permissions::<Server>(server_id, user, PermissionLevel::Write)
.await
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
config.server_id = Some(server.id);
}
}
if let Some(DeploymentImage::Build { build_id, version }) =
&config.image
{
if !build_id.is_empty() {
let build = get_check_permissions::<Build>(build_id, user, PermissionLevel::Read)
.await
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
config.image = Some(DeploymentImage::Build {
build_id: build.id,
version: version.clone(),
});
}
}
if let Some(volumes) = &mut config.volumes {
volumes.retain(|v| {
!empty_or_only_spaces(&v.local)
&& !empty_or_only_spaces(&v.container)
})
}
if let Some(ports) = &mut config.ports {
ports.retain(|v| {
!empty_or_only_spaces(&v.local)
&& !empty_or_only_spaces(&v.container)
})
}
if let Some(environment) = &mut config.environment {
environment.retain(|v| {
!empty_or_only_spaces(&v.variable)
&& !empty_or_only_spaces(&v.value)
})
}
if let Some(extra_args) = &mut config.extra_args {
extra_args.retain(|v| !empty_or_only_spaces(v))
}
Ok(())
}
+619
View File
@@ -0,0 +1,619 @@
use std::str::FromStr;
use anyhow::{anyhow, Context};
use futures::future::join_all;
use monitor_client::{
api::write::CreateTag,
entities::{
monitor_timestamp,
permission::PermissionLevel,
resource::{AddFilters, Resource, ResourceQuery},
to_monitor_name,
update::{ResourceTarget, ResourceTargetVariant, Update},
user::User,
Operation,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
find::find_collect,
mongodb::{
bson::{doc, oid::ObjectId, to_document, Document},
Collection,
},
};
use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff};
use resolver_api::Resolve;
use serde::{de::DeserializeOwned, Serialize};
use serror::serialize_error_pretty;
use crate::{
helpers::{
create_permission,
query::{
get_resource_ids_for_non_admin, get_tag,
get_user_permission_on_resource, id_or_name_filter,
},
update::{add_update, make_update},
},
state::{db_client, State},
};
mod alerter;
mod build;
mod builder;
mod deployment;
mod procedure;
mod repo;
mod server;
mod server_template;
/// Implement on each monitor resource for common methods
pub trait MonitorResource {
type ListItem: Serialize + Send;
type Config: Send
+ Sync
+ Unpin
+ Serialize
+ DeserializeOwned
+ PartialDiff<Self::PartialConfig, Self::ConfigDiff>
+ 'static;
type PartialConfig: Into<Self::Config> + Serialize;
type ConfigDiff: Into<Self::PartialConfig>
+ Serialize
+ Diff
+ MaybeNone;
type Info: Send
+ Sync
+ Unpin
+ Default
+ Serialize
+ DeserializeOwned
+ 'static;
type QuerySpecifics: AddFilters + Default + std::fmt::Debug;
fn resource_type() -> ResourceTargetVariant;
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>>;
async fn to_list_item(
resource: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem>;
#[allow(clippy::ptr_arg)]
async fn busy(id: &String) -> anyhow::Result<bool>;
// =======
// CREATE
// =======
fn create_operation() -> Operation;
fn user_can_create(user: &User) -> bool;
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()>;
async fn default_info() -> anyhow::Result<Self::Info> {
Ok(Default::default())
}
async fn post_create(
created: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()>;
// =======
// UPDATE
// =======
fn update_operation() -> Operation;
async fn validate_update_config(
id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()>;
/// Should be overridden for enum configs, eg Alerter, Builder, ...
fn update_document(
_original: Resource<Self::Config, Self::Info>,
config: Self::PartialConfig,
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
to_document(&config)
}
/// Run any required task after resource updated in database but
/// before the request resolves.
async fn post_update(
updated: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()>;
// =======
// DELETE
// =======
fn delete_operation() -> Operation;
/// Clean up all links to this resource before deleting it.
async fn pre_delete(
resource: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()>;
/// Run any required task after resource deleted from database but
/// before the request resolves.
async fn post_delete(
resource: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()>;
}
// Methods
// ======
// GET
// ======
pub async fn get<T: MonitorResource>(
id_or_name: &str,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
T::coll()
.await
.find_one(id_or_name_filter(id_or_name), None)
.await
.context("failed to query db for resource")?
.with_context(|| {
format!(
"did not find any {} matching {id_or_name}",
T::resource_type()
)
})
}
pub async fn get_check_permissions<T: MonitorResource>(
id_or_name: &str,
user: &User,
permission_level: PermissionLevel,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
let resource = get::<T>(id_or_name).await?;
if user.admin {
return Ok(resource);
}
let permissions = get_user_permission_on_resource(
&user.id,
T::resource_type(),
&resource.id,
)
.await?;
if permissions >= permission_level {
Ok(resource)
} else {
Err(anyhow!(
"user does not have required permissions on this {}",
T::resource_type()
))
}
}
// ======
// LIST
// ======
pub async fn list_for_user<T: MonitorResource>(
mut query: ResourceQuery<T::QuerySpecifics>,
user: &User,
) -> anyhow::Result<Vec<T::ListItem>> {
validate_resource_query_tags(&mut query).await;
let mut filters = Document::new();
query.add_filters(&mut filters);
list_for_user_using_document::<T>(filters, user).await
}
pub async fn list_for_user_using_document<T: MonitorResource>(
filters: Document,
user: &User,
) -> anyhow::Result<Vec<T::ListItem>> {
let list = list_full_for_user_using_document::<T>(filters, user)
.await?
.into_iter()
.map(|resource| T::to_list_item(resource));
let list = join_all(list)
.await
.into_iter()
.collect::<anyhow::Result<Vec<_>>>()
.context(format!(
"failed to convert {} list item",
T::resource_type()
))?;
Ok(list)
}
pub async fn list_full_for_user<T: MonitorResource>(
mut query: ResourceQuery<T::QuerySpecifics>,
user: &User,
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
validate_resource_query_tags(&mut query).await;
let mut filters = Document::new();
query.add_filters(&mut filters);
list_full_for_user_using_document::<T>(filters, user).await
}
async fn list_full_for_user_using_document<T: MonitorResource>(
mut filters: Document,
user: &User,
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
if !user.admin {
let ids =
get_resource_ids_for_non_admin(&user.id, T::resource_type())
.await?
.into_iter()
.flat_map(|id| ObjectId::from_str(&id))
.collect::<Vec<_>>();
filters.insert("_id", doc! { "$in": ids });
}
find_collect(T::coll().await, filters, None)
.await
.with_context(|| {
format!("failed to pull {}s from mongo", T::resource_type())
})
}
// =======
// CREATE
// =======
pub async fn create<T: MonitorResource>(
name: &str,
mut config: T::PartialConfig,
user: &User,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
if !T::user_can_create(user) {
return Err(anyhow!(
"User does not have permissions to create {}",
T::resource_type()
));
}
let name = to_monitor_name(name);
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names"));
}
let start_ts = monitor_timestamp();
T::validate_create_config(&mut config, user).await?;
let resource = Resource::<T::Config, T::Info> {
id: Default::default(),
name,
updated_at: start_ts,
description: Default::default(),
tags: Default::default(),
config: config.into(),
info: T::default_info().await?,
};
let resource_id = T::coll()
.await
.insert_one(&resource, None)
.await
.with_context(|| {
format!("failed to add {} to db", T::resource_type())
})?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let resource = get::<T>(&resource_id).await?;
let target = resource_target::<T>(resource_id);
create_permission(user, target.clone(), PermissionLevel::Write)
.await;
let mut update = make_update(target, T::create_operation(), user);
update.start_ts = start_ts;
update.push_simple_log(
&format!("create {}", T::resource_type()),
format!(
"created {}\nid: {}\nname: {}",
T::resource_type(),
resource.id,
resource.name
),
);
update.push_simple_log(
"config",
serde_json::to_string_pretty(&resource.config)
.context("failed to serialize resource config to JSON")?,
);
T::post_create(&resource, &mut update).await?;
update.finalize();
add_update(update).await?;
Ok(resource)
}
// =======
// UPDATE
// =======
pub async fn update<T: MonitorResource>(
id_or_name: &str,
mut config: T::PartialConfig,
user: &User,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
let resource = get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
)
.await?;
if T::busy(&resource.id).await? {
return Err(anyhow!("{} busy", T::resource_type()));
}
T::validate_update_config(&resource.id, &mut config, user).await?;
// Gets a diff object.
let diff = resource.config.partial_diff(config);
if diff.is_none() {
return Err(anyhow!("update has no changes"));
}
let mut diff_log = String::from("diff");
for FieldDiff { field, from, to } in diff.iter_field_diffs() {
diff_log.push_str(&format!(
"\n\nfield: '{field}'\nfrom: {from}\nto: {to}"
));
}
// This minimizes the update against the existing config
let config: T::PartialConfig = diff.into();
let id = resource.id.clone();
let config_doc = T::update_document(resource, config)
.context("failed to serialize config to bson document")?;
update_one_by_id(
T::coll().await,
&id,
mungos::update::Update::FlattenSet(doc! { "config": config_doc }),
None,
)
.await
.context("failed to update resource on database")?;
let mut update = make_update(
resource_target::<T>(id),
T::update_operation(),
user,
);
update.push_simple_log("update config", diff_log);
let updated = get::<T>(id_or_name).await?;
T::post_update(&updated, &mut update).await?;
update.finalize();
add_update(update).await?;
Ok(updated)
}
fn resource_target<T: MonitorResource>(id: String) -> ResourceTarget {
match T::resource_type() {
ResourceTargetVariant::System => ResourceTarget::System(id),
ResourceTargetVariant::Build => ResourceTarget::Build(id),
ResourceTargetVariant::Builder => ResourceTarget::Builder(id),
ResourceTargetVariant::Deployment => {
ResourceTarget::Deployment(id)
}
ResourceTargetVariant::Server => ResourceTarget::Server(id),
ResourceTargetVariant::Repo => ResourceTarget::Repo(id),
ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id),
ResourceTargetVariant::Procedure => ResourceTarget::Procedure(id),
ResourceTargetVariant::ServerTemplate => {
ResourceTarget::ServerTemplate(id)
}
}
}
pub async fn update_description<T: MonitorResource>(
id_or_name: &str,
description: &str,
user: &User,
) -> anyhow::Result<()> {
get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
)
.await?;
T::coll()
.await
.update_one(
id_or_name_filter(id_or_name),
doc! { "$set": { "description": description } },
None,
)
.await?;
Ok(())
}
pub async fn update_tags<T: MonitorResource>(
id_or_name: &str,
tags: Vec<String>,
user: User,
) -> anyhow::Result<()> {
let futures = tags.iter().map(|tag| async {
match get_tag(tag).await {
Ok(tag) => Ok(tag.id),
Err(_) => State
.resolve(
CreateTag {
name: tag.to_string(),
},
user.clone(),
)
.await
.map(|tag| tag.id),
}
});
let tags = join_all(futures)
.await
.into_iter()
.flatten()
.collect::<Vec<_>>();
T::coll()
.await
.update_one(
id_or_name_filter(id_or_name),
doc! { "$set": { "tags": tags } },
None,
)
.await?;
Ok(())
}
pub async fn remove_tag_from_all<T: MonitorResource>(
tag_id: &str,
) -> anyhow::Result<()> {
T::coll()
.await
.update_many(doc! {}, doc! { "$pull": { "tags": tag_id } }, None)
.await
.context("failed to remove tag from resources")?;
Ok(())
}
// =======
// DELETE
// =======
pub async fn delete<T: MonitorResource>(
id_or_name: &str,
user: &User,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
let resource = get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
)
.await?;
if T::busy(&resource.id).await? {
return Err(anyhow!("{} busy", T::resource_type()));
}
let target = resource_target::<T>(resource.id.clone());
let mut update =
make_update(target.clone(), T::delete_operation(), user);
T::pre_delete(&resource, &mut update).await?;
delete_all_permissions_on_resource(target.clone()).await;
remove_from_recently_viewed(target.clone()).await;
delete_one_by_id(T::coll().await, &resource.id, None)
.await
.with_context(|| {
format!("failed to delete {} from database", T::resource_type())
})?;
update.push_simple_log(
&format!("delete {}", T::resource_type()),
format!("deleted {} {}", T::resource_type(), resource.name),
);
if let Err(e) = T::post_delete(&resource, &mut update).await {
update.push_error_log("post delete", serialize_error_pretty(&e));
}
update.finalize();
add_update(update).await?;
Ok(resource)
}
// =======
#[instrument(level = "debug")]
pub async fn validate_resource_query_tags<
T: Default + std::fmt::Debug,
>(
query: &mut ResourceQuery<T>,
) {
let futures = query.tags.iter().map(|tag| get_tag(tag));
let res = join_all(futures).await;
query.tags = res.into_iter().flatten().map(|tag| tag.id).collect();
}
#[instrument]
pub async fn delete_all_permissions_on_resource<T>(target: T)
where
T: Into<ResourceTarget> + std::fmt::Debug,
{
let target: ResourceTarget = target.into();
let (variant, id) = target.extract_variant_id();
if let Err(e) = db_client()
.await
.permissions
.delete_many(
doc! {
"resource_target.type": variant.as_ref(),
"resource_target.id": &id
},
None,
)
.await
{
warn!("failed to delete_many permissions matching target {target:?} | {e:#}");
}
}
#[instrument]
pub async fn remove_from_recently_viewed<T>(resource: T)
where
T: Into<ResourceTarget> + std::fmt::Debug,
{
let resource: ResourceTarget = resource.into();
let (ty, id) = resource.extract_variant_id();
if let Err(e) = db_client()
.await
.users
.update_many(
doc! {},
doc! {
"$pull": {
"recently_viewed": {
"type": ty.to_string(),
"id": id,
}
}
},
None,
)
.await
.context("failed to remove resource from users recently viewed")
{
warn!("{e:#}");
}
}
+262
View File
@@ -0,0 +1,262 @@
use anyhow::anyhow;
use monitor_client::{
api::execute::Execution,
entities::{
build::Build,
deployment::Deployment,
permission::PermissionLevel,
procedure::{
PartialProcedureConfig, Procedure, ProcedureConfig,
ProcedureConfigDiff, ProcedureListItem, ProcedureListItemInfo,
ProcedureQuerySpecifics,
},
repo::Repo,
resource::Resource,
server::Server,
update::{ResourceTargetVariant, Update},
user::User,
Operation,
},
};
use mungos::mongodb::Collection;
use crate::state::{action_states, db_client};
impl super::MonitorResource for Procedure {
type Config = ProcedureConfig;
type PartialConfig = PartialProcedureConfig;
type ConfigDiff = ProcedureConfigDiff;
type Info = ();
type ListItem = ProcedureListItem;
type QuerySpecifics = ProcedureQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Procedure
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.procedures
}
async fn to_list_item(
procedure: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
Ok(ProcedureListItem {
name: procedure.name,
id: procedure.id,
tags: procedure.tags,
resource_type: ResourceTargetVariant::Procedure,
info: ProcedureListItemInfo {
procedure_type: procedure.config.procedure_type,
},
})
}
async fn busy(id: &String) -> anyhow::Result<bool> {
action_states()
.procedure
.get(id)
.await
.unwrap_or_default()
.busy()
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateProcedure
}
fn user_can_create(_user: &User) -> bool {
true
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user, None).await
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateProcedure
}
async fn validate_update_config(
id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user, Some(id)).await
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteProcedure
}
async fn pre_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialProcedureConfig,
user: &User,
id: Option<&str>,
) -> anyhow::Result<()> {
let Some(executions) = &mut config.executions else {
return Ok(());
};
for exec in executions {
match &mut exec.execution {
Execution::None(_) => {}
Execution::RunProcedure(params) => {
let procedure = super::get_check_permissions::<Procedure>(
&params.procedure,
user,
PermissionLevel::Execute,
)
.await?;
match id {
Some(id) if procedure.id == id => {
return Err(anyhow!(
"Cannot have self-referential procedure"
))
}
_ => {}
}
params.procedure = procedure.id;
}
Execution::RunBuild(params) => {
let build = super::get_check_permissions::<Build>(
&params.build,
user,
PermissionLevel::Execute,
)
.await?;
params.build = build.id;
}
Execution::Deploy(params) => {
let deployment = super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StartContainer(params) => {
let deployment = super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StopContainer(params) => {
let deployment = super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::StopAllContainers(params) => {
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::RemoveContainer(params) => {
let deployment = super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
)
.await?;
params.deployment = deployment.id;
}
Execution::CloneRepo(params) => {
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
)
.await?;
params.repo = repo.id;
}
Execution::PullRepo(params) => {
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
)
.await?;
params.repo = repo.id;
}
Execution::PruneDockerNetworks(params) => {
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::PruneDockerImages(params) => {
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
Execution::PruneDockerContainers(params) => {
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
)
.await?;
params.server = server.id;
}
}
}
Ok(())
}
+172
View File
@@ -0,0 +1,172 @@
use anyhow::Context;
use monitor_client::entities::{
permission::PermissionLevel,
repo::{
PartialRepoConfig, Repo, RepoConfig, RepoConfigDiff, RepoInfo,
RepoListItem, RepoListItemInfo, RepoQuerySpecifics,
},
resource::Resource,
server::Server,
update::{ResourceTargetVariant, Update},
user::User,
Operation,
};
use mungos::mongodb::Collection;
use periphery_client::api::git::DeleteRepo;
use serror::serialize_error_pretty;
use crate::{
helpers::periphery_client,
state::{action_states, db_client},
};
use super::get_check_permissions;
impl super::MonitorResource for Repo {
type Config = RepoConfig;
type PartialConfig = PartialRepoConfig;
type ConfigDiff = RepoConfigDiff;
type Info = RepoInfo;
type ListItem = RepoListItem;
type QuerySpecifics = RepoQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Repo
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.repos
}
async fn to_list_item(
repo: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
Ok(RepoListItem {
name: repo.name,
id: repo.id,
tags: repo.tags,
resource_type: ResourceTargetVariant::Repo,
info: RepoListItemInfo {
last_pulled_at: repo.info.last_pulled_at,
repo: repo.config.repo,
branch: repo.config.branch,
},
})
}
async fn busy(id: &String) -> anyhow::Result<bool> {
action_states()
.repo
.get(id)
.await
.unwrap_or_default()
.busy()
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateRepo
}
fn user_can_create(_user: &User) -> bool {
true
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateRepo
}
async fn validate_update_config(
_id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteRepo
}
async fn pre_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
repo: &Resource<Self::Config, Self::Info>,
update: &mut Update,
) -> anyhow::Result<()> {
if repo.config.server_id.is_empty() {
return Ok(());
}
let server = super::get::<Server>(&repo.config.server_id).await?;
let periphery = periphery_client(&server)?;
match periphery
.request(DeleteRepo {
name: repo.name.clone(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.push_error_log(
"delete repo on periphery",
serialize_error_pretty(&e),
),
}
Ok(())
}
}
#[instrument(skip(user))]
async fn validate_config(
config: &mut PartialRepoConfig,
user: &User,
) -> anyhow::Result<()> {
match &config.server_id {
Some(server_id) if !server_id.is_empty() => {
let server = get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
)
.await
.context("cannot create repo on this server. user must have update permissions on the server.")?;
config.server_id = Some(server.id);
}
_ => {}
}
Ok(())
}
+178
View File
@@ -0,0 +1,178 @@
use anyhow::Context;
use monitor_client::entities::{
monitor_timestamp,
resource::Resource,
server::{
PartialServerConfig, Server, ServerConfig, ServerConfigDiff,
ServerListItem, ServerListItemInfo, ServerQuerySpecifics,
},
update::{ResourceTargetVariant, Update},
user::User,
Operation,
};
use mungos::mongodb::{bson::doc, Collection};
use crate::{
monitor::update_cache_for_server,
state::{action_states, db_client, server_status_cache},
};
impl super::MonitorResource for Server {
type Config = ServerConfig;
type PartialConfig = PartialServerConfig;
type ConfigDiff = ServerConfigDiff;
type Info = ();
type ListItem = ServerListItem;
type QuerySpecifics = ServerQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Server
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.servers
}
async fn to_list_item(
server: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
let status = server_status_cache().get(&server.id).await;
Ok(ServerListItem {
name: server.name,
id: server.id,
tags: server.tags,
resource_type: ResourceTargetVariant::Server,
info: ServerListItemInfo {
status: status.map(|s| s.status).unwrap_or_default(),
region: server.config.region,
send_unreachable_alerts: server
.config
.send_unreachable_alerts,
send_cpu_alerts: server.config.send_cpu_alerts,
send_mem_alerts: server.config.send_mem_alerts,
send_disk_alerts: server.config.send_disk_alerts,
},
})
}
async fn busy(id: &String) -> anyhow::Result<bool> {
action_states()
.server
.get(id)
.await
.unwrap_or_default()
.busy()
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateServer
}
fn user_can_create(user: &User) -> bool {
user.admin || user.create_server_permissions
}
async fn validate_create_config(
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_create(
created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
update_cache_for_server(created).await;
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateServer
}
async fn validate_update_config(
_id: &str,
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_update(
updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
update_cache_for_server(updated).await;
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteServer
}
async fn pre_delete(
resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
let db = db_client().await;
let id = &resource.id;
db.builders
.update_many(
doc! { "config.params.server_id": &id },
doc! { "$set": { "config.params.server_id": "" } },
None,
)
.await
.context("failed to detach server from builders")?;
db.deployments
.update_many(
doc! { "config.server_id": &id },
doc! { "$set": { "config.server_id": "" } },
None,
)
.await
.context("failed to detach server from deployments")?;
db.repos
.update_many(
doc! { "config.server_id": &id },
doc! { "$set": { "config.server_id": "" } },
None,
)
.await
.context("failed to detach server from repos")?;
db.alerts
.update_many(
doc! { "target.type": "Server", "target.id": &id },
doc! { "$set": {
"resolved": true,
"resolved_ts": monitor_timestamp()
} },
None,
)
.await
.context("failed to detach server from repos")?;
Ok(())
}
async fn post_delete(
resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
server_status_cache().remove(&resource.id).await;
Ok(())
}
}
+135
View File
@@ -0,0 +1,135 @@
use monitor_client::entities::{
resource::Resource,
server_template::{
PartialServerTemplateConfig, ServerTemplate,
ServerTemplateConfig, ServerTemplateConfigDiff,
ServerTemplateConfigVariant, ServerTemplateListItem,
ServerTemplateListItemInfo, ServerTemplateQuerySpecifics,
},
update::{ResourceTargetVariant, Update},
user::User,
MergePartial, Operation,
};
use mungos::mongodb::{
bson::{to_document, Document},
Collection,
};
use crate::state::db_client;
impl super::MonitorResource for ServerTemplate {
type Config = ServerTemplateConfig;
type PartialConfig = PartialServerTemplateConfig;
type ConfigDiff = ServerTemplateConfigDiff;
type Info = ();
type ListItem = ServerTemplateListItem;
type QuerySpecifics = ServerTemplateQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::ServerTemplate
}
async fn coll(
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
&db_client().await.server_templates
}
async fn to_list_item(
server_template: Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Self::ListItem> {
let (template_type, instance_type) = match server_template.config
{
ServerTemplateConfig::Aws(config) => (
ServerTemplateConfigVariant::Aws.to_string(),
Some(config.instance_type),
),
};
Ok(ServerTemplateListItem {
name: server_template.name,
id: server_template.id,
tags: server_template.tags,
resource_type: ResourceTargetVariant::ServerTemplate,
info: ServerTemplateListItemInfo {
provider: template_type.to_string(),
instance_type,
},
})
}
async fn busy(_id: &String) -> anyhow::Result<bool> {
Ok(false)
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateServerTemplate
}
fn user_can_create(user: &User) -> bool {
user.admin
}
async fn validate_create_config(
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_create(
_created: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateServerTemplate
}
async fn validate_update_config(
_id: &str,
_config: &mut Self::PartialConfig,
_user: &User,
) -> anyhow::Result<()> {
Ok(())
}
fn update_document(
original: Resource<Self::Config, Self::Info>,
config: Self::PartialConfig,
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
let config = original.config.merge_partial(config);
to_document(&config)
}
async fn post_update(
_updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteServerTemplate
}
async fn pre_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
_resource: &Resource<Self::Config, Self::Info>,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
}
+1 -2
View File
@@ -25,8 +25,7 @@ use crate::{
auth::{auth_api_key_check_enabled, auth_jwt_check_enabled},
db::DbClient,
helpers::{
channel::update_channel,
resource::get_user_permission_on_resource,
channel::update_channel, query::get_user_permission_on_resource,
},
state::db_client,
};
+6 -3
View File
@@ -74,9 +74,12 @@ pub struct Version {
pub patch: i32,
}
impl ToString for Version {
fn to_string(&self) -> String {
format!("{}.{}.{}", self.major, self.minor, self.patch)
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"{}.{}.{}",
self.major, self.minor, self.patch
))
}
}
+1 -1
View File
@@ -369,7 +369,7 @@ impl TryFrom<Deployment>
.collect(),
network: value.docker_run_args.network,
restart: value.docker_run_args.restart.into(),
process_args: value
command: value
.docker_run_args
.post_image
.unwrap_or_default(),
-9
View File
@@ -1,9 +0,0 @@
## Examples
```sh
monrun resources up ./resources/prod_hub.toml
```
```sh
monrun exec
```
-46
View File
@@ -1,46 +0,0 @@
[[build]]
name = "test_logger"
tags = ["test"]
config.builder = "mogh-builder"
config.repo = "mbecker20/test_logger"
config.branch = "master"
config.github_account = "mbecker20"
config.docker_account = "mbecker2020"
[[build]]
name = "alert_logger"
tags = ["test"]
config.builder = "mogh-builder"
config.repo = "mbecker20/monitor"
config.branch = "next"
config.build_path = "."
config.dockerfile_path = "bin/alert_logger/Dockerfile"
config.docker_account = "mbecker2020"
[[build]]
name = "update_logger"
tags = ["test"]
config.builder = "mogh-builder"
config.repo = "mbecker20/monitor"
config.branch = "next"
config.build_path = "."
config.dockerfile_path = "bin/update_logger/Dockerfile"
config.docker_account = "mbecker2020"
# BUILDER
[[builder]]
name = "mogh-builder"
description = "the builder for the mogh images"
config.type = "Aws"
config.params.region = "us-east-2"
config.params.instance_type = "c5.2xlarge"
config.params.volume_gb = 20
config.params.ami_id = "ami-01c8bdba63708cde6"
config.params.subnet_id = "subnet-02ae5ad480eacc4bc"
config.params.key_pair_name = "mogh-key"
config.params.assign_public_ip = true
config.params.use_public_ip = false
config.params.port = 8000
config.params.security_group_ids = ["sg-049d98c819f9ace58", "sg-006c0ca638af8eb44"]
config.params.github_accounts = ["mbecker20"]
config.params.docker_accounts = ["mbecker2020"]
-51
View File
@@ -1,51 +0,0 @@
[[deployment]]
name = "hello-world"
tags = ["test"]
config.server = "mogh-server"
config.image.type = "Image"
config.image.params.image = "hello-world"
[[deployment]]
name = "test_logger"
tags = ["test"]
config.server = "mogh-server"
config.image.type = "Build"
config.image.params.build = "test_logger"
[[deployment]]
name = "alert_logger"
tags = ["test"]
config.server = "mogh-server"
config.image.type = "Build"
config.image.params.build = "alert_logger"
[[deployment]]
name = "update_logger"
tags = ["test", "logging"]
config.server = "mogh-server"
config.image.type = "Build"
config.image.params.build = "update_logger"
[[deployment.config.environment]]
variable = "MONITOR_ADDRESS"
value = "http://localhost:9001"
[[deployment]]
name = "vector"
tags = ["logging"]
config.server = "mogh-server"
config.image.type = "Image"
config.image.params.image = "timberio/vector"
[[deployment]]
name = "loki"
tags = ["logging"]
config.server = "mogh-server"
config.image.type = "Image"
config.image.params.image = "grafana/loki"
[[deployment]]
name = "grafana"
tags = ["frontend"]
config.server = "mogh-server"
config.image.type = "Image"
config.image.params.image = "grafana/grafana"
-1
View File
@@ -1 +0,0 @@
-13
View File
@@ -1,13 +0,0 @@
[[server]]
name = "mogh-server"
description = "main mogh server"
tags = ["mogh", "prod"]
config.address = "http://localhost:8001"
config.region = "us-east-2"
[[server]]
name = "testo"
description = "test server 1"
tags = ["mogh", "test"]
config.address = "http://locahost:8120"
config.enabled = false
-28
View File
@@ -1,28 +0,0 @@
[[user_group]]
name = "uso_groupo"
users = ["becker.maxh", "defi moses"]
[[user_group.permission]]
target.type = "Server"
target.id = "mogh-server"
level = "Read"
[[user_group.permission]]
target.type = "Deployment"
target.id = "monitor-core-v1-dev"
level = "Execute"
[[user_group.permission]]
target.type = "Deployment"
target.id = "monitor-frontend-v1-dev"
level = "Execute"
[[user_group.permission]]
target.type = "Build"
target.id = "monitor-core-next"
level = "Execute"
[[user_group.permission]]
target.type = "Build"
target.id = "monitor-frontend-next"
level = "Execute"
-212
View File
@@ -1,212 +0,0 @@
use std::collections::HashMap;
use monitor_client::{
api::write::{UpdateDescription, UpdateTagsOnResource},
entities::{
resource::{Resource, ResourceListItem},
toml::ResourceToml,
update::ResourceTarget,
},
};
use partial_derive2::MaybeNone;
use crate::{cli_args, monitor_client};
pub mod alerter;
pub mod build;
pub mod builder;
pub mod deployment;
pub mod procedure;
pub mod repo;
pub mod server;
type ToUpdate<T> = Vec<(String, ResourceToml<T>)>;
type ToCreate<T> = Vec<ResourceToml<T>>;
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>);
pub trait ResourceSync {
type PartialConfig: std::fmt::Debug
+ Clone
+ Send
+ MaybeNone
+ 'static;
type FullConfig: Clone + Send + 'static;
type FullInfo: Default;
type ListItemInfo: 'static;
fn display() -> &'static str;
fn resource_target(id: String) -> ResourceTarget;
fn name_to_resource(
) -> &'static HashMap<String, ResourceListItem<Self::ListItemInfo>>;
/// Creates the resource and returns created id.
async fn create(
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<String>;
/// Updates the resource at id with the partial config.
async fn update(
id: String,
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<()>;
async fn get(
id: String,
) -> anyhow::Result<Resource<Self::FullConfig, Self::FullInfo>>;
/// Diffs the declared toml (partial) against the full existing config.
/// Removes all fields from toml (partial) that haven't changed.
async fn minimize_update(
original: Self::FullConfig,
update: Self::PartialConfig,
) -> anyhow::Result<Self::PartialConfig>;
async fn get_updates(
resources: Vec<ResourceToml<Self::PartialConfig>>,
) -> anyhow::Result<UpdatesResult<Self::PartialConfig>> {
let map = Self::name_to_resource();
let mut to_create = ToCreate::<Self::PartialConfig>::new();
let mut to_update = ToUpdate::<Self::PartialConfig>::new();
for mut resource in resources {
match map.get(&resource.name).map(|s| s.id.clone()) {
Some(id) => {
// Get the full original config for the resource.
let original = Self::get(id.clone()).await?.config;
// Minimizes updates through diffing.
resource.config =
Self::minimize_update(original, resource.config).await?;
// Only try to update if there are any fields to update.
if !resource.config.is_none() {
to_update.push((id, resource));
}
}
None => {
to_create.push(resource);
}
}
}
let verbose = cli_args().verbose;
if !to_create.is_empty() {
if verbose {
println!("\n{} TO CREATE:\n{to_create:#?}", Self::display(),);
} else {
println!(
"\n{} TO CREATE: {:#?}",
Self::display(),
to_create
.iter()
.map(|item| item.name.as_str())
.collect::<Vec<_>>()
);
}
}
if !to_update.is_empty() {
if verbose {
println!(
"\n{} TO UPDATE:\n{:#?}",
Self::display(),
to_update.iter().map(|(_, r)| r).collect::<Vec<_>>()
);
} else {
println!(
"\n{} TO UPDATE: {}",
Self::display(),
to_update
.iter()
.map(|(_, item)| item.name.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
}
Ok((to_create, to_update))
}
async fn run_updates(
to_create: ToCreate<Self::PartialConfig>,
to_update: ToUpdate<Self::PartialConfig>,
) {
let log_after = !to_update.is_empty() || !to_create.is_empty();
for resource in to_create {
let name = resource.name.clone();
let tags = resource.tags.clone();
let description = resource.description.clone();
let id = match Self::create(resource).await {
Ok(id) => id,
Err(e) => {
warn!(
"failed to create {} {name} | {e:#}",
Self::display(),
);
continue;
}
};
Self::update_tags(id.clone(), &name, tags).await;
Self::update_description(id, description).await;
info!("{} {name} created", Self::display());
}
for (id, resource) in to_update {
// Update resource
let name = resource.name.clone();
let tags = resource.tags.clone();
let description = resource.description.clone();
if let Err(e) = Self::update(id.clone(), resource).await {
warn!("failed to update {} {name} | {e:#}", Self::display());
}
Self::update_tags(id.clone(), &name, tags).await;
Self::update_description(id, description).await;
info!("{} {name} updated", Self::display());
}
if log_after {
info!(
"============ {}s synced ✅ ============",
Self::display()
);
}
}
async fn update_tags(
resource_id: String,
resource_name: &str,
tags: Vec<String>,
) {
// Update tags
if let Err(e) = monitor_client()
.write(UpdateTagsOnResource {
target: Self::resource_target(resource_id),
tags,
})
.await
{
warn!(
"failed to update tags on {} {resource_name} | {e:#}",
Self::display(),
);
}
}
async fn update_description(id: String, description: String) {
if let Err(e) = monitor_client()
.write(UpdateDescription {
target: Self::resource_target(id.clone()),
description,
})
.await
{
warn!("failed to update resource {id} description | {e:#}");
}
}
}
+1 -1
View File
@@ -131,7 +131,7 @@ fn get_version_image_name(
image_name: &str,
version: &Version,
) -> String {
format!("{image_name}:{}", version.to_string())
format!("{image_name}:{version}")
}
fn get_latest_image_name(image_name: &str) -> String {
@@ -197,7 +197,7 @@ pub async fn deploy(
return Log::error("docker login", serialize_error_pretty(&e))
}
};
if let Err(e) = docker_login(
&optional_string(&deployment.config.docker_account),
&docker_token,
@@ -266,7 +266,7 @@ pub fn docker_run_command(
volumes,
ports,
network,
process_args,
command,
restart,
environment,
labels,
@@ -285,9 +285,9 @@ pub fn docker_run_command(
let restart = parse_restart(restart);
let environment = parse_environment(environment);
let labels = parse_labels(labels);
let process_args = parse_process_args(process_args);
let command = parse_command(command);
let extra_args = parse_extra_args(extra_args);
format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{process_args}")
format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{command}")
}
fn parse_conversions(
@@ -321,10 +321,10 @@ fn parse_restart(restart: &RestartMode) -> String {
format!(" --restart {restart}")
}
fn parse_process_args(process_args: &String) -> String {
if process_args.is_empty() {
fn parse_command(command: &str) -> String {
if command.is_empty() {
String::new()
} else {
format!(" {process_args}")
format!(" {command}")
}
}
+1
View File
@@ -1,5 +1,6 @@
[package]
name = "monitor_client"
description = "Client for the monitor build and deployment system"
version.workspace = true
edition.workspace = true
authors.workspace = true
View File
+13
View File
@@ -35,6 +35,19 @@
//! - [execute]: Run actions on monitor resources, eg [execute::RunBuild].
//! - [write]: Requests which alter data, like create / update / delete resources.
//!
//! ## Errors
//!
//! Request errors will be returned with a JSON body containing information about the error.
//! They will have the following common format:
//! ```
//! {
//! "error": "top level error message",
//! "trace": [
//! "first traceback message",
//! "second traceback message"
//! ]
//! }
//! ```
pub mod auth;
pub mod execute;
+1 -2
View File
@@ -23,7 +23,6 @@ impl Busy for DeploymentActionState {
|| self.starting
|| self.stopping
|| self.renaming
|| self.deleting
}
}
@@ -35,7 +34,7 @@ impl Busy for BuildActionState {
impl Busy for RepoActionState {
fn busy(&self) -> bool {
self.cloning || self.pulling || self.updating || self.deleting
self.cloning || self.pulling
}
}
+109 -34
View File
@@ -2,13 +2,14 @@ use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use derive_variants::EnumVariants;
use mungos::mongodb::bson::{doc, Document};
use partial_derive2::{MaybeNone, Partial, PartialDiff};
use partial_derive2::{Diff, MaybeNone, Partial, PartialDiff};
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, Display, EnumString};
use typeshare::typeshare;
use super::resource::{
AddFilters, Resource, ResourceListItem, ResourceQuery,
use super::{
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
MergePartial,
};
#[typeshare]
@@ -56,28 +57,6 @@ pub enum AlerterConfig {
Slack(SlackAlerterConfig),
}
impl PartialDiff<PartialAlerterConfig> for AlerterConfig {
fn partial_diff(
&self,
partial: PartialAlerterConfig,
) -> PartialAlerterConfig {
match self {
AlerterConfig::Custom(original) => match partial {
PartialAlerterConfig::Custom(partial) => {
PartialAlerterConfig::Custom(original.partial_diff(partial))
}
slack => slack,
},
AlerterConfig::Slack(original) => match partial {
PartialAlerterConfig::Slack(partial) => {
PartialAlerterConfig::Slack(original.partial_diff(partial))
}
custom => custom,
},
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, EnumVariants)]
#[variant_derive(
@@ -96,11 +75,81 @@ pub enum PartialAlerterConfig {
Slack(_PartialSlackAlerterConfig),
}
impl MaybeNone for PartialAlerterConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AlerterConfigDiff {
Custom(CustomAlerterConfigDiff),
Slack(SlackAlerterConfigDiff),
}
impl From<AlerterConfigDiff> for PartialAlerterConfig {
fn from(value: AlerterConfigDiff) -> Self {
match value {
AlerterConfigDiff::Custom(diff) => {
PartialAlerterConfig::Custom(diff.into())
}
AlerterConfigDiff::Slack(diff) => {
PartialAlerterConfig::Slack(diff.into())
}
}
}
}
impl Diff for AlerterConfigDiff {
fn iter_field_diffs(
&self,
) -> impl Iterator<Item = partial_derive2::FieldDiff> {
match self {
AlerterConfigDiff::Custom(diff) => {
diff.iter_field_diffs().collect::<Vec<_>>().into_iter()
}
AlerterConfigDiff::Slack(diff) => {
diff.iter_field_diffs().collect::<Vec<_>>().into_iter()
}
}
}
}
impl PartialDiff<PartialAlerterConfig, AlerterConfigDiff>
for AlerterConfig
{
fn partial_diff(
&self,
partial: PartialAlerterConfig,
) -> AlerterConfigDiff {
match self {
AlerterConfig::Custom(original) => match partial {
PartialAlerterConfig::Custom(partial) => {
AlerterConfigDiff::Custom(original.partial_diff(partial))
}
PartialAlerterConfig::Slack(partial) => {
let full: SlackAlerterConfig = partial.into();
AlerterConfigDiff::Slack(SlackAlerterConfigDiff {
url: Some((original.url.clone(), full.url)),
enabled: Some((original.enabled, full.enabled)),
})
}
},
AlerterConfig::Slack(original) => match partial {
PartialAlerterConfig::Slack(partial) => {
AlerterConfigDiff::Slack(original.partial_diff(partial))
}
PartialAlerterConfig::Custom(partial) => {
let full: CustomAlerterConfig = partial.into();
AlerterConfigDiff::Custom(CustomAlerterConfigDiff {
url: Some((original.url.clone(), full.url)),
enabled: Some((original.enabled, full.enabled)),
})
}
},
}
}
}
impl MaybeNone for AlerterConfigDiff {
fn is_none(&self) -> bool {
match self {
PartialAlerterConfig::Custom(config) => config.is_none(),
PartialAlerterConfig::Slack(config) => config.is_none(),
AlerterConfigDiff::Custom(config) => config.is_none(),
AlerterConfigDiff::Slack(config) => config.is_none(),
}
}
}
@@ -131,8 +180,9 @@ impl From<AlerterConfig> for PartialAlerterConfig {
}
}
impl AlerterConfig {
pub fn merge_partial(
impl MergePartial for AlerterConfig {
type Partial = PartialAlerterConfig;
fn merge_partial(
self,
partial: PartialAlerterConfig,
) -> AlerterConfig {
@@ -171,13 +221,21 @@ pub type _PartialCustomAlerterConfig = PartialCustomAlerterConfig;
#[partial(skip_serializing_none, from, diff)]
pub struct CustomAlerterConfig {
/// The http/s endpoint to send the POST to
#[partial_default(String::from("http://localhost:7000"))]
#[serde(default = "default_custom_url")]
#[builder(default = "default_custom_url()")]
#[partial_default(default_custom_url())]
pub url: String,
/// Whether the alerter is enabled
#[serde(default)]
#[builder(default)]
pub enabled: bool,
}
fn default_custom_url() -> String {
String::from("http://localhost:7000")
}
#[typeshare(serialized_as = "Partial<SlackAlerterConfig>")]
pub type _PartialSlackAlerterConfig = PartialSlackAlerterConfig;
@@ -188,15 +246,32 @@ pub type _PartialSlackAlerterConfig = PartialSlackAlerterConfig;
#[partial(skip_serializing_none, from, diff)]
pub struct SlackAlerterConfig {
/// The slack app url
#[partial_default(String::from(
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
))]
#[serde(default = "default_slack_url")]
#[builder(default = "default_slack_url()")]
#[partial_default(default_slack_url())]
pub url: String,
/// Whether the alerter is enabled
#[serde(default)]
#[builder(default)]
pub enabled: bool,
}
impl Default for SlackAlerterConfig {
fn default() -> Self {
Self {
url: default_slack_url(),
enabled: false,
}
}
}
fn default_slack_url() -> String {
String::from(
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
)
}
#[typeshare]
pub type AlerterQuery = ResourceQuery<AlerterQuerySpecifics>;
+98 -32
View File
@@ -1,12 +1,13 @@
use derive_builder::Builder;
use derive_variants::EnumVariants;
use partial_derive2::{MaybeNone, Partial, PartialDiff};
use partial_derive2::{Diff, MaybeNone, Partial, PartialDiff};
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};
use typeshare::typeshare;
use super::resource::{
AddFilters, Resource, ResourceListItem, ResourceQuery,
use super::{
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
MergePartial,
};
#[typeshare]
@@ -45,28 +46,6 @@ pub enum BuilderConfig {
Aws(AwsBuilderConfig),
}
impl PartialDiff<PartialBuilderConfig> for BuilderConfig {
fn partial_diff(
&self,
partial: PartialBuilderConfig,
) -> PartialBuilderConfig {
match self {
BuilderConfig::Server(original) => match partial {
PartialBuilderConfig::Server(partial) => {
PartialBuilderConfig::Server(original.partial_diff(partial))
}
aws => aws,
},
BuilderConfig::Aws(original) => match partial {
PartialBuilderConfig::Aws(partial) => {
PartialBuilderConfig::Aws(original.partial_diff(partial))
}
server => server,
},
}
}
}
/// Partial representation of [BuilderConfig]
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, EnumVariants)]
@@ -85,11 +64,76 @@ pub enum PartialBuilderConfig {
Aws(_PartialAwsBuilderConfig),
}
impl MaybeNone for PartialBuilderConfig {
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BuilderConfigDiff {
Server(ServerBuilderConfigDiff),
Aws(AwsBuilderConfigDiff),
}
impl From<BuilderConfigDiff> for PartialBuilderConfig {
fn from(value: BuilderConfigDiff) -> Self {
match value {
BuilderConfigDiff::Server(diff) => {
PartialBuilderConfig::Server(diff.into())
}
BuilderConfigDiff::Aws(diff) => {
PartialBuilderConfig::Aws(diff.into())
}
}
}
}
impl Diff for BuilderConfigDiff {
fn iter_field_diffs(
&self,
) -> impl Iterator<Item = partial_derive2::FieldDiff> {
match self {
BuilderConfigDiff::Server(diff) => {
diff.iter_field_diffs().collect::<Vec<_>>().into_iter()
}
BuilderConfigDiff::Aws(diff) => {
diff.iter_field_diffs().collect::<Vec<_>>().into_iter()
}
}
}
}
impl PartialDiff<PartialBuilderConfig, BuilderConfigDiff>
for BuilderConfig
{
fn partial_diff(
&self,
partial: PartialBuilderConfig,
) -> BuilderConfigDiff {
match self {
BuilderConfig::Server(original) => match partial {
PartialBuilderConfig::Server(partial) => {
BuilderConfigDiff::Server(original.partial_diff(partial))
}
PartialBuilderConfig::Aws(partial) => {
let default = AwsBuilderConfig::default();
BuilderConfigDiff::Aws(default.partial_diff(partial))
}
},
BuilderConfig::Aws(original) => match partial {
PartialBuilderConfig::Aws(partial) => {
BuilderConfigDiff::Aws(original.partial_diff(partial))
}
PartialBuilderConfig::Server(partial) => {
let default = ServerBuilderConfig::default();
BuilderConfigDiff::Server(default.partial_diff(partial))
}
},
}
}
}
impl MaybeNone for BuilderConfigDiff {
fn is_none(&self) -> bool {
match self {
PartialBuilderConfig::Server(config) => config.is_none(),
PartialBuilderConfig::Aws(config) => config.is_none(),
BuilderConfigDiff::Server(config) => config.is_none(),
BuilderConfigDiff::Aws(config) => config.is_none(),
}
}
}
@@ -120,8 +164,9 @@ impl From<BuilderConfig> for PartialBuilderConfig {
}
}
impl BuilderConfig {
pub fn merge_partial(
impl MergePartial for BuilderConfig {
type Partial = PartialBuilderConfig;
fn merge_partial(
self,
partial: PartialBuilderConfig,
) -> BuilderConfig {
@@ -178,7 +223,9 @@ pub type _PartialServerBuilderConfig = PartialServerBuilderConfig;
/// Configuration for a monitor server builder.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, Builder, Partial,
)]
#[partial_derive(Serialize, Deserialize, Debug, Clone, Default)]
#[partial(skip_serializing_none, from, diff)]
pub struct ServerBuilderConfig {
@@ -193,7 +240,7 @@ pub type _PartialAwsBuilderConfig = PartialAwsBuilderConfig;
/// Configuration for an AWS builder.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)]
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Partial)]
#[partial_derive(Serialize, Deserialize, Debug, Clone, Default)]
#[partial(skip_serializing_none, from, diff)]
pub struct AwsBuilderConfig {
@@ -248,6 +295,25 @@ pub struct AwsBuilderConfig {
pub docker_accounts: Vec<String>,
}
impl Default for AwsBuilderConfig {
fn default() -> Self {
Self {
region: aws_default_region(),
instance_type: aws_default_instance_type(),
volume_gb: aws_default_volume_gb(),
port: default_port(),
ami_id: Default::default(),
subnet_id: Default::default(),
security_group_ids: Default::default(),
key_pair_name: Default::default(),
assign_public_ip: false,
use_public_ip: false,
github_accounts: Default::default(),
docker_accounts: Default::default(),
}
}
}
impl AwsBuilderConfig {
pub fn builder() -> AwsBuilderConfigBuilder {
AwsBuilderConfigBuilder::default()
@@ -233,7 +233,7 @@ fn default_periphery_port() -> u16 {
}
fn default_repo_dir() -> PathBuf {
"/repos".parse().unwrap()
"/etc/monitor/repos".parse().unwrap()
}
fn default_stats_refresh_interval() -> Timelength {
+2 -3
View File
@@ -128,10 +128,10 @@ pub struct DeploymentConfig {
/// This is interpolated at the end of the `docker run` command,
/// which means they are either passed to the containers inner process,
/// or replaces the container command, depending on use of ENTRYPOINT or CMD in dockerfile.
/// Empty is no process args.
/// Empty is no command.
#[serde(default)]
#[builder(default)]
pub process_args: String,
pub command: String,
/// Extra args which are interpolated into the `docker run` command,
/// and affect the container configuration.
@@ -374,7 +374,6 @@ pub struct DeploymentActionState {
pub starting: bool,
pub removing: bool,
pub renaming: bool,
pub deleting: bool,
}
#[typeshare]
+11 -3
View File
@@ -43,6 +43,11 @@ pub type _Serror = Serror;
)]
pub struct NoData {}
pub trait MergePartial: Sized {
type Partial;
fn merge_partial(self, partial: Self::Partial) -> Self;
}
pub fn all_logs_success(logs: &[update::Log]) -> bool {
for log in logs {
if !log.success {
@@ -147,9 +152,12 @@ pub struct Version {
pub patch: i32,
}
impl ToString for Version {
fn to_string(&self) -> String {
format!("{}.{}.{}", self.major, self.minor, self.patch)
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"{}.{}.{}",
self.major, self.minor, self.patch
))
}
}
-2
View File
@@ -91,8 +91,6 @@ fn default_branch() -> String {
pub struct RepoActionState {
pub cloning: bool,
pub pulling: bool,
pub updating: bool,
pub deleting: bool,
}
#[typeshare]
+10 -2
View File
@@ -28,13 +28,16 @@ pub struct Resource<Config, Info: Default = ()> {
#[builder(setter(skip))]
pub id: MongoId,
///
/// The resource name.
/// This is guaranteed unique among others of the same resource type.
pub name: String,
/// A description for the resource
#[serde(default)]
#[builder(default)]
pub description: String,
/// When description last updated
#[serde(default)]
#[builder(setter(skip))]
pub updated_at: I64,
@@ -44,23 +47,28 @@ pub struct Resource<Config, Info: Default = ()> {
#[builder(default)]
pub tags: Vec<String>,
/// Resource-specific information (not user configurable).
#[serde(default)]
#[builder(setter(skip))]
pub info: Info,
/// Resource-specific configuration.
pub config: Config,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResourceListItem<Info> {
/// The resource id
pub id: String,
/// The resource type, ie `Server` or `Deployment`
#[serde(rename = "type")]
pub resource_type: ResourceTargetVariant,
/// The resource name
pub name: String,
pub created_at: I64,
/// Tag Ids
pub tags: Vec<String>,
/// Resource specific info
pub info: Info,
}
+1 -1
View File
@@ -143,7 +143,7 @@ impl ServerConfig {
}
fn default_enabled() -> bool {
true
false
}
fn default_stats_monitoring() -> bool {
+138 -38
View File
@@ -2,7 +2,7 @@ use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use derive_variants::EnumVariants;
use mungos::mongodb::bson::{doc, Document};
use partial_derive2::{MaybeNone, Partial, PartialDiff};
use partial_derive2::{Diff, MaybeNone, Partial, PartialDiff};
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, Display, EnumString};
use typeshare::typeshare;
@@ -10,6 +10,7 @@ use typeshare::typeshare;
use super::{
builder::AwsBuilderConfig,
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
MergePartial,
};
#[typeshare]
@@ -46,25 +47,6 @@ pub enum ServerTemplateConfig {
Aws(AwsServerTemplateConfig),
}
impl PartialDiff<PartialServerTemplateConfig>
for ServerTemplateConfig
{
fn partial_diff(
&self,
partial: PartialServerTemplateConfig,
) -> PartialServerTemplateConfig {
match self {
ServerTemplateConfig::Aws(original) => match partial {
PartialServerTemplateConfig::Aws(partial) => {
PartialServerTemplateConfig::Aws(
original.partial_diff(partial),
)
}
},
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, EnumVariants)]
#[variant_derive(
@@ -82,10 +64,55 @@ pub enum PartialServerTemplateConfig {
Aws(_PartialAwsServerTemplateConfig),
}
impl MaybeNone for PartialServerTemplateConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerTemplateConfigDiff {
Aws(AwsServerTemplateConfigDiff),
}
impl From<ServerTemplateConfigDiff> for PartialServerTemplateConfig {
fn from(value: ServerTemplateConfigDiff) -> Self {
match value {
ServerTemplateConfigDiff::Aws(diff) => {
PartialServerTemplateConfig::Aws(diff.into())
}
}
}
}
impl Diff for ServerTemplateConfigDiff {
fn iter_field_diffs(
&self,
) -> impl Iterator<Item = partial_derive2::FieldDiff> {
match self {
ServerTemplateConfigDiff::Aws(diff) => diff.iter_field_diffs(),
}
}
}
impl
PartialDiff<PartialServerTemplateConfig, ServerTemplateConfigDiff>
for ServerTemplateConfig
{
fn partial_diff(
&self,
partial: PartialServerTemplateConfig,
) -> ServerTemplateConfigDiff {
match self {
ServerTemplateConfig::Aws(original) => match partial {
PartialServerTemplateConfig::Aws(partial) => {
ServerTemplateConfigDiff::Aws(
original.partial_diff(partial),
)
}
},
}
}
}
impl MaybeNone for ServerTemplateConfigDiff {
fn is_none(&self) -> bool {
match self {
PartialServerTemplateConfig::Aws(config) => config.is_none(),
ServerTemplateConfigDiff::Aws(config) => config.is_none(),
}
}
}
@@ -112,8 +139,9 @@ impl From<ServerTemplateConfig> for PartialServerTemplateConfig {
}
}
impl ServerTemplateConfig {
pub fn merge_partial(
impl MergePartial for ServerTemplateConfig {
type Partial = PartialServerTemplateConfig;
fn merge_partial(
self,
partial: PartialServerTemplateConfig,
) -> ServerTemplateConfig {
@@ -141,6 +169,7 @@ impl ServerTemplateConfig {
.use_public_ip
.unwrap_or(config.use_public_ip),
port: partial.port.unwrap_or(config.port),
user_data: partial.user_data.unwrap_or(config.user_data),
};
ServerTemplateConfig::Aws(config)
}
@@ -160,24 +189,32 @@ pub type _PartialAwsServerTemplateConfig =
#[partial(skip_serializing_none, from, diff)]
pub struct AwsServerTemplateConfig {
/// The aws region to launch the server in, eg. us-east-1
#[serde(default = "default_region")]
#[builder(default = "default_region()")]
#[partial_default(default_region())]
pub region: String,
/// The instance type to launch, eg. c5.2xlarge
#[serde(default = "default_instance_type")]
#[builder(default = "default_instance_type()")]
#[partial_default(default_instance_type())]
pub instance_type: String,
/// Specify the EBS volumes to attach.
pub volumes: Vec<AwsVolume>,
/// Specify the ami id to use. Must be set up to start the periphery binary on startup.
pub ami_id: String,
/// The subnet to assign to the instance.
pub subnet_id: String,
/// The security groups to give to the instance.
pub security_group_ids: Vec<String>,
/// The key pair name to give to the instance in case SSH access required.
pub key_pair_name: String,
/// Assign a public ip to the instance. Depending on how your network is
/// setup, this may be required for the instance to reach the public internet.
#[serde(default = "default_assign_public_ip")]
#[builder(default = "default_assign_public_ip()")]
#[partial_default(default_assign_public_ip())]
pub assign_public_ip: bool,
/// Use the instances public ip as the address for the server.
/// Could be used when build instances are created in another non-interconnected network to the core api.
#[serde(default = "default_use_public_ip")]
#[builder(default = "default_use_public_ip()")]
#[partial_default(default_use_public_ip())]
pub use_public_ip: bool,
/// The port periphery will be running on in AMI.
/// Default: `8120`
@@ -185,6 +222,45 @@ pub struct AwsServerTemplateConfig {
#[builder(default = "default_port()")]
#[partial_default(default_port())]
pub port: i32,
/// The user data to deploy the instance with.
#[serde(default)]
#[builder(default)]
pub user_data: String,
/// The security groups to give to the instance.
#[serde(default)]
#[builder(default)]
pub security_group_ids: Vec<String>,
/// Specify the EBS volumes to attach.
#[serde(default = "default_volumes")]
#[builder(default = "default_volumes()")]
#[partial_default(default_volumes())]
pub volumes: Vec<AwsVolume>,
}
fn default_region() -> String {
String::from("us-east-1")
}
fn default_instance_type() -> String {
String::from("t3.small")
}
fn default_assign_public_ip() -> bool {
true
}
fn default_use_public_ip() -> bool {
false
}
fn default_volumes() -> Vec<AwsVolume> {
vec![AwsVolume {
device_name: "/dev/sda1".to_string(),
size_gb: 20,
volume_type: AwsVolumeType::Gp2,
iops: 0,
throughput: 0,
}]
}
fn default_port() -> i32 {
@@ -200,12 +276,35 @@ pub struct AwsVolume {
pub device_name: String,
/// The size of the volume in GB
pub size_gb: i32,
/// The type of volume, eg gp2, gp3, io1
pub volume_type: Option<String>,
/// The iops of the volume, or AWS default.
pub iops: Option<i32>,
/// The throughput of the volume, or AWS default.
pub throughput: Option<i32>,
/// The type of volume. Options: gp2, gp3, io1, io2.
pub volume_type: AwsVolumeType,
/// The iops of the volume, or 0 for AWS default.
pub iops: i32,
/// The throughput of the volume, or 0 for AWS default.
pub throughput: i32,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
Display,
AsRefStr,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum AwsVolumeType {
#[default]
Gp2,
Gp3,
Io1,
Io2,
}
impl AwsServerTemplateConfig {
@@ -214,11 +313,11 @@ impl AwsServerTemplateConfig {
region: value.region.clone(),
instance_type: value.instance_type.clone(),
volumes: vec![AwsVolume {
size_gb: value.volume_gb,
device_name: "/dev/sda1".to_string(),
volume_type: None,
iops: None,
throughput: None,
size_gb: value.volume_gb,
volume_type: AwsVolumeType::Gp2,
iops: 0,
throughput: 0,
}],
ami_id: value.ami_id.clone(),
subnet_id: value.subnet_id.clone(),
@@ -227,6 +326,7 @@ impl AwsServerTemplateConfig {
assign_public_ip: value.assign_public_ip,
use_public_ip: value.use_public_ip,
port: value.port,
user_data: Default::default(),
}
}
}
+6 -1
View File
@@ -79,9 +79,10 @@ pub struct ResourcesToml {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceToml<PartialConfig> {
///
/// The resource name. Required
pub name: String,
/// The resource description.
#[serde(default)]
pub description: String,
@@ -89,16 +90,20 @@ pub struct ResourceToml<PartialConfig> {
#[serde(default)]
pub tags: Vec<String>,
/// Resource specific configuration
pub config: PartialConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserGroupToml {
/// User group name
pub name: String,
/// Users in the group
#[serde(default)]
pub users: Vec<String>,
/// Permissions given to the group
#[serde(default, rename = "permission")]
pub permissions: Vec<PermissionToml>,
}

Some files were not shown because too many files have changed in this diff Show More