forked from github-starred/komodo
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84fb603951 | ||
|
|
55bac0dd13 | ||
|
|
b143f42363 | ||
|
|
007efd136a | ||
|
|
b329767f9e | ||
|
|
b4231957d5 | ||
|
|
b4dc446f95 | ||
|
|
c92515cecc | ||
|
|
f3712feea2 | ||
|
|
0e81d17860 | ||
|
|
c3f1557b83 | ||
|
|
5f88e4b436 | ||
|
|
473c6b3867 | ||
|
|
c10edaa5d1 | ||
|
|
9418a6d963 | ||
|
|
57646b750f | ||
|
|
0d57f9411c | ||
|
|
7d396dd539 | ||
|
|
bfe762b71a | ||
|
|
16ede84bac | ||
|
|
4524db94db | ||
|
|
580dab4acd | ||
|
|
645382856a | ||
|
|
5c4e6a6dbb | ||
|
|
66810e1efb | ||
|
|
69a84882f0 | ||
|
|
41648436a5 | ||
|
|
083a88aa7b | ||
|
|
750f95c90d | ||
|
|
129f3ecd82 | ||
|
|
1b754f80ab | ||
|
|
968a882012 | ||
|
|
696ebdb26f | ||
|
|
8fee04607d | ||
|
|
6fe250244b | ||
|
|
b530af0eec | ||
|
|
21e9361079 | ||
|
|
524d2d956b | ||
|
|
aca9633941 | ||
|
|
32e1bd2dda | ||
|
|
cb363d1559 | ||
|
|
63eb74b9c8 | ||
|
|
bbcc27704f | ||
|
|
0aa9513dd0 | ||
|
|
26b216b478 | ||
|
|
166299bb57 | ||
|
|
03c47eb3dc | ||
|
|
1fcb4ad085 | ||
|
|
f51af8fbe1 | ||
|
|
4a975e1b92 | ||
|
|
ba556e3284 | ||
|
|
299a326942 | ||
|
|
a5d4b9aefb | ||
|
|
40b820ae42 | ||
|
|
7028bf2996 | ||
|
|
75ebd0e6c0 | ||
|
|
426153df66 | ||
|
|
5bd423a6a6 | ||
|
|
c24131d383 | ||
|
|
9f54b6c26a | ||
|
|
ab8ae51ece | ||
|
|
ef2a83ff16 | ||
|
|
7872771aee | ||
|
|
b12cf858d8 | ||
|
|
38dba91c3a | ||
|
|
ea8136aa57 | ||
|
|
f956e12e28 | ||
|
|
207ea52b95 | ||
|
|
caf28d3a26 | ||
|
|
8fff45649d | ||
|
|
de5df70e11 | ||
|
|
3df010ac2a | ||
|
|
2d3beb708e | ||
|
|
1dc22d01c4 | ||
|
|
eb029d0408 | ||
|
|
f926932181 | ||
|
|
cc96d80c6a | ||
|
|
144b49495c | ||
|
|
de9354bdc7 | ||
|
|
38bfee84d7 | ||
|
|
ec33d9fb9e | ||
|
|
0a66937b1d | ||
|
|
43cc0c3bc1 | ||
|
|
c14b395c70 | ||
|
|
7b8529a7c6 | ||
|
|
547c089581 | ||
|
|
4fe5e461b3 | ||
|
|
edfb873f7c | ||
|
|
5ef5294c44 | ||
|
|
5d3c50e04f | ||
|
|
f10efbb5ba | ||
|
|
39ce98161b | ||
|
|
cff6e79eee | ||
|
|
dedf22ede8 | ||
|
|
6955b92a99 | ||
|
|
5c63eeab02 | ||
|
|
4c14a4ae20 | ||
|
|
29fd856a2d | ||
|
|
195bdbd94a | ||
|
|
298ccd945c | ||
|
|
436e4e79e9 | ||
|
|
8b8c89d976 | ||
|
|
25c8d25636 | ||
|
|
ea242de2e4 | ||
|
|
be03547407 | ||
|
|
9c0d28b311 | ||
|
|
f269deb99c | ||
|
|
3df8163131 | ||
|
|
33a16a9bd2 | ||
|
|
215e7d1bdc | ||
|
|
25e0905c0c | ||
|
|
1c07ccea85 | ||
|
|
405ec1b8cc | ||
|
|
4f212bd06f | ||
|
|
074f4ea2db | ||
|
|
c9abccaf02 | ||
|
|
6428fa6de2 | ||
|
|
883f54431d | ||
|
|
28dc030e2b | ||
|
|
145d933e63 | ||
|
|
9772ca1a1c | ||
|
|
4059b69201 | ||
|
|
8e175ea5a1 | ||
|
|
d931b8b4e7 | ||
|
|
0982800ad2 | ||
|
|
4382ad0b3b | ||
|
|
e7891f7870 | ||
|
|
6bada46841 | ||
|
|
eae6cbd228 | ||
|
|
a0ee6180b2 | ||
|
|
3ce3de8768 | ||
|
|
6c46993b61 | ||
|
|
fbd9d14aaa |
Generated
+239
-137
@@ -32,7 +32,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
@@ -41,7 +41,6 @@ dependencies = [
|
||||
"logger",
|
||||
"monitor_client",
|
||||
"serde",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -176,9 +175,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.0"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1234b742ac4a40a7d3459c6e3c99818271976a5a6ae3732cb415f4a9a94da7b6"
|
||||
checksum = "2368fb843e9eec932f7789d64d0e05850f4a79067188c657e572f1f5a7589df0"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -219,9 +218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.2.2"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75588e7ee5e8496eed939adac2035a6dbab9f7eb2acdd9ab2d31856dab6f3955"
|
||||
checksum = "9a4a5e448145999d7de17bf44a886900ecb834953408dae8aaf90465ce91c1dd"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
@@ -242,9 +241,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ec2"
|
||||
version = "1.46.0"
|
||||
version = "1.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a9b096506d42000f97eb8bc8a6e3ec16ffc607b8b5caaa50814c8544a3bf97"
|
||||
checksum = "e2fd9674e7f6fd95c7df47694c7826ea34d0c997529b62708df886de4bab6887"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -265,10 +264,32 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.28.0"
|
||||
name = "aws-sdk-ecr"
|
||||
version = "1.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee458e39982214c70432e87756227de474318283137c38d84be0aeebc77acced"
|
||||
checksum = "23c7734480f3e434de256544f534cda7ac08742808dd485f64ab86782cbb5c48"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8aee358b755b2738b3ffb8a5b54ee991b28c8a07483a0ff7d49a58305cc2609"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -288,9 +309,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.29.0"
|
||||
version = "1.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75562f0e82b87f41210c1de110f3fb169e189f962f4b2e94fd7f370d70e063c"
|
||||
checksum = "1d5ce026f0ae73e06b20be5932150dd0e9b063417fd7c3acf5ca97018b9cbd64"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -310,9 +331,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.28.0"
|
||||
version = "1.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a422d2f3080421ed23630ada0e474c76e4279c18b4a379bff2f1062e05cef466"
|
||||
checksum = "c820248cb02e4ea83630ad2e43d0721cdbccedba5ac902cd0b6fb84d7271f205"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -333,9 +354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57"
|
||||
checksum = "31eed8d45759b2c5fe7fd304dd70739060e9e0de509209036eabea14d0720cce"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-http",
|
||||
@@ -406,9 +427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.5.4"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "607e8b53aeb2bc23fb332159d72a69650cd9643c161d76cd3b7f88ac00b5a1bb"
|
||||
checksum = "db83b08939838d18e33b5dbaf1a0f048f28c10bd28071ab7ce6f245451855414"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
@@ -420,21 +441,22 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"http-body 1.0.0",
|
||||
"httparse",
|
||||
"hyper 0.14.28",
|
||||
"hyper-rustls",
|
||||
"hyper-rustls 0.24.2",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b7d790d553d163c7d80a4e06e2906bf24b9172c9ebe045fc3a274e9358ab7bb"
|
||||
checksum = "1b570ea39eb95bd32543f6e4032bce172cb6209b9bc8c83c770d08169e875afc"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-types",
|
||||
@@ -449,9 +471,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.1.10"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b6764ba7e1c5ede1c9f9e4046645534f06c2581402461c559b481a420330a83"
|
||||
checksum = "cfe321a6b21f5d8eabd0ade9c55d3d0335f3c3157fc2b3e87f05f34b539e4df5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -484,15 +506,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-types"
|
||||
version = "1.3.0"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fa328e19c849b20ef7ada4c9b581dd12351ff35ecc7642d06e69de4f98407c"
|
||||
checksum = "2009a9733865d0ebf428a314440bbe357cc10d0c16d86a8e15d32e9b47c1e80e"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"http 0.2.12",
|
||||
"rustc_version 0.4.0",
|
||||
"tracing",
|
||||
]
|
||||
@@ -781,9 +802,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bson"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2"
|
||||
checksum = "d8a88e82b9106923b5c4d6edfca9e7db958d4e98a478ec115022e81b9b38e2c8"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64 0.13.1",
|
||||
@@ -867,9 +888,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -877,9 +898,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -889,9 +910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -923,7 +944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
@@ -1299,6 +1320,13 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -1423,10 +1451,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
"formatting",
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
"tracing",
|
||||
@@ -1482,6 +1511,15 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@@ -1647,7 +1685,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1700,10 +1738,27 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.28",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"hyper 1.3.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.10",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1760,7 +1815,7 @@ dependencies = [
|
||||
"http-body 1.0.0",
|
||||
"hyper 1.3.1",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
@@ -1869,7 +1924,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
|
||||
dependencies = [
|
||||
"socket2 0.5.7",
|
||||
"socket2",
|
||||
"widestring",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg 0.50.0",
|
||||
@@ -1968,7 +2023,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2037,7 +2092,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2047,7 +2102,6 @@ dependencies = [
|
||||
"monitor_client",
|
||||
"mungos",
|
||||
"serde",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2090,9 +2144,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongo_indexed"
|
||||
version = "0.3.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e837961dc668ca0d6e298066f22c1c9242bdfe7cc473b18f5e3d960f27e6adb"
|
||||
checksum = "202ab1775c25b55f035ae5a5e10c6505ddcb8319159bca055ebe3790542478e4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"mongo_indexed_derive",
|
||||
@@ -2102,9 +2156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongo_indexed_derive"
|
||||
version = "0.3.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b1bfd94b65c8adb78d87177ccb5754204c175098be1057e7ad25282753dc4e8"
|
||||
checksum = "177b97b43773866e7ed1aa023acdbe438d973ce181d64d9769c303c002b3eaf5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2113,9 +2167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongodb"
|
||||
version = "2.8.2"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef206acb1b72389b49bc9985efe7eb1f8a9bb18e5680d262fac26c07f44025f1"
|
||||
checksum = "bb4af699cbb12a221e978134999b16cbf030d964c3a7cd6c4af2584734190e58"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
@@ -2130,26 +2184,27 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac",
|
||||
"lazy_static",
|
||||
"md-5",
|
||||
"mongodb-internal-macros",
|
||||
"once_cell",
|
||||
"pbkdf2",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rustc_version_runtime",
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_with 1.14.0",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"socket2 0.4.10",
|
||||
"socket2",
|
||||
"stringprep",
|
||||
"strsim 0.10.0",
|
||||
"take_mut",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
"trust-dns-proto",
|
||||
"trust-dns-resolver",
|
||||
@@ -2158,9 +2213,20 @@ dependencies = [
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mongodb-internal-macros"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1159486b5e1993138c385369c3c46f5608cb0b1c53734e503538366f3c4cedcc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2180,7 +2246,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2195,7 +2261,7 @@ dependencies = [
|
||||
"futures",
|
||||
"mongo_indexed",
|
||||
"partial_derive2",
|
||||
"reqwest 0.12.4",
|
||||
"reqwest 0.12.5",
|
||||
"resolver_api",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2203,7 +2269,7 @@ dependencies = [
|
||||
"strum 0.26.2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.22.0",
|
||||
"tokio-tungstenite 0.23.1",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"typeshare",
|
||||
@@ -2212,12 +2278,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
"aws-config",
|
||||
"aws-sdk-ec2",
|
||||
"aws-sdk-ecr",
|
||||
"axum 0.7.5",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
@@ -2225,6 +2292,7 @@ dependencies = [
|
||||
"derive_variants",
|
||||
"dotenv",
|
||||
"envy",
|
||||
"formatting",
|
||||
"futures",
|
||||
"git",
|
||||
"hex",
|
||||
@@ -2235,22 +2303,25 @@ dependencies = [
|
||||
"mongo_indexed",
|
||||
"monitor_client",
|
||||
"mungos",
|
||||
"ordered_hash_map",
|
||||
"parse_csl",
|
||||
"partial_derive2",
|
||||
"periphery_client",
|
||||
"rand",
|
||||
"reqwest 0.12.4",
|
||||
"reqwest 0.12.5",
|
||||
"resolver_api",
|
||||
"run_command",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serror",
|
||||
"sha2",
|
||||
"slack_client_rs",
|
||||
"strum 0.26.2",
|
||||
"svi",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"toml_pretty",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -2261,7 +2332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2272,6 +2343,7 @@ dependencies = [
|
||||
"command",
|
||||
"dotenv",
|
||||
"envy",
|
||||
"formatting",
|
||||
"git",
|
||||
"logger",
|
||||
"merge_config_files",
|
||||
@@ -2285,7 +2357,6 @@ dependencies = [
|
||||
"serror",
|
||||
"svi",
|
||||
"sysinfo",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
@@ -2293,15 +2364,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mungos"
|
||||
version = "0.5.6"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ba5223d327a4869d60eee6aaf505e88109c2299b6e4b26ff26f9f2e1d4cb65a"
|
||||
checksum = "9c89188a577f4e4464d83203336cc57ee5c835bf0e52ce3748f0abc090466ed8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"envy",
|
||||
"futures",
|
||||
"mongodb",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2509,6 +2581,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered_hash_map"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.5.1"
|
||||
@@ -2587,11 +2669,11 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
"reqwest 0.12.4",
|
||||
"reqwest 0.12.5",
|
||||
"resolver_api",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2809,9 +2891,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.4"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
||||
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -2823,6 +2905,7 @@ dependencies = [
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.3.1",
|
||||
"hyper-rustls 0.27.2",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@@ -2837,7 +2920,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 0.1.2",
|
||||
"sync_wrapper 1.0.1",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
@@ -2861,9 +2944,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "resolver_api"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4e8831e742662de68dbce8ca6243d1f0a1a5a295e1b3fa9ad996f215d1645b5"
|
||||
checksum = "e86d2e3a6e01f92f8f964bc16a5e579028c02fa625612e5e1f159a6474e47969"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"resolver_api_derive",
|
||||
@@ -2962,10 +3045,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.3"
|
||||
@@ -3013,6 +3109,17 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
@@ -3125,9 +3232,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
version = "1.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"itoa",
|
||||
@@ -3270,16 +3377,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -3289,18 +3386,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-tokio"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"libc",
|
||||
"signal-hook",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@@ -3331,16 +3416,6 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.7"
|
||||
@@ -3549,22 +3624,9 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termination_signal"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16e2993a3469a2732b1c07df31a0227f2354500779c175f42af4c21693f1ad8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenv",
|
||||
@@ -3670,7 +3732,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.5.7",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -3712,7 +3774,18 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.10",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -3736,19 +3809,19 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
"tungstenite 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.22.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d46baf930138837d65e25e3b33be49c9228579a6135dbf756b5cb9e4283e7cef"
|
||||
checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
"tungstenite 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3767,9 +3840,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3788,9 +3861,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.13"
|
||||
version = "0.22.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
@@ -3799,6 +3872,18 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_pretty"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd412f79539d72f97ac0ec9905b0178be1be4f255ddc2ae6f1ff3f7b234bb870"
|
||||
dependencies = [
|
||||
"ordered_hash_map",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.11.0"
|
||||
@@ -4042,6 +4127,24 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.10.0"
|
||||
@@ -4119,12 +4222,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
"monitor_client",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -4160,9 +4262,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
|
||||
+18
-15
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,8 +15,9 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.6.2"
|
||||
monitor_client = "1.10.0"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
command = { path = "lib/command" }
|
||||
logger = { path = "lib/logger" }
|
||||
git = { path = "lib/git" }
|
||||
@@ -28,19 +29,19 @@ slack = { version = "0.1.0", package = "slack_client_rs" }
|
||||
derive_default_builder = "0.1.8"
|
||||
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.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "0.3.0"
|
||||
resolver_api = "1.1.0"
|
||||
mongo_indexed = "1.0.0"
|
||||
resolver_api = "1.1.1"
|
||||
toml_pretty = "1.1.2"
|
||||
parse_csl = "0.1.0"
|
||||
mungos = "0.5.6"
|
||||
mungos = "1.0.0"
|
||||
svi = "1.0.1"
|
||||
|
||||
# ASYNC
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
reqwest = { version = "0.12.4", features = ["json"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
tokio-util = "0.7.11"
|
||||
futures = "0.3.30"
|
||||
futures-util = "0.3.30"
|
||||
@@ -50,13 +51,14 @@ axum = { version = "0.7.5", features = ["ws", "json"] }
|
||||
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
||||
tower = { version = "0.4.13", features = ["timeout"] }
|
||||
tower-http = { version = "0.5.2", features = ["fs", "cors"] }
|
||||
tokio-tungstenite = "0.22.0"
|
||||
tokio-tungstenite = "0.23.1"
|
||||
|
||||
# SER/DE
|
||||
ordered_hash_map = { version = "0.4.0", features = ["serde"] }
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
strum = { version = "0.26.2", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
toml = "0.8.13"
|
||||
serde_json = "1.0.118"
|
||||
toml = "0.8.14"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.86"
|
||||
@@ -71,12 +73,12 @@ opentelemetry = "0.23.0"
|
||||
tracing = "0.1.40"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
clap = { version = "4.5.7", features = ["derive"] }
|
||||
dotenv = "0.15.0"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO
|
||||
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
|
||||
uuid = { version = "1.9.1", features = ["v4", "fast-rng", "serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
bcrypt = "0.15.1"
|
||||
base64 = "0.22.1"
|
||||
@@ -91,11 +93,12 @@ bollard = "0.16.1"
|
||||
sysinfo = "0.30.12"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.5.0"
|
||||
aws-sdk-ec2 = "1.46.0"
|
||||
aws-config = "1.5.3"
|
||||
aws-sdk-ec2 = "1.53.0"
|
||||
aws-sdk-ecr = "1.33.0"
|
||||
|
||||
# MISC
|
||||
derive_builder = "0.20.0"
|
||||
typeshare = "1.0.3"
|
||||
colored = "2.1.0"
|
||||
bson = "2.10.0"
|
||||
bson = "2.11.0"
|
||||
|
||||
@@ -13,8 +13,6 @@ repository.workspace = true
|
||||
# local
|
||||
monitor_client.workspace = true
|
||||
logger.workspace = true
|
||||
# mogh
|
||||
termination_signal.workspace = true
|
||||
# external
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ use monitor_client::entities::{
|
||||
alert::Alert, server::stats::SeverityLevel,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
@@ -57,13 +56,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
res = app => return res?,
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -19,7 +19,7 @@ pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem<T> {
|
||||
pub struct ToUpdateItem<T: Default> {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<T>,
|
||||
pub update_description: bool,
|
||||
@@ -37,6 +37,7 @@ pub trait ResourceSync: Sized {
|
||||
type PartialConfig: std::fmt::Debug
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Default
|
||||
+ From<Self::Config>
|
||||
+ From<Self::ConfigDiff>
|
||||
+ Serialize
|
||||
|
||||
@@ -84,7 +84,7 @@ impl ResourceSync for Deployment {
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: version.clone(),
|
||||
version: *version,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -80,15 +80,25 @@ pub fn get_updates(
|
||||
to_update.push(item);
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"description".dimmed(),
|
||||
variable.description,
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
if variable.description.is_empty() {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"description".dimmed(),
|
||||
variable.description,
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
}
|
||||
to_create.push(variable)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -17,24 +17,28 @@ path = "src/main.rs"
|
||||
# local
|
||||
monitor_client = { workspace = true, features = ["mongo"] }
|
||||
periphery_client.workspace = true
|
||||
formatting.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
merge_config_files.workspace = true
|
||||
termination_signal.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
partial_derive2.workspace = true
|
||||
derive_variants.workspace = true
|
||||
mongo_indexed.workspace = true
|
||||
resolver_api.workspace = true
|
||||
toml_pretty.workspace = true
|
||||
run_command.workspace = true
|
||||
parse_csl.workspace = true
|
||||
mungos.workspace = true
|
||||
slack.workspace = true
|
||||
svi.workspace = true
|
||||
# external
|
||||
ordered_hash_map.workspace = true
|
||||
urlencoding.workspace = true
|
||||
aws-sdk-ec2.workspace = true
|
||||
aws-sdk-ecr.workspace = true
|
||||
aws-config.workspace = true
|
||||
tokio-util.workspace = true
|
||||
axum-extra.workspace = true
|
||||
@@ -51,6 +55,7 @@ base64.workspace = true
|
||||
tokio.workspace = true
|
||||
tower.workspace = true
|
||||
serde.workspace = true
|
||||
strum.workspace = true
|
||||
axum.workspace = true
|
||||
toml.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
+17
-2
@@ -1,5 +1,5 @@
|
||||
# Build Core
|
||||
FROM rust:1.78.0-bookworm as core-builder
|
||||
FROM rust:1.79.0-bookworm as core-builder
|
||||
WORKDIR /builder
|
||||
COPY . .
|
||||
RUN cargo build -p monitor_core --release
|
||||
@@ -14,9 +14,24 @@ RUN cd frontend && yarn link @monitor/client && yarn && yarn build
|
||||
|
||||
# Final Image
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt update && apt install -y git ca-certificates
|
||||
|
||||
# Install Deps
|
||||
RUN apt update && apt install -y git curl unzip ca-certificates && \
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
|
||||
unzip awscliv2.zip && \
|
||||
./aws/install
|
||||
|
||||
# Copy
|
||||
COPY ./config_example/core.config.example.toml /config/config.toml
|
||||
COPY --from=core-builder /builder/target/release/core /
|
||||
COPY --from=frontend-builder /builder/frontend/dist /frontend
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9000
|
||||
|
||||
# Label for Ghcr
|
||||
LABEL org.opencontainers.image.source=https://github.com/mbecker20/monitor
|
||||
LABEL org.opencontainers.image.description="A tool to build and deploy software across many servers"
|
||||
LABEL org.opencontainers.image.licenses=GPL-3.0
|
||||
|
||||
CMD ["./core"]
|
||||
@@ -1,20 +1,24 @@
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use std::{collections::HashSet, future::IntoFuture, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{format_serror, muted};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::{
|
||||
CancelBuild, CancelBuildResponse, Deploy, RunBuild,
|
||||
},
|
||||
entities::{
|
||||
alert::{Alert, AlertData},
|
||||
all_logs_success,
|
||||
build::{Build, CloudRegistryConfig, ImageRegistry},
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
config::core::{AwsEcrConfig, AwsEcrConfigWithCredentials},
|
||||
deployment::DeploymentState,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
server::{stats::SeverityLevel, Server},
|
||||
server_template::aws::AwsServerTemplateConfig,
|
||||
to_monitor_name,
|
||||
update::{Log, Update},
|
||||
user::{auto_redeploy_user, User},
|
||||
},
|
||||
@@ -32,19 +36,22 @@ use periphery_client::{
|
||||
PeripheryClient,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
cloud::{
|
||||
aws::{
|
||||
launch_ec2_instance, terminate_ec2_instance_with_retry,
|
||||
Ec2Instance,
|
||||
ec2::{
|
||||
launch_ec2_instance, terminate_ec2_instance_with_retry,
|
||||
Ec2Instance,
|
||||
},
|
||||
ecr,
|
||||
},
|
||||
BuildCleanupData,
|
||||
},
|
||||
config::core_config,
|
||||
helpers::{
|
||||
alert::send_alerts,
|
||||
channel::build_cancel_channel,
|
||||
periphery_client,
|
||||
query::{get_deployment_state, get_global_variables},
|
||||
@@ -59,7 +66,7 @@ use crate::helpers::update::init_execution_update;
|
||||
use super::ExecuteRequest;
|
||||
|
||||
impl Resolve<RunBuild, (User, Update)> for State {
|
||||
#[instrument(name = "RunBuild", skip(self, user))]
|
||||
#[instrument(name = "RunBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunBuild { build }: RunBuild,
|
||||
@@ -72,8 +79,8 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let registry_token =
|
||||
validate_account_extract_registry_token(&build)?;
|
||||
let (registry_token, aws_ecr) =
|
||||
validate_account_extract_registry_token_aws_ecr(&build).await?;
|
||||
|
||||
// get the action state for the build (or insert default).
|
||||
let action_state =
|
||||
@@ -85,7 +92,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
build.config.version.increment();
|
||||
update.version = build.config.version.clone();
|
||||
update.version = build.config.version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
@@ -102,10 +109,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
id = cancel_recv.recv() => id?
|
||||
};
|
||||
if incoming_build_id == build_id {
|
||||
info!("build cancel acknowledged");
|
||||
update.push_simple_log(
|
||||
"cancel acknowledged",
|
||||
"the build cancellation has been queud, it may still take some time",
|
||||
"the build cancellation has been queued, it may still take some time",
|
||||
);
|
||||
update.finalize();
|
||||
let id = update.id.clone();
|
||||
@@ -137,9 +143,12 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
warn!("failed to get builder | {e:#}");
|
||||
update.logs.push(Log::error(
|
||||
"get builder",
|
||||
serialize_error_pretty(&e),
|
||||
format_serror(&e.context("failed to get builder").into()),
|
||||
));
|
||||
return handle_early_return(update).await;
|
||||
return handle_early_return(
|
||||
update, build.id, build.name, false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,7 +174,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
return handle_early_return(update).await
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
|
||||
@@ -176,8 +185,10 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("failed build at clone repo | {e:#}");
|
||||
update
|
||||
.push_error_log("clone repo", serialize_error_pretty(&e));
|
||||
update.push_error_log(
|
||||
"clone repo",
|
||||
format_serror(&e.context("failed to clone repo").into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +198,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
// Interpolate variables / secrets into build args
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
let mut secret_replacers_for_log = HashSet::new();
|
||||
|
||||
// Interpolate into build args
|
||||
for arg in &mut build.config.build_args {
|
||||
// first pass - global variables
|
||||
let (res, more_replacers) = svi::interpolate_variables(
|
||||
@@ -205,10 +219,40 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
false,
|
||||
)
|
||||
.context("failed to interpolate core secrets")?;
|
||||
secret_replacers_for_log.extend(
|
||||
more_replacers.iter().map(|(_, variable)| variable.clone()),
|
||||
);
|
||||
secret_replacers.extend(more_replacers);
|
||||
arg.value = res;
|
||||
}
|
||||
|
||||
// Interpolate into secret args
|
||||
for arg in &mut build.config.secret_args {
|
||||
// first pass - global variables
|
||||
let (res, more_replacers) = svi::interpolate_variables(
|
||||
&arg.value,
|
||||
&variables,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
false,
|
||||
)
|
||||
.context("failed to interpolate global variables")?;
|
||||
global_replacers.extend(more_replacers);
|
||||
// second pass - core secrets
|
||||
let (res, more_replacers) = svi::interpolate_variables(
|
||||
&res,
|
||||
&core_config.secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
false,
|
||||
)
|
||||
.context("failed to interpolate core secrets")?;
|
||||
secret_replacers_for_log.extend(
|
||||
more_replacers.into_iter().map(|(_, variable)| variable),
|
||||
);
|
||||
// Secret args don't need to be in replacers sent to periphery.
|
||||
// The secret args don't end up in the command like build args do.
|
||||
arg.value = res;
|
||||
}
|
||||
|
||||
// Show which variables were interpolated
|
||||
if !global_replacers.is_empty() {
|
||||
update.push_simple_log(
|
||||
@@ -220,12 +264,12 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
if !secret_replacers.is_empty() {
|
||||
if !secret_replacers_for_log.is_empty() {
|
||||
update.push_simple_log(
|
||||
"interpolate core secrets",
|
||||
secret_replacers
|
||||
.iter()
|
||||
.map(|(_, variable)| format!("<span class=\"text-muted-foreground\">replaced:</span> {variable}"))
|
||||
secret_replacers_for_log
|
||||
.into_iter()
|
||||
.map(|variable| format!("<span class=\"text-muted-foreground\">replaced:</span> {variable}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
@@ -236,6 +280,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
.request(api::build::Build {
|
||||
build: build.clone(),
|
||||
registry_token,
|
||||
aws_ecr,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
}) => res.context("failed at call to periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
@@ -243,7 +288,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
update.push_error_log("build cancelled", String::from("user cancelled build during docker build"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
return handle_early_return(update).await
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
|
||||
@@ -254,7 +299,10 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error in build | {e:#}");
|
||||
update.push_error_log("build", serialize_error_pretty(&e))
|
||||
update.push_error_log(
|
||||
"build",
|
||||
format_serror(&e.context("failed to build").into()),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -275,7 +323,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
"info.last_built_at": monitor_timestamp(),
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -309,6 +356,27 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
handle_post_build_redeploy(&build.id).await;
|
||||
info!("post build redeploy handled");
|
||||
});
|
||||
} else {
|
||||
let target = update.target.clone();
|
||||
let version = update.version;
|
||||
let err = update.logs.iter().find(|l| !l.success).cloned();
|
||||
tokio::spawn(async move {
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
target,
|
||||
ts: monitor_timestamp(),
|
||||
resolved_ts: Some(monitor_timestamp()),
|
||||
resolved: true,
|
||||
level: SeverityLevel::Warning,
|
||||
data: AlertData::BuildFailed {
|
||||
id: build.id,
|
||||
name: build.name,
|
||||
err,
|
||||
version,
|
||||
},
|
||||
};
|
||||
send_alerts(&[alert]).await
|
||||
});
|
||||
}
|
||||
|
||||
Ok(update)
|
||||
@@ -318,6 +386,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
#[instrument(skip(update))]
|
||||
async fn handle_early_return(
|
||||
mut update: Update,
|
||||
build_id: String,
|
||||
build_name: String,
|
||||
is_cancel: bool,
|
||||
) -> anyhow::Result<Update> {
|
||||
update.finalize();
|
||||
// Need to manually update the update before cache refresh,
|
||||
@@ -335,9 +406,32 @@ async fn handle_early_return(
|
||||
refresh_build_state_cache().await;
|
||||
}
|
||||
update_update(update.clone()).await?;
|
||||
if !update.success && !is_cancel {
|
||||
let target = update.target.clone();
|
||||
let version = update.version;
|
||||
let err = update.logs.iter().find(|l| !l.success).cloned();
|
||||
tokio::spawn(async move {
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
target,
|
||||
ts: monitor_timestamp(),
|
||||
resolved_ts: Some(monitor_timestamp()),
|
||||
resolved: true,
|
||||
level: SeverityLevel::Warning,
|
||||
data: AlertData::BuildFailed {
|
||||
id: build_id,
|
||||
name: build_name,
|
||||
version,
|
||||
err,
|
||||
},
|
||||
};
|
||||
send_alerts(&[alert]).await
|
||||
});
|
||||
}
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn validate_cancel_build(
|
||||
request: &ExecuteRequest,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -347,24 +441,28 @@ pub async fn validate_cancel_build(
|
||||
let db = db_client().await;
|
||||
|
||||
let (latest_build, latest_cancel) = tokio::try_join!(
|
||||
db.updates.find_one(
|
||||
doc! {
|
||||
db.updates
|
||||
.find_one(doc! {
|
||||
"operation": "RunBuild",
|
||||
"target.id": &build.id,
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
),
|
||||
db.updates.find_one(
|
||||
doc! {
|
||||
},)
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build()
|
||||
)
|
||||
.into_future(),
|
||||
db.updates
|
||||
.find_one(doc! {
|
||||
"operation": "CancelBuild",
|
||||
"target.id": &build.id,
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
},)
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build()
|
||||
)
|
||||
.into_future()
|
||||
)?;
|
||||
|
||||
match (latest_build, latest_cancel) {
|
||||
@@ -381,7 +479,7 @@ pub async fn validate_cancel_build(
|
||||
}
|
||||
|
||||
impl Resolve<CancelBuild, (User, Update)> for State {
|
||||
#[instrument(name = "CancelBuild", skip(self, user))]
|
||||
#[instrument(name = "CancelBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CancelBuild { build }: CancelBuild,
|
||||
@@ -636,20 +734,12 @@ async fn handle_post_build_redeploy(build_id: &str) {
|
||||
}
|
||||
});
|
||||
|
||||
let redeploy_results = join_all(futures).await;
|
||||
|
||||
let mut redeploys = Vec::<String>::new();
|
||||
let mut redeploy_failures = Vec::<String>::new();
|
||||
|
||||
for res in redeploy_results {
|
||||
if res.is_none() {
|
||||
for res in join_all(futures).await {
|
||||
let Some((id, res)) = res else {
|
||||
continue;
|
||||
}
|
||||
let (id, res) = res.unwrap();
|
||||
match res {
|
||||
Ok(_) => redeploys.push(id),
|
||||
Err(e) => redeploy_failures
|
||||
.push(format!("{id}: {}", serialize_error_pretty(&e))),
|
||||
};
|
||||
if let Err(e) = res {
|
||||
warn!("failed post build redeploy for deployment {id}: {e:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,16 +762,28 @@ fn start_aws_builder_log(
|
||||
|
||||
let readable_sec_group_ids = security_group_ids.join(", ");
|
||||
|
||||
format!("instance id: {instance_id}\nip: {ip}\nami id: {ami_id}\ninstance type: {instance_type}\nvolume size: {volume_gb} GB\nsubnet id: {subnet_id}\nsecurity groups: {readable_sec_group_ids}\nassign public ip: {assign_public_ip}\nuse public ip: {use_public_ip}")
|
||||
[
|
||||
format!("{}: {instance_id}", muted("instance id")),
|
||||
format!("{}: {ip}", muted("ip")),
|
||||
format!("{}: {ami_id}", muted("ami id")),
|
||||
format!("{}: {instance_type}", muted("instance type")),
|
||||
format!("{}: {volume_gb} GB", muted("volume size")),
|
||||
format!("{}: {subnet_id}", muted("subnet id")),
|
||||
format!("{}: {readable_sec_group_ids}", muted("security groups")),
|
||||
format!("{}: {assign_public_ip}", muted("assign public ip")),
|
||||
format!("{}: {use_public_ip}", muted("use public ip")),
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/// This will make sure that a build with non-none image registry has an account attached,
|
||||
/// and will check the core config for a token matching requirements (otherwise it is left to periphery)
|
||||
fn validate_account_extract_registry_token(
|
||||
/// and will check the core config for a token / aws ecr config matching requirements.
|
||||
/// Otherwise it is left to periphery.
|
||||
async fn validate_account_extract_registry_token_aws_ecr(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
) -> anyhow::Result<(Option<String>, Option<AwsEcrConfig>)> {
|
||||
match &build.config.image_registry {
|
||||
ImageRegistry::None(_) => Ok(None),
|
||||
ImageRegistry::None(_) => Ok((None, None)),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
@@ -690,7 +792,7 @@ fn validate_account_extract_registry_token(
|
||||
"Must attach account to use DockerHub image registry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().docker_accounts.get(account).cloned())
|
||||
Ok((core_config().docker_accounts.get(account).cloned(), None))
|
||||
}
|
||||
ImageRegistry::Ghcr(CloudRegistryConfig { account, .. }) => {
|
||||
if account.is_empty() {
|
||||
@@ -698,8 +800,40 @@ fn validate_account_extract_registry_token(
|
||||
"Must attach account to use GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().github_accounts.get(account).cloned())
|
||||
Ok((core_config().github_accounts.get(account).cloned(), None))
|
||||
}
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let config = core_config().aws_ecr_registries.get(label);
|
||||
let token = match config {
|
||||
Some(AwsEcrConfigWithCredentials {
|
||||
region,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
..
|
||||
}) => {
|
||||
let token = ecr::get_ecr_token(
|
||||
region,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
)
|
||||
.await
|
||||
.context("failed to get aws ecr token")?;
|
||||
ecr::maybe_create_repo(
|
||||
&to_monitor_name(&build.name),
|
||||
region.to_string(),
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
)
|
||||
.await
|
||||
.context("failed to create aws ecr repo")?;
|
||||
Some(token)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
Ok((token, config.map(AwsEcrConfig::from)))
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
Err(anyhow!("Custom image registry is not implemented"))
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::format_serror;
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
build::{Build, ImageRegistry},
|
||||
config::core::AwsEcrConfig,
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
get_image_name,
|
||||
permission::PermissionLevel,
|
||||
@@ -18,9 +20,9 @@ use monitor_client::{
|
||||
use mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use periphery_client::api;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
cloud::aws::ecr,
|
||||
config::core_config,
|
||||
helpers::{
|
||||
periphery_client,
|
||||
@@ -35,7 +37,7 @@ use crate::{
|
||||
use crate::helpers::update::init_execution_update;
|
||||
|
||||
impl Resolve<Deploy, (User, Update)> for State {
|
||||
#[instrument(name = "Deploy", skip(self, user))]
|
||||
#[instrument(name = "Deploy", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
Deploy {
|
||||
@@ -83,7 +85,13 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
let version = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(&build_id).await?;
|
||||
let image_name = get_image_name(&build);
|
||||
let image_name = get_image_name(&build, |label| {
|
||||
core_config()
|
||||
.aws_ecr_registries
|
||||
.get(label)
|
||||
.map(AwsEcrConfig::from)
|
||||
})
|
||||
.context("failed to create image name")?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
@@ -161,13 +169,40 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
update.version = version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let registry_token = match &deployment.config.image_registry {
|
||||
ImageRegistry::None(_) => None,
|
||||
ImageRegistry::DockerHub(params) => {
|
||||
core_config.docker_accounts.get(¶ms.account).cloned()
|
||||
}
|
||||
ImageRegistry::Ghcr(params) => {
|
||||
core_config.github_accounts.get(¶ms.account).cloned()
|
||||
let (registry_token, aws_ecr) = match &deployment
|
||||
.config
|
||||
.image_registry
|
||||
{
|
||||
ImageRegistry::None(_) => (None, None),
|
||||
ImageRegistry::DockerHub(params) => (
|
||||
core_config.docker_accounts.get(¶ms.account).cloned(),
|
||||
None,
|
||||
),
|
||||
ImageRegistry::Ghcr(params) => (
|
||||
core_config.github_accounts.get(¶ms.account).cloned(),
|
||||
None,
|
||||
),
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let config = core_config
|
||||
.aws_ecr_registries
|
||||
.get(label)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"did not find config for aws ecr registry {label}"
|
||||
)
|
||||
})?;
|
||||
(
|
||||
Some(
|
||||
ecr::get_ecr_token(
|
||||
&config.region,
|
||||
&config.access_key_id,
|
||||
&config.secret_access_key,
|
||||
)
|
||||
.await
|
||||
.context("failed to create aws ecr login token")?,
|
||||
),
|
||||
Some(AwsEcrConfig::from(config)),
|
||||
)
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
return Err(anyhow!("Custom ImageRegistry not yet supported"))
|
||||
@@ -180,6 +215,7 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
stop_signal,
|
||||
stop_time,
|
||||
registry_token,
|
||||
aws_ecr,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
})
|
||||
.await
|
||||
@@ -188,7 +224,9 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"deploy container",
|
||||
serialize_error_pretty(&e),
|
||||
format_serror(
|
||||
&e.context("failed to deploy container").into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -203,7 +241,7 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StartContainer, (User, Update)> for State {
|
||||
#[instrument(name = "StartContainer", skip(self, user))]
|
||||
#[instrument(name = "StartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StartContainer { deployment }: StartContainer,
|
||||
@@ -248,9 +286,10 @@ impl Resolve<StartContainer, (User, Update)> for State {
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => {
|
||||
Log::error("start container", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
"start container",
|
||||
format_serror(&e.context("failed to start container").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
@@ -263,7 +302,7 @@ impl Resolve<StartContainer, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StopContainer, (User, Update)> for State {
|
||||
#[instrument(name = "StopContainer", skip(self, user))]
|
||||
#[instrument(name = "StopContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopContainer {
|
||||
@@ -318,9 +357,10 @@ impl Resolve<StopContainer, (User, Update)> for State {
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => {
|
||||
Log::error("stop container", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
"stop container",
|
||||
format_serror(&e.context("failed to stop container").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
@@ -333,7 +373,7 @@ impl Resolve<StopContainer, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StopAllContainers, (User, Update)> for State {
|
||||
#[instrument(name = "StopAllContainers", skip(self, user))]
|
||||
#[instrument(name = "StopAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopAllContainers { server }: StopAllContainers,
|
||||
@@ -403,9 +443,11 @@ impl Resolve<StopAllContainers, (User, Update)> for State {
|
||||
if let Err(e) = res {
|
||||
update.push_error_log(
|
||||
"stop container failure",
|
||||
format!(
|
||||
"failed to stop container {name} ({id})\n\n{}",
|
||||
serialize_error_pretty(&e)
|
||||
format_serror(
|
||||
&e.context(format!(
|
||||
"failed to stop container {name} ({id})"
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -419,7 +461,7 @@ impl Resolve<StopAllContainers, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<RemoveContainer, (User, Update)> for State {
|
||||
#[instrument(name = "RemoveContainer", skip(self, user))]
|
||||
#[instrument(name = "RemoveContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RemoveContainer {
|
||||
@@ -474,9 +516,10 @@ impl Resolve<RemoveContainer, (User, Update)> for State {
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => {
|
||||
Log::error("stop container", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
"stop container",
|
||||
format_serror(&e.context("failed to stop container").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{middleware, routing::post, Extension, Router};
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
@@ -12,7 +13,7 @@ use monitor_client::{
|
||||
use mungos::by_id::find_one_by_id;
|
||||
use resolver_api::{derive::Resolver, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serror::{serialize_error_pretty, Json};
|
||||
use serror::Json;
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -92,7 +93,7 @@ async fn handler(
|
||||
let log = match handle.await {
|
||||
Ok(Err(e)) => {
|
||||
warn!("/execute request {req_id} task error: {e:#}",);
|
||||
Log::error("task error", serialize_error_pretty(&e))
|
||||
Log::error("task error", format_serror(&e.into()))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("/execute request {req_id} spawn error: {e:?}",);
|
||||
@@ -121,7 +122,7 @@ async fn handler(
|
||||
Ok(Json(update))
|
||||
}
|
||||
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update))]
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: ExecuteRequest,
|
||||
@@ -149,7 +150,7 @@ async fn task(
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/execute request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/execute request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use formatting::{bold, colored, format_serror, muted, Color};
|
||||
use monitor_client::{
|
||||
api::execute::RunProcedure,
|
||||
entities::{
|
||||
@@ -9,7 +10,6 @@ use monitor_client::{
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_document};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<RunProcedure, (User, Update)> for State {
|
||||
#[instrument(name = "RunProcedure", skip(self, user))]
|
||||
#[instrument(name = "RunProcedure", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunProcedure { procedure }: RunProcedure,
|
||||
@@ -51,7 +51,11 @@ fn resolve_inner(
|
||||
// and will panic otherwise.
|
||||
update.push_simple_log(
|
||||
"execute_procedure",
|
||||
format!("executing procedure {}", procedure.name),
|
||||
format!(
|
||||
"{}: executing procedure '{}'",
|
||||
muted("INFO"),
|
||||
bold(&procedure.name)
|
||||
),
|
||||
);
|
||||
|
||||
// get the action state for the procedure (or insert default).
|
||||
@@ -75,13 +79,15 @@ fn resolve_inner(
|
||||
Ok(_) => {
|
||||
update.push_simple_log(
|
||||
"execution ok",
|
||||
"the procedure has completed with no errors",
|
||||
format!(
|
||||
"{}: the procedure has {} with no errors",
|
||||
muted("INFO"),
|
||||
colored("completed", Color::Green)
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => update.push_error_log(
|
||||
"execution error",
|
||||
serialize_error_pretty(&e),
|
||||
),
|
||||
Err(e) => update
|
||||
.push_error_log("execution error", format_serror(&e.into())),
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
@@ -16,7 +17,6 @@ use mungos::{
|
||||
};
|
||||
use periphery_client::api;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
#[instrument(name = "CloneRepo", skip(self, user))]
|
||||
#[instrument(name = "CloneRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CloneRepo { repo }: CloneRepo,
|
||||
@@ -71,7 +71,10 @@ impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
{
|
||||
Ok(logs) => logs,
|
||||
Err(e) => {
|
||||
vec![Log::error("clone repo", serialize_error_pretty(&e))]
|
||||
vec![Log::error(
|
||||
"clone repo",
|
||||
format_serror(&e.context("failed to clone repo").into()),
|
||||
)]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,7 +90,7 @@ impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PullRepo, (User, Update)> for State {
|
||||
#[instrument(name = "PullRepo", skip(self, user))]
|
||||
#[instrument(name = "PullRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PullRepo { repo }: PullRepo,
|
||||
@@ -129,7 +132,10 @@ impl Resolve<PullRepo, (User, Update)> for State {
|
||||
{
|
||||
Ok(logs) => logs,
|
||||
Err(e) => {
|
||||
vec![Log::error("pull repo", serialize_error_pretty(&e))]
|
||||
vec![Log::error(
|
||||
"pull repo",
|
||||
format_serror(&e.context("failed to pull repo").into()),
|
||||
)]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,7 +181,6 @@ async fn update_last_pulled_time(repo_name: &str) {
|
||||
.update_one(
|
||||
doc! { "name": repo_name },
|
||||
doc! { "$set": { "info.last_pulled_at": monitor_timestamp() } },
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
@@ -11,7 +12,6 @@ use monitor_client::{
|
||||
};
|
||||
use periphery_client::api;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{periphery_client, update::update_update},
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<PruneContainers, (User, Update)> for State {
|
||||
#[instrument(name = "PruneContainers", skip(self, user))]
|
||||
#[instrument(name = "PruneContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneContainers { server }: PruneContainers,
|
||||
@@ -54,9 +54,12 @@ impl Resolve<PruneContainers, (User, Update)> for State {
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => {
|
||||
Log::error("prune containers", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
"prune containers",
|
||||
format_serror(
|
||||
&e.context("failed to prune containers").into(),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
@@ -71,7 +74,7 @@ impl Resolve<PruneContainers, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PruneNetworks, (User, Update)> for State {
|
||||
#[instrument(name = "PruneNetworks", skip(self, user))]
|
||||
#[instrument(name = "PruneNetworks", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneNetworks { server }: PruneNetworks,
|
||||
@@ -105,9 +108,10 @@ impl Resolve<PruneNetworks, (User, Update)> for State {
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => {
|
||||
Log::error("prune networks", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
"prune networks",
|
||||
format_serror(&e.context("failed to prune networks").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
@@ -122,7 +126,7 @@ impl Resolve<PruneNetworks, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PruneImages, (User, Update)> for State {
|
||||
#[instrument(name = "PruneImages", skip(self, user))]
|
||||
#[instrument(name = "PruneImages", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneImages { server }: PruneImages,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::{execute::LaunchServer, write::CreateServer},
|
||||
entities::{
|
||||
@@ -11,17 +12,18 @@ use monitor_client::{
|
||||
};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
cloud::{aws::launch_ec2_instance, hetzner::launch_hetzner_server},
|
||||
cloud::{
|
||||
aws::ec2::launch_ec2_instance, hetzner::launch_hetzner_server,
|
||||
},
|
||||
helpers::update::update_update,
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<LaunchServer, (User, Update)> for State {
|
||||
#[instrument(name = "LaunchServer", skip(self, user))]
|
||||
#[instrument(name = "LaunchServer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
LaunchServer {
|
||||
@@ -34,12 +36,9 @@ impl Resolve<LaunchServer, (User, Update)> for State {
|
||||
if db_client()
|
||||
.await
|
||||
.servers
|
||||
.find_one(
|
||||
doc! {
|
||||
"name": &name
|
||||
},
|
||||
None,
|
||||
)
|
||||
.find_one(doc! {
|
||||
"name": &name
|
||||
})
|
||||
.await
|
||||
.context("failed to query db for servers")?
|
||||
.is_some()
|
||||
@@ -130,10 +129,7 @@ impl Resolve<LaunchServer, (User, Update)> for State {
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"create server",
|
||||
format!(
|
||||
"failed to create server\n\n{}",
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
format_serror(&e.context("failed to create server").into()),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{colored, format_serror, Color};
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
@@ -7,7 +8,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -20,13 +20,12 @@ use monitor_client::{
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_document};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::{
|
||||
colored,
|
||||
deployment,
|
||||
resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
@@ -38,6 +37,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<RunSync, (User, Update)> for State {
|
||||
#[instrument(name = "RunSync", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunSync { sync }: RunSync,
|
||||
@@ -77,7 +77,7 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
) = get_updates_for_execution::<Deployment>(
|
||||
) = deployment::get_updates_for_execution(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
@@ -205,7 +205,10 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
{
|
||||
update.push_simple_log(
|
||||
"No Changes",
|
||||
format!("{}. exiting.", colored("nothing to do", "green")),
|
||||
format!(
|
||||
"{}. exiting.",
|
||||
colored("nothing to do", Color::Green)
|
||||
),
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
@@ -302,15 +305,15 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
);
|
||||
|
||||
// Dependant on server / build
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Deployment::run_updates(
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
if let Some(res) = deployment::run_updates(
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
)
|
||||
.await
|
||||
{
|
||||
update.logs.extend(res);
|
||||
}
|
||||
|
||||
// Dependant on everything
|
||||
maybe_extend(
|
||||
@@ -355,9 +358,9 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
warn!("failed to refresh sync {} after run | {e:#}", sync.name);
|
||||
update.push_error_log(
|
||||
"refresh sync",
|
||||
format!(
|
||||
"failed to refresh sync pending after run | {}",
|
||||
serialize_error_pretty(&e)
|
||||
format_serror(
|
||||
&e.context("failed to refresh sync pending after run")
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Resolve<GetAlertersSummary, User> for State {
|
||||
let total = db_client()
|
||||
.await
|
||||
.alerters
|
||||
.count_documents(query, None)
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.await
|
||||
.context("failed to count all alerter documents")?;
|
||||
let res = GetAlertersSummaryResponse {
|
||||
|
||||
@@ -147,16 +147,13 @@ impl Resolve<GetBuildMonthlyStats, User> for State {
|
||||
let mut build_updates = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find(
|
||||
doc! {
|
||||
"start_ts": {
|
||||
"$gte": open_ts,
|
||||
"$lt": close_ts
|
||||
},
|
||||
"operation": Operation::RunBuild.to_string(),
|
||||
.find(doc! {
|
||||
"start_ts": {
|
||||
"$gte": open_ts,
|
||||
"$lt": close_ts
|
||||
},
|
||||
None,
|
||||
)
|
||||
"operation": Operation::RunBuild.to_string(),
|
||||
})
|
||||
.await
|
||||
.context("failed to get updates cursor")?;
|
||||
|
||||
@@ -201,6 +198,7 @@ impl Resolve<GetBuildVersions, User> for State {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
limit,
|
||||
}: GetBuildVersions,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<BuildVersionResponseItem>> {
|
||||
@@ -233,7 +231,10 @@ impl Resolve<GetBuildVersions, User> for State {
|
||||
let versions = find_collect(
|
||||
&db_client().await.updates,
|
||||
filter,
|
||||
FindOptions::builder().sort(doc! { "_id": -1 }).build(),
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "_id": -1 })
|
||||
.limit(limit)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to pull versions from mongo")?
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Resolve<GetBuildersSummary, User> for State {
|
||||
let total = db_client()
|
||||
.await
|
||||
.builders
|
||||
.count_documents(query, None)
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.await
|
||||
.context("failed to count all builder documents")?;
|
||||
let res = GetBuildersSummaryResponse {
|
||||
|
||||
@@ -121,6 +121,7 @@ impl Resolve<SearchLog, User> for State {
|
||||
deployment,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
}: SearchLog,
|
||||
user: User,
|
||||
) -> anyhow::Result<Log> {
|
||||
@@ -143,6 +144,7 @@ impl Resolve<SearchLog, User> for State {
|
||||
name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")
|
||||
|
||||
@@ -39,6 +39,7 @@ mod variable;
|
||||
enum ReadRequest {
|
||||
GetVersion(GetVersion),
|
||||
GetCoreInfo(GetCoreInfo),
|
||||
GetAvailableAwsEcrLabels(GetAvailableAwsEcrLabels),
|
||||
|
||||
// ==== USER ====
|
||||
ListUsers(ListUsers),
|
||||
@@ -172,17 +173,14 @@ pub fn router() -> Router {
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
|
||||
#[instrument(name = "ReadHandler", level = "debug", skip(user))]
|
||||
#[instrument(name = "ReadHandler", level = "debug", skip(user), fields(user_id = user.id))]
|
||||
async fn handler(
|
||||
Extension(user): Extension<User>,
|
||||
Json(request): Json<ReadRequest>,
|
||||
) -> serror::Result<(TypedHeader<ContentType>, String)> {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
debug!(
|
||||
"/read request {req_id} | user: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
debug!("/read request | user: {}", user.username);
|
||||
let res =
|
||||
State
|
||||
.resolve_request(request, user)
|
||||
@@ -194,7 +192,7 @@ async fn handler(
|
||||
resolver_api::Error::Inner(e) => e,
|
||||
});
|
||||
if let Err(e) = &res {
|
||||
warn!("/read request {req_id} error: {e:#}");
|
||||
debug!("/read request {req_id} error: {e:#}");
|
||||
}
|
||||
let elapsed = timer.elapsed();
|
||||
debug!("/read request {req_id} | resolve time: {elapsed:?}");
|
||||
@@ -234,3 +232,13 @@ impl Resolve<GetCoreInfo, User> for State {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetAvailableAwsEcrLabels, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetAvailableAwsEcrLabels {}: GetAvailableAwsEcrLabels,
|
||||
_: User,
|
||||
) -> anyhow::Result<GetAvailableAwsEcrLabelsResponse> {
|
||||
Ok(core_config().aws_ecr_registries.keys().cloned().collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Resolve<GetServerTemplatesSummary, User> for State {
|
||||
let total = db_client()
|
||||
.await
|
||||
.server_templates
|
||||
.count_documents(query, None)
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.await
|
||||
.context("failed to count all server template documents")?;
|
||||
let res = GetServerTemplatesSummaryResponse {
|
||||
|
||||
@@ -14,7 +14,11 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
deployment::{
|
||||
conversions_to_string, term_signal_labels_to_string,
|
||||
Deployment, DeploymentImage,
|
||||
},
|
||||
environment_vars_to_string,
|
||||
permission::{PermissionLevel, UserTarget},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
@@ -30,8 +34,10 @@ use monitor_client::{
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use ordered_hash_map::OrderedHashMap;
|
||||
use partial_derive2::PartialDiff;
|
||||
use resolver_api::Resolve;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_user_user_group_ids,
|
||||
@@ -112,13 +118,22 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ServerTemplate>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::ServerTemplate(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::ResourceSync(resource.id)),
|
||||
);
|
||||
|
||||
let user_groups = if user.admin {
|
||||
find_collect(&db_client().await.user_groups, None, None)
|
||||
@@ -215,7 +230,10 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
// replace server id of builder
|
||||
if let BuilderConfig::Server(config) = &mut builder.config {
|
||||
config.server_id.clone_from(
|
||||
names.servers.get(&id).unwrap_or(&String::new()),
|
||||
names
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.unwrap_or(&String::new()),
|
||||
)
|
||||
}
|
||||
res
|
||||
@@ -305,7 +323,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
.context("failed to get variables from db")?;
|
||||
}
|
||||
|
||||
let toml = toml::to_string(&res)
|
||||
let toml = serialize_resources_toml(&res)
|
||||
.context("failed to serialize resources to toml")?;
|
||||
|
||||
Ok(ExportResourcesToTomlResponse { toml })
|
||||
@@ -527,6 +545,248 @@ fn convert_resource<R: MonitorResource>(
|
||||
.filter_map(|t| tag_names.get(t).cloned())
|
||||
.collect(),
|
||||
description: resource.description,
|
||||
deploy: false,
|
||||
after: Default::default(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_resources_toml(
|
||||
resources: &ResourcesToml,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut res = String::new();
|
||||
|
||||
let options = toml_pretty::Options::default()
|
||||
.tab(" ")
|
||||
.skip_empty_string(true)
|
||||
.max_inline_array_length(30);
|
||||
|
||||
for server in &resources.servers {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[server]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&server, options)
|
||||
.context("failed to serialize servers to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for deployment in &resources.deployments {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[deployment]]\n");
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&deployment)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("deployment has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
if let Some(DeploymentImage::Build { version, .. }) =
|
||||
&deployment.config.image
|
||||
{
|
||||
let image = config
|
||||
.get_mut("image")
|
||||
.context("deployment has no image")?
|
||||
.get_mut("params")
|
||||
.context("deployment image has no params")?
|
||||
.as_object_mut()
|
||||
.context("deployment image params is not object")?;
|
||||
if version.is_none() {
|
||||
image.remove("version");
|
||||
} else {
|
||||
image.insert(
|
||||
"version".to_string(),
|
||||
Value::String(version.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(term_signal_labels) =
|
||||
&deployment.config.term_signal_labels
|
||||
{
|
||||
config.insert(
|
||||
"term_signal_labels".to_string(),
|
||||
Value::String(term_signal_labels_to_string(
|
||||
term_signal_labels,
|
||||
)),
|
||||
);
|
||||
}
|
||||
if let Some(ports) = &deployment.config.ports {
|
||||
config.insert(
|
||||
"ports".to_string(),
|
||||
Value::String(conversions_to_string(ports)),
|
||||
);
|
||||
}
|
||||
if let Some(volumes) = &deployment.config.volumes {
|
||||
config.insert(
|
||||
"volumes".to_string(),
|
||||
Value::String(conversions_to_string(volumes)),
|
||||
);
|
||||
}
|
||||
if let Some(environment) = &deployment.config.environment {
|
||||
config.insert(
|
||||
"environment".to_string(),
|
||||
Value::String(environment_vars_to_string(environment)),
|
||||
);
|
||||
}
|
||||
if let Some(labels) = &deployment.config.labels {
|
||||
config.insert(
|
||||
"labels".to_string(),
|
||||
Value::String(environment_vars_to_string(labels)),
|
||||
);
|
||||
}
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize deployments to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for build in &resources.builds {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&build)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("build has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
if let Some(version) = &build.config.version {
|
||||
config.insert(
|
||||
"version".to_string(),
|
||||
Value::String(version.to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(build_args) = &build.config.build_args {
|
||||
config.insert(
|
||||
"build_args".to_string(),
|
||||
Value::String(environment_vars_to_string(build_args)),
|
||||
);
|
||||
}
|
||||
if let Some(labels) = &build.config.labels {
|
||||
config.insert(
|
||||
"labels".to_string(),
|
||||
Value::String(environment_vars_to_string(labels)),
|
||||
);
|
||||
}
|
||||
res.push_str("[[build]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize builds to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for repo in &resources.repos {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[repo]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&repo, options)
|
||||
.context("failed to serialize repos to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for procedure in &resources.procedures {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&procedure)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("procedure has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
|
||||
let stages = config
|
||||
.remove("stages")
|
||||
.context("procedure config has no stages")?;
|
||||
let stages = stages.as_array().context("stages is not array")?;
|
||||
|
||||
res.push_str("[[procedure]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize procedures to toml")?,
|
||||
);
|
||||
|
||||
for stage in stages {
|
||||
res.push_str("\n\n[[procedure.config.stage]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(stage, options)
|
||||
.context("failed to serialize procedures to toml")?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for alerter in &resources.alerters {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[alerter]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&alerter, options)
|
||||
.context("failed to serialize alerters to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for builder in &resources.builders {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[builder]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&builder, options)
|
||||
.context("failed to serialize builders to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for server_template in &resources.server_templates {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[server_template]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&server_template, options)
|
||||
.context("failed to serialize server_templates to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for resource_sync in &resources.resource_syncs {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[resource_sync]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&resource_sync, options)
|
||||
.context("failed to serialize resource_syncs to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for variable in &resources.variables {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[variable]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&variable, options)
|
||||
.context("failed to serialize variables to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for user_group in &resources.user_groups {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[user_group]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&user_group, options)
|
||||
.context("failed to serialize user_groups to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Resolve<GetUserGroup, User> for State {
|
||||
db_client()
|
||||
.await
|
||||
.user_groups
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for user groups")?
|
||||
.context("no UserGroup found with given name or id")
|
||||
|
||||
@@ -187,7 +187,7 @@ impl Resolve<CreateApiKey, User> for State {
|
||||
db_client()
|
||||
.await
|
||||
.api_keys
|
||||
.insert_one(api_key, None)
|
||||
.insert_one(api_key)
|
||||
.await
|
||||
.context("failed to create api key on db")?;
|
||||
Ok(CreateApiKeyResponse { key, secret })
|
||||
@@ -208,7 +208,7 @@ impl Resolve<DeleteApiKey, User> for State {
|
||||
let client = db_client().await;
|
||||
let key = client
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.find_one(doc! { "key": &key })
|
||||
.await
|
||||
.context("failed at db query")?
|
||||
.context("no api key with key found")?;
|
||||
@@ -217,7 +217,7 @@ impl Resolve<DeleteApiKey, User> for State {
|
||||
}
|
||||
client
|
||||
.api_keys
|
||||
.delete_one(doc! { "key": key.key }, None)
|
||||
.delete_one(doc! { "key": key.key })
|
||||
.await
|
||||
.context("failed to delete api key from db")?;
|
||||
Ok(DeleteApiKeyResponse {})
|
||||
|
||||
@@ -149,16 +149,13 @@ async fn handler(
|
||||
Ok((TypedHeader(ContentType::json()), res??))
|
||||
}
|
||||
|
||||
#[instrument(name = "WriteRequest", skip(user))]
|
||||
#[instrument(name = "WriteRequest", skip(user), fields(user_id = user.id))]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: WriteRequest,
|
||||
user: User,
|
||||
) -> anyhow::Result<String> {
|
||||
info!(
|
||||
"/write request {req_id} | user: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
info!("/write request | user: {}", user.username);
|
||||
|
||||
let timer = Instant::now();
|
||||
|
||||
@@ -178,7 +175,7 @@ async fn task(
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/write request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/write request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ impl Resolve<UpdatePermissionOnTarget, User> for State {
|
||||
"level": permission.as_ref(),
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.with_options(UpdateOptions::builder().upsert(true).build())
|
||||
.await?;
|
||||
|
||||
Ok(UpdatePermissionOnTargetResponse {})
|
||||
@@ -150,7 +150,7 @@ async fn extract_user_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.users
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for users")?
|
||||
.context("no matching user found")?
|
||||
@@ -165,7 +165,7 @@ async fn extract_user_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.user_groups
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for user_groups")?
|
||||
.context("no matching user_group found")?
|
||||
@@ -192,7 +192,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.builds
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for builds")?
|
||||
.context("no matching build found")?
|
||||
@@ -207,7 +207,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.builders
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for builders")?
|
||||
.context("no matching builder found")?
|
||||
@@ -222,7 +222,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.deployments
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for deployments")?
|
||||
.context("no matching deployment found")?
|
||||
@@ -237,7 +237,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.servers
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for servers")?
|
||||
.context("no matching server found")?
|
||||
@@ -252,7 +252,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.repos
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for repos")?
|
||||
.context("no matching repo found")?
|
||||
@@ -267,7 +267,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.alerters
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for alerters")?
|
||||
.context("no matching alerter found")?
|
||||
@@ -282,7 +282,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.procedures
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for procedures")?
|
||||
.context("no matching procedure found")?
|
||||
@@ -297,7 +297,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.server_templates
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for server templates")?
|
||||
.context("no matching server template found")?
|
||||
@@ -312,7 +312,7 @@ async fn extract_resource_target_with_validation(
|
||||
let id = db_client()
|
||||
.await
|
||||
.resource_syncs
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for resource syncs")?
|
||||
.context("no matching resource sync found")?
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
@@ -13,7 +14,6 @@ use monitor_client::{
|
||||
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::{
|
||||
@@ -112,8 +112,10 @@ impl Resolve<CreateNetwork, User> for State {
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update
|
||||
.push_error_log("create network", serialize_error_pretty(&e)),
|
||||
Err(e) => update.push_error_log(
|
||||
"create network",
|
||||
format_serror(&e.context("failed to create network").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
@@ -149,8 +151,10 @@ impl Resolve<DeleteNetwork, User> for State {
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update
|
||||
.push_error_log("delete network", serialize_error_pretty(&e)),
|
||||
Err(e) => update.push_error_log(
|
||||
"delete network",
|
||||
format_serror(&e.context("failed to delete network").into()),
|
||||
),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Resolve<CreateServiceUser, User> for State {
|
||||
user.id = db_client()
|
||||
.await
|
||||
.users
|
||||
.insert_one(&user, None)
|
||||
.insert_one(&user)
|
||||
.await
|
||||
.context("failed to create service user on db")?
|
||||
.inserted_id
|
||||
@@ -91,7 +91,7 @@ impl Resolve<UpdateServiceUserDescription, User> for State {
|
||||
let db = db_client().await;
|
||||
let service_user = db
|
||||
.users
|
||||
.find_one(doc! { "username": &username }, None)
|
||||
.find_one(doc! { "username": &username })
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("no user with given username")?;
|
||||
@@ -102,12 +102,11 @@ impl Resolve<UpdateServiceUserDescription, User> for State {
|
||||
.update_one(
|
||||
doc! { "username": &username },
|
||||
doc! { "$set": { "config.data.description": description } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update user on db")?;
|
||||
db.users
|
||||
.find_one(doc! { "username": &username }, None)
|
||||
.find_one(doc! { "username": &username })
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("user with username not found")
|
||||
@@ -155,7 +154,7 @@ impl Resolve<DeleteApiKeyForServiceUser, User> for State {
|
||||
let db = db_client().await;
|
||||
let api_key = db
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.find_one(doc! { "key": &key })
|
||||
.await
|
||||
.context("failed to query db for api key")?
|
||||
.context("did not find matching api key")?;
|
||||
@@ -168,7 +167,7 @@ impl Resolve<DeleteApiKeyForServiceUser, User> for State {
|
||||
return Err(anyhow!("user is not service user"));
|
||||
};
|
||||
db.api_keys
|
||||
.delete_one(doc! { "key": key }, None)
|
||||
.delete_one(doc! { "key": key })
|
||||
.await
|
||||
.context("failed to delete api key on db")?;
|
||||
Ok(DeleteApiKeyForServiceUserResponse {})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
@@ -7,7 +8,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -28,12 +28,15 @@ use mungos::{
|
||||
mongodb::bson::{doc, to_document},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
alert::send_alerts,
|
||||
query::get_id_to_tags,
|
||||
sync::resource::{get_updates_for_view, AllResourcesById},
|
||||
sync::{
|
||||
deployment,
|
||||
resource::{get_updates_for_view, AllResourcesById},
|
||||
},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
@@ -127,7 +130,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
)
|
||||
.await
|
||||
.context("failed to get server updates")?,
|
||||
deployment_updates: get_updates_for_view::<Deployment>(
|
||||
deployment_updates: deployment::get_updates_for_view(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
@@ -232,7 +235,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
message: None,
|
||||
data: PendingSyncUpdatesData::Err(
|
||||
PendingSyncUpdatesDataErr {
|
||||
message: serialize_error_pretty(&e),
|
||||
message: format_serror(&e.into()),
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -265,7 +268,6 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": &id,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for alert")
|
||||
@@ -287,11 +289,12 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
resolved_ts: None,
|
||||
};
|
||||
db.alerts
|
||||
.insert_one(&alert, None)
|
||||
.insert_one(&alert)
|
||||
.await
|
||||
.context("failed to open existing pending resource sync updates alert")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
.ok();
|
||||
send_alerts(&[alert]).await;
|
||||
}
|
||||
// CLOSE ALERT
|
||||
(Some(existing), false) => {
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Resolve<CreateTag, User> for State {
|
||||
tag.id = db_client()
|
||||
.await
|
||||
.tags
|
||||
.insert_one(&tag, None)
|
||||
.insert_one(&tag)
|
||||
.await
|
||||
.context("failed to create tag on db")?
|
||||
.inserted_id
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Resolve<CreateUserGroup, User> for State {
|
||||
let db = db_client().await;
|
||||
let id = db
|
||||
.user_groups
|
||||
.insert_one(user_group, None)
|
||||
.insert_one(user_group)
|
||||
.await
|
||||
.context("failed to create UserGroup on db")?
|
||||
.inserted_id
|
||||
@@ -99,7 +99,7 @@ impl Resolve<DeleteUserGroup, User> for State {
|
||||
.delete_many(doc! {
|
||||
"user_target.type": "UserGroup",
|
||||
"user_target.id": id,
|
||||
}, None)
|
||||
})
|
||||
.await
|
||||
.context("failed to clean up UserGroups permissions. User Group has been deleted")?;
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Resolve<AddUserToUserGroup, User> for State {
|
||||
};
|
||||
let user = db
|
||||
.users
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query mongo for users")?
|
||||
.context("no matching user found")?;
|
||||
@@ -138,12 +138,11 @@ impl Resolve<AddUserToUserGroup, User> for State {
|
||||
.update_one(
|
||||
filter.clone(),
|
||||
doc! { "$addToSet": { "users": &user.id } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to add user to group on db")?;
|
||||
db.user_groups
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.context("no user group with given id")
|
||||
@@ -171,7 +170,7 @@ impl Resolve<RemoveUserFromUserGroup, User> for State {
|
||||
};
|
||||
let user = db
|
||||
.users
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query mongo for users")?
|
||||
.context("no matching user found")?;
|
||||
@@ -184,12 +183,11 @@ impl Resolve<RemoveUserFromUserGroup, User> for State {
|
||||
.update_one(
|
||||
filter.clone(),
|
||||
doc! { "$pull": { "users": &user.id } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to add user to group on db")?;
|
||||
db.user_groups
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.context("no user group with given id")
|
||||
@@ -229,15 +227,11 @@ impl Resolve<SetUsersInUserGroup, User> for State {
|
||||
Err(_) => doc! { "name": &user_group },
|
||||
};
|
||||
db.user_groups
|
||||
.update_one(
|
||||
filter.clone(),
|
||||
doc! { "$set": { "users": users } },
|
||||
None,
|
||||
)
|
||||
.update_one(filter.clone(), doc! { "$set": { "users": users } })
|
||||
.await
|
||||
.context("failed to add user to group on db")?;
|
||||
db.user_groups
|
||||
.find_one(filter, None)
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.context("no user group with given id")
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Resolve<CreateVariable, User> for State {
|
||||
db_client()
|
||||
.await
|
||||
.variables
|
||||
.insert_one(&variable, None)
|
||||
.insert_one(&variable)
|
||||
.await
|
||||
.context("failed to create variable on db")?;
|
||||
|
||||
@@ -86,7 +86,6 @@ impl Resolve<UpdateVariableValue, User> for State {
|
||||
.update_one(
|
||||
doc! { "name": &name },
|
||||
doc! { "$set": { "value": &value } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update variable value on db")?;
|
||||
@@ -127,7 +126,6 @@ impl Resolve<UpdateVariableDescription, User> for State {
|
||||
.update_one(
|
||||
doc! { "name": &name },
|
||||
doc! { "$set": { "description": &description } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update variable description on db")?;
|
||||
@@ -148,7 +146,7 @@ impl Resolve<DeleteVariable, User> for State {
|
||||
db_client()
|
||||
.await
|
||||
.variables
|
||||
.delete_one(doc! { "name": &name }, None)
|
||||
.delete_one(doc! { "name": &name })
|
||||
.await
|
||||
.context("failed to delete variable on db")?;
|
||||
|
||||
|
||||
@@ -216,8 +216,8 @@ impl GithubOauthClient {
|
||||
#[derive(Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
pub access_token: String,
|
||||
pub scope: String,
|
||||
pub token_type: String,
|
||||
// pub scope: String,
|
||||
// pub token_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -225,5 +225,5 @@ pub struct GithubUserResponse {
|
||||
pub login: String,
|
||||
pub id: u128,
|
||||
pub avatar_url: String,
|
||||
pub email: Option<String>,
|
||||
// pub email: Option<String>,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
extract::Query, response::Redirect, routing::get, Router,
|
||||
};
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::entities::{
|
||||
monitor_timestamp,
|
||||
user::{User, UserConfig},
|
||||
@@ -66,7 +67,7 @@ async fn callback(
|
||||
let db_client = db_client().await;
|
||||
let user = db_client
|
||||
.users
|
||||
.find_one(doc! { "config.data.github_id": &github_id }, None)
|
||||
.find_one(doc! { "config.data.github_id": &github_id })
|
||||
.await
|
||||
.context("failed at find user query from mongo")?;
|
||||
let jwt = match user {
|
||||
@@ -76,7 +77,7 @@ async fn callback(
|
||||
None => {
|
||||
let ts = monitor_timestamp();
|
||||
let no_users_exist =
|
||||
db_client.users.find_one(None, None).await?.is_none();
|
||||
db_client.users.find_one(Document::new()).await?.is_none();
|
||||
let user = User {
|
||||
id: Default::default(),
|
||||
username: github_user.login,
|
||||
@@ -98,7 +99,7 @@ async fn callback(
|
||||
};
|
||||
let user_id = db_client
|
||||
.users
|
||||
.insert_one(user, None)
|
||||
.insert_one(user)
|
||||
.await
|
||||
.context("failed to create user on mongo")?
|
||||
.inserted_id
|
||||
|
||||
@@ -185,10 +185,10 @@ impl GoogleOauthClient {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
pub access_token: String,
|
||||
// pub access_token: String,
|
||||
pub id_token: String,
|
||||
pub scope: String,
|
||||
pub token_type: String,
|
||||
// pub scope: String,
|
||||
// pub token_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
|
||||
@@ -3,6 +3,7 @@ use async_timing_util::unix_timestamp_ms;
|
||||
use axum::{
|
||||
extract::Query, response::Redirect, routing::get, Router,
|
||||
};
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::entities::user::{User, UserConfig};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use reqwest::StatusCode;
|
||||
@@ -75,7 +76,7 @@ async fn callback(
|
||||
let db_client = db_client().await;
|
||||
let user = db_client
|
||||
.users
|
||||
.find_one(doc! { "config.data.google_id": &google_id }, None)
|
||||
.find_one(doc! { "config.data.google_id": &google_id })
|
||||
.await
|
||||
.context("failed at find user query from mongo")?;
|
||||
let jwt = match user {
|
||||
@@ -85,7 +86,7 @@ async fn callback(
|
||||
None => {
|
||||
let ts = unix_timestamp_ms() as i64;
|
||||
let no_users_exist =
|
||||
db_client.users.find_one(None, None).await?.is_none();
|
||||
db_client.users.find_one(Document::new()).await?.is_none();
|
||||
let user = User {
|
||||
id: Default::default(),
|
||||
username: google_user
|
||||
@@ -113,7 +114,7 @@ async fn callback(
|
||||
};
|
||||
let user_id = db_client
|
||||
.users
|
||||
.insert_one(user, None)
|
||||
.insert_one(user)
|
||||
.await
|
||||
.context("failed to create user on mongo")?
|
||||
.inserted_id
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::str::FromStr;
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_timing_util::unix_timestamp_ms;
|
||||
use axum::http::HeaderMap;
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::{
|
||||
api::auth::{
|
||||
CreateLocalUser, CreateLocalUserResponse, LoginLocalUser,
|
||||
@@ -46,7 +47,7 @@ impl Resolve<CreateLocalUser, HeaderMap> for State {
|
||||
let no_users_exist = db_client()
|
||||
.await
|
||||
.users
|
||||
.find_one(None, None)
|
||||
.find_one(Document::new())
|
||||
.await?
|
||||
.is_none();
|
||||
|
||||
@@ -72,7 +73,7 @@ impl Resolve<CreateLocalUser, HeaderMap> for State {
|
||||
let user_id = db_client()
|
||||
.await
|
||||
.users
|
||||
.insert_one(user, None)
|
||||
.insert_one(user)
|
||||
.await
|
||||
.context("failed to create user")?
|
||||
.inserted_id
|
||||
@@ -102,7 +103,7 @@ impl Resolve<LoginLocalUser, HeaderMap> for State {
|
||||
let user = db_client()
|
||||
.await
|
||||
.users
|
||||
.find_one(doc! { "username": &username }, None)
|
||||
.find_one(doc! { "username": &username })
|
||||
.await
|
||||
.context("failed at db query for users")?
|
||||
.with_context(|| {
|
||||
|
||||
@@ -127,7 +127,7 @@ pub async fn auth_api_key_get_user_id(
|
||||
let key = db_client()
|
||||
.await
|
||||
.api_keys
|
||||
.find_one(doc! { "key": key }, None)
|
||||
.find_one(doc! { "key": key })
|
||||
.await
|
||||
.context("failed to query db")?
|
||||
.context("no api key matching key")?;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use aws_config::BehaviorVersion;
|
||||
use aws_config::{BehaviorVersion, Region};
|
||||
use aws_sdk_ec2::{
|
||||
config::Region,
|
||||
types::{
|
||||
BlockDeviceMapping, EbsBlockDevice,
|
||||
InstanceNetworkInterfaceSpecification, InstanceStateChange,
|
||||
@@ -0,0 +1,82 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use aws_config::{BehaviorVersion, Region};
|
||||
use aws_sdk_ecr::Client as EcrClient;
|
||||
use run_command::async_run_command;
|
||||
|
||||
#[tracing::instrument(skip(access_key_id, secret_access_key))]
|
||||
async fn make_ecr_client(
|
||||
region: String,
|
||||
access_key_id: &str,
|
||||
secret_access_key: &str,
|
||||
) -> EcrClient {
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", access_key_id);
|
||||
std::env::set_var("AWS_SECRET_ACCESS_KEY", secret_access_key);
|
||||
let region = Region::new(region);
|
||||
let config = aws_config::defaults(BehaviorVersion::v2024_03_28())
|
||||
.region(region)
|
||||
.load()
|
||||
.await;
|
||||
EcrClient::new(&config)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(access_key_id, secret_access_key))]
|
||||
pub async fn maybe_create_repo(
|
||||
repo: &str,
|
||||
region: String,
|
||||
access_key_id: &str,
|
||||
secret_access_key: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let client =
|
||||
make_ecr_client(region, access_key_id, secret_access_key).await;
|
||||
|
||||
let existing = client
|
||||
.describe_repositories()
|
||||
.send()
|
||||
.await
|
||||
.context("failed to describe existing repositories")?
|
||||
.repositories
|
||||
.unwrap_or_default();
|
||||
|
||||
if existing.iter().any(|r| {
|
||||
if let Some(name) = r.repository_name() {
|
||||
name == repo
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
client
|
||||
.create_repository()
|
||||
.repository_name(repo)
|
||||
.send()
|
||||
.await
|
||||
.context("failed to create repository")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets a token docker login.
|
||||
///
|
||||
/// Requires the aws cli be installed on the host
|
||||
#[tracing::instrument(skip(access_key_id, secret_access_key))]
|
||||
pub async fn get_ecr_token(
|
||||
region: &str,
|
||||
access_key_id: &str,
|
||||
secret_access_key: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let log = async_run_command(&format!(
|
||||
"AWS_ACCESS_KEY_ID={access_key_id} AWS_SECRET_ACCESS_KEY={secret_access_key} aws ecr get-login-password --region {region}"
|
||||
))
|
||||
.await;
|
||||
|
||||
if log.success() {
|
||||
Ok(log.stdout)
|
||||
} else {
|
||||
Err(
|
||||
anyhow!("stdout: {} | stderr: {}", log.stdout, log.stderr)
|
||||
.context("failed to get aws ecr login token"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod ec2;
|
||||
pub mod ecr;
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod aws;
|
||||
|
||||
#[allow(unused)]
|
||||
pub mod hetzner;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
.unwrap_or(config.jwt_valid_for),
|
||||
sync_directory: env
|
||||
.monitor_sync_directory
|
||||
.map(|dir|
|
||||
.map(|dir|
|
||||
dir.parse()
|
||||
.context("failed to parse env MONITOR_SYNC_DIRECTORY as valid path").unwrap())
|
||||
.unwrap_or(config.sync_directory),
|
||||
@@ -157,6 +157,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
secrets: config.secrets,
|
||||
github_accounts: config.github_accounts,
|
||||
docker_accounts: config.docker_accounts,
|
||||
aws_ecr_registries: config.aws_ecr_registries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ impl DbClient {
|
||||
}
|
||||
}
|
||||
|
||||
async fn resource_collection<T>(
|
||||
async fn resource_collection<T: Send + Sync>(
|
||||
db: &Database,
|
||||
collection_name: &str,
|
||||
) -> anyhow::Result<Collection<T>> {
|
||||
|
||||
+198
-43
@@ -6,11 +6,12 @@ use monitor_client::entities::{
|
||||
alerter::*,
|
||||
deployment::DeploymentState,
|
||||
server::stats::SeverityLevel,
|
||||
update::ResourceTargetVariant,
|
||||
};
|
||||
use mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use slack::types::Block;
|
||||
|
||||
use crate::state::db_client;
|
||||
use crate::{config::core_config, state::db_client};
|
||||
|
||||
#[instrument]
|
||||
pub async fn send_alerts(alerts: &[Alert]) {
|
||||
@@ -61,7 +62,12 @@ async fn send_alert(alerters: &[Alerter], alert: &Alert) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Don't send if resource target not configured on the alerter
|
||||
// Don't send if resource is in the blacklist
|
||||
if alerter.config.except_resources.contains(&alert.target) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Don't send if whitelist configured and target is not included
|
||||
if !alerter.config.resources.is_empty()
|
||||
&& !alerter.config.resources.contains(&alert.target)
|
||||
{
|
||||
@@ -126,7 +132,12 @@ async fn send_slack_alert(
|
||||
) -> anyhow::Result<()> {
|
||||
let level = fmt_level(alert.level);
|
||||
let (text, blocks): (_, Option<_>) = match &alert.data {
|
||||
AlertData::ServerUnreachable { name, region, .. } => {
|
||||
AlertData::ServerUnreachable {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
err,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
@@ -143,10 +154,18 @@ async fn send_slack_alert(
|
||||
SeverityLevel::Critical => {
|
||||
let text =
|
||||
format!("{level} | *{name}*{region} is *unreachable* ❌");
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|e| format!("\nerror: {e:#?}"))
|
||||
.unwrap_or_default();
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} is *unreachable* ❌"
|
||||
"*{name}*{region} is *unreachable* ❌{err}"
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
@@ -155,70 +174,136 @@ async fn send_slack_alert(
|
||||
}
|
||||
}
|
||||
AlertData::ServerCpu {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
percentage,
|
||||
..
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let text = format!("{level} | *{name}*{region} cpu usage at *{percentage:.1}%* 📈 🚨");
|
||||
let blocks = vec![
|
||||
Block::header(format!("{level} 🚨")),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} cpu usage at *{percentage:.1}%* 📈 🚨"
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
let text = format!("{level} | *{name}*{region} cpu usage at *{percentage:.1}%*");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} cpu usage at *{percentage:.1}%*"
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
_ => {
|
||||
let text = format!("{level} | *{name}*{region} cpu usage at *{percentage:.1}%* 📈");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} cpu usage at *{percentage:.1}%* 📈"
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertData::ServerMem {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
used_gb,
|
||||
total_gb,
|
||||
..
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let percentage = 100.0 * used_gb / total_gb;
|
||||
let text =
|
||||
format!("{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾 🚨");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} memory usage at *{percentage:.1}%* 💾 🚨"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
let text = format!("{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} memory usage at *{percentage:.1}%* 💾"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
_ => {
|
||||
let text = format!("{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} memory usage at *{percentage:.1}%* 💾"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertData::ServerDisk {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
path,
|
||||
used_gb,
|
||||
total_gb,
|
||||
..
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let percentage = 100.0 * used_gb / total_gb;
|
||||
let text = format!("{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿 🚨");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} disk usage at *{percentage:.1}%* 💿 🚨"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"mount point: {path:?} | using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
let text = format!("{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} disk usage at *{percentage:.1}%* 💿"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"mount point: {path:?} | using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
Block::section(resource_link(ResourceTargetVariant::Server, id)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
_ => {
|
||||
let text = format!("{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿");
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
Block::section(format!(
|
||||
"*{name}*{region} disk usage at *{percentage:.1}%* 💿"
|
||||
)),
|
||||
Block::section(format!(
|
||||
"mount point: {path:?} | using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*"
|
||||
)),
|
||||
Block::section(resource_link(ResourceTargetVariant::Server, id)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertData::ContainerStateChange {
|
||||
name,
|
||||
server_name,
|
||||
from,
|
||||
to,
|
||||
id,
|
||||
..
|
||||
} => {
|
||||
let to = fmt_docker_container_state(to);
|
||||
@@ -226,7 +311,11 @@ async fn send_slack_alert(
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"server: {server_name}\nprevious: {from}"
|
||||
"server: {server_name}\nprevious: {from}",
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Deployment,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
@@ -236,12 +325,12 @@ async fn send_slack_alert(
|
||||
message,
|
||||
} => {
|
||||
let text = format!(
|
||||
"{level} | Failed to terminated AWS builder instance"
|
||||
"{level} | Failed to terminated AWS builder instance "
|
||||
);
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"instance id: **{instance_id}**\n{message}"
|
||||
"instance id: *{instance_id}*\n{message}"
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
@@ -252,8 +341,40 @@ async fn send_slack_alert(
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"sync id: **{id}**\nsync name: **{name}**"
|
||||
"sync id: *{id}*\nsync name: *{name}*",
|
||||
)),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::ResourceSync,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::BuildFailed {
|
||||
id,
|
||||
name,
|
||||
version,
|
||||
err,
|
||||
} => {
|
||||
let text = format!("{level} | Build {name} has failed");
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|log| {
|
||||
let stdout = (!log.stdout.is_empty())
|
||||
.then(|| format!("\nstdout: {}", log.stdout))
|
||||
.unwrap_or_default();
|
||||
let stderr = (!log.stderr.is_empty())
|
||||
.then(|| format!("\nstderr: {}", log.stderr))
|
||||
.unwrap_or_default();
|
||||
format!("\nfailed at stage: {}{stdout}{stderr}", log.stage)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"build id: *{id}*\nbuild name: *{name}*\nversion: v{version}{err}",
|
||||
)),
|
||||
Block::section(resource_link(ResourceTargetVariant::Build, id))
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
@@ -286,7 +407,41 @@ fn fmt_docker_container_state(state: &DeploymentState) -> String {
|
||||
fn fmt_level(level: SeverityLevel) -> &'static str {
|
||||
match level {
|
||||
SeverityLevel::Critical => "CRITICAL 🚨",
|
||||
SeverityLevel::Warning => "WARNING 🚨",
|
||||
SeverityLevel::Warning => "WARNING ‼️",
|
||||
SeverityLevel::Ok => "OK ✅",
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_link(
|
||||
resource_type: ResourceTargetVariant,
|
||||
id: &str,
|
||||
) -> String {
|
||||
let path = match resource_type {
|
||||
ResourceTargetVariant::System => unreachable!(),
|
||||
ResourceTargetVariant::Build => format!("/builds/{id}"),
|
||||
ResourceTargetVariant::Builder => {
|
||||
format!("/builders/{id}")
|
||||
}
|
||||
ResourceTargetVariant::Deployment => {
|
||||
format!("/deployments/{id}")
|
||||
}
|
||||
ResourceTargetVariant::Server => {
|
||||
format!("/servers/{id}")
|
||||
}
|
||||
ResourceTargetVariant::Repo => format!("/repos/{id}"),
|
||||
ResourceTargetVariant::Alerter => {
|
||||
format!("/alerters/{id}")
|
||||
}
|
||||
ResourceTargetVariant::Procedure => {
|
||||
format!("/procedures/{id}")
|
||||
}
|
||||
ResourceTargetVariant::ServerTemplate => {
|
||||
format!("/server-templates/{id}")
|
||||
}
|
||||
ResourceTargetVariant::ResourceSync => {
|
||||
format!("/resource-syncs/{id}")
|
||||
}
|
||||
};
|
||||
|
||||
format!("{}{path}", core_config().host)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::entities::{
|
||||
permission::{Permission, PermissionLevel, UserTarget},
|
||||
server::Server,
|
||||
update::ResourceTarget,
|
||||
user::User,
|
||||
};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use mungos::mongodb::bson::{doc, Bson};
|
||||
use periphery_client::PeripheryClient;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
@@ -61,7 +62,6 @@ where
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to remove resource from users recently viewed")
|
||||
@@ -110,10 +110,29 @@ pub async fn create_permission<T>(
|
||||
resource_target: target.clone(),
|
||||
level,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("failed to create permission for {target:?} | {e:#}");
|
||||
};
|
||||
}
|
||||
|
||||
/// Flattens a document only one level deep
|
||||
///
|
||||
/// eg `{ config: { label: "yes", thing: { field1: "ok", field2: "ok" } } }` ->
|
||||
/// `{ "config.label": "yes", "config.thing": { field1: "ok", field2: "ok" } }`
|
||||
pub fn flatten_document(doc: Document) -> Document {
|
||||
let mut target = Document::new();
|
||||
|
||||
for (outer_field, bson) in doc {
|
||||
if let Bson::Document(doc) = bson {
|
||||
for (inner_field, bson) in doc {
|
||||
target.insert(format!("{outer_field}.{inner_field}"), bson);
|
||||
}
|
||||
} else {
|
||||
target.insert(outer_field, bson);
|
||||
}
|
||||
}
|
||||
|
||||
target
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{anyhow, Context, Ok};
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
@@ -21,9 +22,16 @@ pub async fn execute_procedure(
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
for stage in &procedure.config.stages {
|
||||
if !stage.enabled {
|
||||
continue;
|
||||
}
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!("executing stage: {}", stage.name),
|
||||
&format!(
|
||||
"{}: executing stage: '{}'",
|
||||
muted("INFO"),
|
||||
bold(&stage.name)
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let timer = Instant::now();
|
||||
@@ -41,16 +49,19 @@ pub async fn execute_procedure(
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed stage '{}' execution after {:?}",
|
||||
stage.name,
|
||||
"{}: failed stage '{}' execution after {:?}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&stage.name),
|
||||
timer.elapsed(),
|
||||
)
|
||||
})?;
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished stage '{}' execution in {:?} ✅",
|
||||
stage.name,
|
||||
"{}: {} stage '{}' execution in {:?}",
|
||||
muted("INFO"),
|
||||
colored("finished", Color::Green),
|
||||
bold(&stage.name),
|
||||
timer.elapsed()
|
||||
),
|
||||
)
|
||||
@@ -69,9 +80,15 @@ async fn execute_stage(
|
||||
) -> anyhow::Result<()> {
|
||||
let futures = executions.into_iter().map(|execution| async move {
|
||||
let now = Instant::now();
|
||||
add_line_to_update(update, &format!("executing: {execution:?}"))
|
||||
.await;
|
||||
let fail_log = format!("failed on {execution:?}");
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!("{}: executing: {execution:?}", muted("INFO")),
|
||||
)
|
||||
.await;
|
||||
let fail_log = format!(
|
||||
"{}: failed on {execution:?}",
|
||||
colored("ERROR", Color::Red)
|
||||
);
|
||||
let res =
|
||||
execute_execution(execution.clone(), parent_id, parent_name)
|
||||
.await
|
||||
@@ -79,7 +96,9 @@ async fn execute_stage(
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished execution in {:?}: {execution:?}",
|
||||
"{}: {} execution in {:?}: {execution:?}",
|
||||
muted("INFO"),
|
||||
colored("finished", Color::Green),
|
||||
now.elapsed()
|
||||
),
|
||||
)
|
||||
@@ -263,8 +282,9 @@ async fn execute_execution(
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"execution not successful. see update {}",
|
||||
update.id
|
||||
"{}: execution not successful. see update '{}'",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&update.id),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ async fn prune_stats() -> anyhow::Result<()> {
|
||||
doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
info!("deleted {} stats from db", res.deleted_count);
|
||||
@@ -93,7 +92,6 @@ async fn prune_alerts() -> anyhow::Result<()> {
|
||||
doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
info!("deleted {} alerts from db", res.deleted_count);
|
||||
|
||||
@@ -89,7 +89,7 @@ pub async fn get_tag(id_or_name: &str) -> anyhow::Result<Tag> {
|
||||
db_client()
|
||||
.await
|
||||
.tags
|
||||
.find_one(query, None)
|
||||
.find_one(query)
|
||||
.await
|
||||
.context("failed to query mongo for tag")?
|
||||
.with_context(|| format!("no tag found matching {id_or_name}"))
|
||||
@@ -240,7 +240,7 @@ pub async fn get_variable(name: &str) -> anyhow::Result<Variable> {
|
||||
db_client()
|
||||
.await
|
||||
.variables
|
||||
.find_one(doc! { "name": &name }, None)
|
||||
.find_one(doc! { "name": &name })
|
||||
.await
|
||||
.context("failed at call to db")?
|
||||
.with_context(|| {
|
||||
@@ -256,12 +256,12 @@ pub async fn get_latest_update(
|
||||
db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": resource_type.as_ref(),
|
||||
"target.id": id,
|
||||
"operation": operation.as_ref()
|
||||
},
|
||||
.find_one(doc! {
|
||||
"target.type": resource_type.as_ref(),
|
||||
"target.id": id,
|
||||
"operation": operation.as_ref()
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
|
||||
@@ -0,0 +1,858 @@
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::{execute::Deploy, read::GetBuildVersions},
|
||||
entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentImage, DeploymentState,
|
||||
PartialDeploymentConfig,
|
||||
},
|
||||
sync::SyncUpdate,
|
||||
tag::Tag,
|
||||
toml::ResourceToml,
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
helpers::update::init_execution_update,
|
||||
resource::MonitorResource,
|
||||
state::{deployment_status_cache, State},
|
||||
};
|
||||
|
||||
use super::resource::{
|
||||
run_update_description, run_update_tags, AllResourcesById,
|
||||
ResourceSync,
|
||||
};
|
||||
|
||||
pub type ToUpdate = Vec<ToUpdateItem>;
|
||||
pub type ToCreate = Vec<ResourceToml<PartialDeploymentConfig>>;
|
||||
/// Vec of resource names
|
||||
pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult = (ToCreate, ToUpdate, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<PartialDeploymentConfig>,
|
||||
pub update_description: bool,
|
||||
pub update_tags: bool,
|
||||
pub deploy: bool,
|
||||
}
|
||||
|
||||
/// Turns all the diffs into a readable string
|
||||
pub async fn get_updates_for_view(
|
||||
resources: Vec<ResourceToml<PartialDeploymentConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(Deployment::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get deployments from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: format!("{} Updates", Deployment::resource_type()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(resource.name.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_deploy_cache = HashMap::<String, bool>::new();
|
||||
let mut to_deploy_build_cache = HashMap::<String, String>::new();
|
||||
|
||||
for mut resource in resources.clone() {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (to_deploy, state, reason) = extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
&map,
|
||||
&resources,
|
||||
resource.name.clone(),
|
||||
&mut to_deploy_cache,
|
||||
&mut to_deploy_build_cache,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& !to_deploy
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
if state == DeploymentState::Unknown {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\nDeployment sync actions could not be computed due to Unknown deployment state\n-------------------",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
update.to_update += 1;
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", Color::Blue),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
if resource.description != original.description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&resource.description, Color::Green)
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from =
|
||||
colored(&format!("{:?}", original_tags), Color::Red);
|
||||
let to =
|
||||
colored(&format!("{:?}", resource.tags), Color::Green);
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
muted("to"),
|
||||
));
|
||||
}
|
||||
lines.extend(diff.iter_field_diffs().map(
|
||||
|FieldDiff { field, from, to }| {
|
||||
format!(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(from, Color::Red),
|
||||
muted("to"),
|
||||
colored(to, Color::Green)
|
||||
)
|
||||
},
|
||||
));
|
||||
if state == DeploymentState::Unknown {
|
||||
lines.push(format!(
|
||||
"{}: Deployment sync actions {} due to Unknown deployment state",
|
||||
colored("ERROR", Color::Red),
|
||||
bold("could not be computed")
|
||||
));
|
||||
} else if to_deploy {
|
||||
let mut line = if state == DeploymentState::Running {
|
||||
format!(
|
||||
"{}: {reason}, {}",
|
||||
muted("deploy"),
|
||||
bold("sync will trigger deploy")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}: deployment is currently in {} state, {}",
|
||||
muted("deploy"),
|
||||
colored(&state.to_string(), Color::Red),
|
||||
bold("sync will trigger deploy")
|
||||
)
|
||||
};
|
||||
if !resource.after.is_empty() {
|
||||
line.push_str(&format!(
|
||||
"\n{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
let mut lines = vec![
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("description"),
|
||||
resource.description,
|
||||
),
|
||||
format!("{}: {:?}", muted("tags"), resource.tags,),
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("config"),
|
||||
serde_json::to_string_pretty(&resource.config)
|
||||
.context("failed to serialize config to json")?
|
||||
),
|
||||
];
|
||||
if resource.deploy {
|
||||
lines.push(format!(
|
||||
"{}: {}",
|
||||
muted("will deploy"),
|
||||
colored("true", Color::Green)
|
||||
));
|
||||
if !resource.after.is_empty() {
|
||||
lines.push(format!(
|
||||
"{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
}
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}",
|
||||
colored("CREATE", Color::Green),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name),
|
||||
lines.join("\n")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
/// Gets all the resources to update. For use in sync execution.
|
||||
pub async fn get_updates_for_execution(
|
||||
resources: Vec<ResourceToml<PartialDeploymentConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<UpdatesResult> {
|
||||
let map = find_collect(Deployment::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get deployments from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = ToCreate::new();
|
||||
let mut to_update = ToUpdate::new();
|
||||
let mut to_delete = ToDelete::new();
|
||||
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
to_delete.push(resource.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_deploy_cache = HashMap::<String, bool>::new();
|
||||
let mut to_deploy_build_cache = HashMap::<String, String>::new();
|
||||
|
||||
for mut resource in resources.clone() {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (to_deploy, _state, _reason) =
|
||||
extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
&map,
|
||||
&resources,
|
||||
resource.name.clone(),
|
||||
&mut to_deploy_cache,
|
||||
&mut to_deploy_build_cache,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& !to_deploy
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Minimizes updates through diffing.
|
||||
resource.config = diff.into();
|
||||
|
||||
let update = ToUpdateItem {
|
||||
id: original.id.clone(),
|
||||
update_description: resource.description
|
||||
!= original.description,
|
||||
update_tags: resource.tags != original_tags,
|
||||
resource,
|
||||
deploy: to_deploy,
|
||||
};
|
||||
|
||||
to_update.push(update);
|
||||
}
|
||||
None => to_create.push(resource),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
type Res<'a> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<(bool, DeploymentState, String)>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn extract_to_deploy_and_state<'a>(
|
||||
all_resources: &'a AllResourcesById,
|
||||
map: &'a HashMap<String, Deployment>,
|
||||
resources: &'a [ResourceToml<PartialDeploymentConfig>],
|
||||
name: String,
|
||||
// name to 'to_deploy'
|
||||
cache: &'a mut HashMap<String, bool>,
|
||||
// build id to latest built version string
|
||||
build_cache: &'a mut HashMap<String, String>,
|
||||
) -> Res<'a> {
|
||||
Box::pin(async move {
|
||||
let mut reason = String::new();
|
||||
let Some(deployment) = resources.iter().find(|r| r.name == name)
|
||||
else {
|
||||
// this case should be unreachable, the names come off of a loop over resources
|
||||
cache.insert(name, false);
|
||||
return Ok((false, DeploymentState::Unknown, reason));
|
||||
};
|
||||
if deployment.deploy {
|
||||
let Some(original) = map.get(&name) else {
|
||||
// not created, definitely deploy
|
||||
cache.insert(name, true);
|
||||
// Don't need reason here, will be populated automatically
|
||||
return Ok((true, DeploymentState::NotDeployed, reason));
|
||||
};
|
||||
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = deployment.config.clone().into();
|
||||
let mut config: PartialDeploymentConfig = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let status = &deployment_status_cache()
|
||||
.get_or_insert_default(&original.id)
|
||||
.await
|
||||
.curr;
|
||||
let state = status.state;
|
||||
|
||||
let mut to_deploy = match state {
|
||||
DeploymentState::Unknown => false,
|
||||
DeploymentState::Running => {
|
||||
// Needs to only check config fields that affect docker run
|
||||
let changed = diff.server_id.is_some()
|
||||
|| diff.image.is_some()
|
||||
|| diff.image_registry.is_some()
|
||||
|| diff.skip_secret_interp.is_some()
|
||||
|| diff.network.is_some()
|
||||
|| diff.restart.is_some()
|
||||
|| diff.command.is_some()
|
||||
|| diff.extra_args.is_some()
|
||||
|| diff.ports.is_some()
|
||||
|| diff.volumes.is_some()
|
||||
|| diff.environment.is_some()
|
||||
|| diff.labels.is_some();
|
||||
if changed {
|
||||
reason = String::from("deployment config has changed")
|
||||
}
|
||||
changed
|
||||
}
|
||||
// All other cases will require Deploy to enter Running state.
|
||||
// Don't need reason here as this case is handled outside, using returned state.
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Check if build attached, version latest, and there is a new build.
|
||||
if !to_deploy {
|
||||
// only need to check original, if diff.image was Some, to_deploy would be true.
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.config.image
|
||||
{
|
||||
// check if version is none, ie use latest build
|
||||
if version.is_none() {
|
||||
let deployed_version = status
|
||||
.container
|
||||
.as_ref()
|
||||
.and_then(|c| c.image.split(':').last())
|
||||
.unwrap_or("0.0.0");
|
||||
match build_cache.get(build_id) {
|
||||
Some(version) if deployed_version != version => {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"attached build has new version ({version})"
|
||||
);
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let Some(version) = State
|
||||
.resolve(
|
||||
GetBuildVersions {
|
||||
build: build_id.to_string(),
|
||||
limit: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to get build versions")?
|
||||
.pop()
|
||||
else {
|
||||
// this case shouldn't ever happen, how would deployment be deployed if build was never built?
|
||||
return Ok((
|
||||
false,
|
||||
DeploymentState::NotDeployed,
|
||||
reason,
|
||||
));
|
||||
};
|
||||
let version = version.version.to_string();
|
||||
build_cache
|
||||
.insert(build_id.to_string(), version.clone());
|
||||
if deployed_version != version {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"attached build has new version ({version})"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still need to check 'after' if they need deploy
|
||||
if !to_deploy {
|
||||
for name in &deployment.after {
|
||||
match cache.get(name) {
|
||||
Some(will_deploy) if *will_deploy => {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"parent dependency '{}' is deploying",
|
||||
bold(name)
|
||||
);
|
||||
break;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let (will_deploy, _, _) = extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
map,
|
||||
resources,
|
||||
name.to_string(),
|
||||
cache,
|
||||
build_cache,
|
||||
)
|
||||
.await?;
|
||||
if will_deploy {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"parent dependency '{}' is deploying",
|
||||
bold(name)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(name, to_deploy);
|
||||
Ok((to_deploy, state, reason))
|
||||
} else {
|
||||
// The state in this case doesn't matter and won't be read (as long as it isn't 'Unknown' which will log in all cases)
|
||||
cache.insert(name, false);
|
||||
Ok((false, DeploymentState::NotDeployed, reason))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: ToCreate,
|
||||
to_update: ToUpdate,
|
||||
to_delete: ToDelete,
|
||||
) -> Option<Vec<Log>> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log = String::new();
|
||||
|
||||
// Collect all the deployment names that need to be deployed
|
||||
// and their 'after' dependencies
|
||||
let mut to_deploy = Vec::<(String, Vec<String>)>::new();
|
||||
|
||||
for resource in to_create {
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
let id = match crate::resource::create::<Deployment>(
|
||||
&resource.name,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resource) => resource.id,
|
||||
Err(e) => {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
run_update_tags::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_description::<Deployment>(
|
||||
id,
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", Color::Green),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
if resource.deploy {
|
||||
to_deploy.push((resource.name, resource.after));
|
||||
}
|
||||
}
|
||||
|
||||
for ToUpdateItem {
|
||||
id,
|
||||
resource,
|
||||
update_description,
|
||||
update_tags,
|
||||
deploy,
|
||||
} in to_update
|
||||
{
|
||||
// Update resource
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
|
||||
if update_description {
|
||||
run_update_description::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if update_tags {
|
||||
run_update_tags::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let mut config_update_error = false;
|
||||
if !resource.config.is_none() {
|
||||
if let Err(e) = crate::resource::update::<Deployment>(
|
||||
&id,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
config_update_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", Color::Blue),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !config_update_error && deploy {
|
||||
to_deploy.push((resource.name, resource.after));
|
||||
}
|
||||
}
|
||||
|
||||
for resource in to_delete {
|
||||
if let Err(e) =
|
||||
crate::resource::delete::<Deployment>(&resource, sync_user())
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut logs = Vec::with_capacity(1);
|
||||
|
||||
let stage = format!("Update {}s", Deployment::resource_type());
|
||||
if has_error {
|
||||
let log = format!(
|
||||
"running updates on {}s{log}",
|
||||
Deployment::resource_type()
|
||||
);
|
||||
logs.push(Log::error(&stage, log));
|
||||
return Some(logs);
|
||||
} else if !log.is_empty() {
|
||||
let log = format!(
|
||||
"running updates on {}s{log}",
|
||||
Deployment::resource_type()
|
||||
);
|
||||
logs.push(Log::simple(&stage, log));
|
||||
}
|
||||
|
||||
if to_deploy.is_empty() {
|
||||
return Some(logs);
|
||||
}
|
||||
|
||||
let mut log = format!(
|
||||
"{}: running executions to sync deployment state",
|
||||
muted("INFO")
|
||||
);
|
||||
let mut round = 1;
|
||||
|
||||
while !to_deploy.is_empty() {
|
||||
// Collect all waiting deployments without waiting dependencies.
|
||||
let good_to_deploy = to_deploy
|
||||
.iter()
|
||||
.filter(|(_, after)| {
|
||||
to_deploy.iter().all(|(name, _)| !after.contains(name))
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Deploy the ones ready for deployment
|
||||
let res =
|
||||
join_all(good_to_deploy.iter().map(|name| async move {
|
||||
let res = async {
|
||||
let req = ExecuteRequest::Deploy(Deploy {
|
||||
deployment: name.to_string(),
|
||||
stop_signal: None,
|
||||
stop_time: None,
|
||||
});
|
||||
let user = sync_user();
|
||||
let update = init_execution_update(&req, user).await?;
|
||||
let ExecuteRequest::Deploy(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user.to_owned(), update)).await
|
||||
}
|
||||
.await;
|
||||
(name, res)
|
||||
}))
|
||||
.await;
|
||||
|
||||
// Log results of deploy
|
||||
for (name, res) in res {
|
||||
if let Err(e) = res {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to deploy '{}' in round {} | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(name),
|
||||
bold(round)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: deployed '{}' in round {}",
|
||||
muted("INFO"),
|
||||
bold(name),
|
||||
bold(round)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Early exit if any deploy has errors
|
||||
if has_error {
|
||||
log.push_str(&format!(
|
||||
"\n{}: exited in round {} {}",
|
||||
muted("INFO"),
|
||||
bold(round),
|
||||
colored("with errors", Color::Red)
|
||||
));
|
||||
logs.push(Log::error("Sync Deployment State", log));
|
||||
return Some(logs);
|
||||
}
|
||||
|
||||
// Remove the deployed ones from 'to_deploy'
|
||||
to_deploy.retain(|(name, _)| !good_to_deploy.contains(name));
|
||||
|
||||
// If there must be another round, these are dependent on the first round.
|
||||
// Sleep for 1s to allow for first round to startup
|
||||
if !to_deploy.is_empty() {
|
||||
// Increment the round
|
||||
round += 1;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
log.push_str(&format!(
|
||||
"\n{}: finished after {} round{}",
|
||||
muted("INFO"),
|
||||
bold(round),
|
||||
(round > 1).then_some("s").unwrap_or_default()
|
||||
));
|
||||
|
||||
logs.push(Log::simple("Sync Deployment State", log));
|
||||
|
||||
Some(logs)
|
||||
}
|
||||
|
||||
impl ResourceSync for Deployment {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace the server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// need to replace the build id with name
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: resources
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: *version,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{colored, muted, Color};
|
||||
use monitor_client::entities::{toml::ResourcesToml, update::Log};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::{colored, muted};
|
||||
|
||||
pub fn read_resources(
|
||||
path: &Path,
|
||||
) -> anyhow::Result<(ResourcesToml, Log)> {
|
||||
let mut res = ResourcesToml::default();
|
||||
let mut log = format!("reading resources from {path:?}");
|
||||
read_resources_recursive(path, &mut res, &mut log)?;
|
||||
let mut log =
|
||||
format!("{}: reading resources from {path:?}", muted("INFO"));
|
||||
read_resources_recursive(path, &mut res, &mut log).with_context(
|
||||
|| format!("failed to read resources from {path:?}"),
|
||||
)?;
|
||||
Ok((res, Log::simple("read remote resources", log)))
|
||||
}
|
||||
|
||||
@@ -30,20 +32,15 @@ fn read_resources_recursive(
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let more = match parse_toml_file::<ResourcesToml>(path) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("failed to parse {:?}. skipping file | {e:#}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let more = parse_toml_file::<ResourcesToml>(path)
|
||||
.context("failed to parse resource file")?;
|
||||
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: {} from {}",
|
||||
muted("INFO"),
|
||||
colored("adding resources", "green"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
colored("adding resources", Color::Green),
|
||||
colored(path.display(), Color::Blue)
|
||||
));
|
||||
|
||||
resources.servers.extend(more.servers);
|
||||
@@ -63,15 +60,9 @@ fn read_resources_recursive(
|
||||
.context("failed to read directory contents")?;
|
||||
for entry in directory.into_iter().flatten() {
|
||||
let path = entry.path();
|
||||
if let Err(e) = read_resources_recursive(&path, resources, log)
|
||||
{
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: failed to read additional resources from {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
));
|
||||
}
|
||||
read_resources_recursive(&path, resources, log).with_context(
|
||||
|| format!("failed to read resources from {path:?}"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -84,5 +75,8 @@ fn parse_toml_file<T: DeserializeOwned>(
|
||||
) -> anyhow::Result<T> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.context("failed to read file contents")?;
|
||||
toml::from_str(&contents).context("failed to parse toml contents")
|
||||
toml::from_str(&contents)
|
||||
// the error without this comes through with multiple lines (\n) and looks bad
|
||||
.map_err(|e| anyhow!("{e:#}"))
|
||||
.context("failed to parse toml contents")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
use async_timing_util::{wait_until_timelength, Timelength};
|
||||
use monitor_client::{
|
||||
api::write::RefreshResourceSyncPending, entities::user::sync_user,
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
pub mod deployment;
|
||||
pub mod remote;
|
||||
pub mod resource;
|
||||
pub mod user_groups;
|
||||
@@ -6,14 +16,29 @@ pub mod variables;
|
||||
mod file;
|
||||
mod resources;
|
||||
|
||||
fn muted(content: &str) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(content: &str, color: &str) -> String {
|
||||
format!("<span class=\"text-{color}-500\">{content}</span>")
|
||||
pub fn spawn_sync_refresh_loop() {
|
||||
tokio::spawn(async move {
|
||||
let db = db_client().await;
|
||||
let user = sync_user();
|
||||
loop {
|
||||
wait_until_timelength(Timelength::FiveMinutes, 0).await;
|
||||
let Ok(syncs) = find_collect(&db.resource_syncs, None, None)
|
||||
.await
|
||||
.inspect_err(|e| warn!("failed to get resource syncs from db in refresh task | {e:#}")) else {
|
||||
continue;
|
||||
};
|
||||
for sync in syncs {
|
||||
State
|
||||
.resolve(
|
||||
RefreshResourceSyncPending { sync: sync.id },
|
||||
user.clone(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to refresh resource sync in refresh task | sync: {} | {e:#}", sync.name)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::write::{UpdateDescription, UpdateTagsOnResource},
|
||||
entities::{
|
||||
@@ -26,8 +27,6 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource::MonitorResource, state::State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub type ToUpdate<T> = Vec<ToUpdateItem<T>>;
|
||||
pub type ToCreate<T> = Vec<ResourceToml<T>>;
|
||||
/// Vec of resource names
|
||||
@@ -35,7 +34,7 @@ pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem<T> {
|
||||
pub struct ToUpdateItem<T: Default> {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<T>,
|
||||
pub update_description: bool,
|
||||
@@ -93,7 +92,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -119,7 +118,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -170,7 +169,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
@@ -178,7 +177,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -193,7 +192,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
@@ -201,7 +200,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&resource)
|
||||
));
|
||||
@@ -283,7 +282,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
@@ -294,14 +293,16 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&resource.description, "green")
|
||||
colored(&resource.description, Color::Green)
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from = colored(&format!("{:?}", original_tags), "red");
|
||||
let to = colored(&format!("{:?}", resource.tags), "green");
|
||||
let from =
|
||||
colored(&format!("{:?}", original_tags), Color::Red);
|
||||
let to =
|
||||
colored(&format!("{:?}", resource.tags), Color::Green);
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
@@ -315,9 +316,9 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&from, "red"),
|
||||
colored(from, Color::Red),
|
||||
muted("to"),
|
||||
colored(&to, "green")
|
||||
colored(to, Color::Green)
|
||||
)
|
||||
},
|
||||
));
|
||||
@@ -328,7 +329,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored("CREATE", Color::Green),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name),
|
||||
muted("description"),
|
||||
@@ -346,7 +347,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -459,7 +460,7 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update tags on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
@@ -467,7 +468,7 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' tags",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
@@ -494,7 +495,7 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update description on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
@@ -502,7 +503,7 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
@@ -5,7 +6,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
@@ -17,12 +17,9 @@ use monitor_client::{
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
helpers::sync::{
|
||||
bold, colored, muted,
|
||||
resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
helpers::sync::resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
resource::MonitorResource,
|
||||
};
|
||||
@@ -73,41 +70,6 @@ impl ResourceSync for Build {
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Deployment {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace the server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// need to replace the build id with name
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: resources
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: version.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Repo {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Repo(id)
|
||||
@@ -329,7 +291,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
@@ -337,7 +299,7 @@ impl ResourceSync for Procedure {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -398,7 +360,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -437,7 +399,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -464,7 +426,7 @@ impl ResourceSync for Procedure {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::{
|
||||
read::ListUserTargetPermissions,
|
||||
@@ -22,7 +23,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted, resource::AllResourcesById};
|
||||
use super::resource::AllResourcesById;
|
||||
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
@@ -77,8 +78,8 @@ pub async fn get_updates_for_view(
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:?}\n{}: {:?}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&user_group.name, "green"),
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&user_group.name, Color::Green),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
@@ -196,7 +197,7 @@ pub async fn get_updates_for_view(
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
bold(&user_group.name),
|
||||
));
|
||||
let mut lines = Vec::<String>::new();
|
||||
@@ -210,7 +211,7 @@ pub async fn get_updates_for_view(
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
colored(&adding.join(", "), Color::Green)
|
||||
};
|
||||
let removing = original_users
|
||||
.iter()
|
||||
@@ -220,7 +221,7 @@ pub async fn get_updates_for_view(
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
colored(&removing.join(", "), Color::Red)
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'users'\n{}: {removing}\n{}: {adding}",
|
||||
@@ -241,7 +242,7 @@ pub async fn get_updates_for_view(
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
colored(&adding.join(", "), Color::Green)
|
||||
};
|
||||
let removing = original_permissions
|
||||
.iter()
|
||||
@@ -253,7 +254,7 @@ pub async fn get_updates_for_view(
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
colored(&removing.join(", "), Color::Red)
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {adding}",
|
||||
@@ -270,7 +271,7 @@ pub async fn get_updates_for_view(
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
@@ -495,7 +496,7 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group.name)
|
||||
));
|
||||
continue;
|
||||
@@ -503,7 +504,7 @@ pub async fn run_updates(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
};
|
||||
@@ -562,14 +563,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
}
|
||||
@@ -602,14 +603,14 @@ async fn set_users(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set users in group {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' users",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
@@ -636,14 +637,14 @@ async fn run_update_permissions(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set permssion in group {} | target: {target:?} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' permissions",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateVariable, DeleteVariable, UpdateVariableDescription,
|
||||
@@ -16,8 +17,6 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub variable: Variable,
|
||||
pub update_value: bool,
|
||||
@@ -66,7 +65,7 @@ pub async fn get_updates_for_view(
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
bold(&item.variable.name),
|
||||
));
|
||||
|
||||
@@ -77,9 +76,9 @@ pub async fn get_updates_for_view(
|
||||
"{}: 'value'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.value, "red"),
|
||||
colored(&original.value, Color::Red),
|
||||
muted("to"),
|
||||
colored(&item.variable.value, "green")
|
||||
colored(&item.variable.value, Color::Green)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -88,9 +87,9 @@ pub async fn get_updates_for_view(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&item.variable.description, "green")
|
||||
colored(&item.variable.description, Color::Green)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -99,15 +98,25 @@ pub async fn get_updates_for_view(
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&variable.name, "green"),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
if variable.description.is_empty() {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&variable.name, Color::Green),
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
} else {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&variable.name, Color::Green),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +124,7 @@ pub async fn get_updates_for_view(
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
@@ -202,14 +211,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -235,14 +244,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable value for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' value",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -261,14 +270,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable description for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -288,14 +297,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
bold(&variable)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ pub async fn add_update(
|
||||
update.id = db_client()
|
||||
.await
|
||||
.updates
|
||||
.insert_one(&update, None)
|
||||
.insert_one(&update)
|
||||
.await
|
||||
.context("failed to insert update into db")?
|
||||
.inserted_id
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{extract::Path, http::HeaderMap, routing::post, Router};
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use monitor_client::{
|
||||
api::{execute, write::RefreshResourceSyncPending},
|
||||
entities::{
|
||||
build::Build, procedure::Procedure, repo::Repo,
|
||||
sync::ResourceSync, user::github_user,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
cache::Cache, random_duration, update::init_execution_update,
|
||||
},
|
||||
resource,
|
||||
state::State,
|
||||
};
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IdBranch {
|
||||
id: String,
|
||||
branch: String,
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
"/build/:id",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("build_webhook", id);
|
||||
async {
|
||||
let res = handle_build_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run build webook for build {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/repo/:id/clone",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_clone_webhook", id);
|
||||
async {
|
||||
let res = handle_repo_clone_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo clone webook for repo {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/repo/:id/pull",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_pull_webhook", id);
|
||||
async {
|
||||
let res = handle_repo_pull_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo pull webook for repo {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/procedure/:id/:branch",
|
||||
post(
|
||||
|Path(IdBranch { id, branch }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("procedure_webhook", id, branch);
|
||||
async {
|
||||
let res = handle_procedure_webhook(
|
||||
id.clone(),
|
||||
branch,
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run procedure webook for procedure {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/refresh",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_refresh_webhook", id);
|
||||
async {
|
||||
let res = handle_sync_refresh_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/sync",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_execute_webhook", id);
|
||||
async {
|
||||
let res = handle_sync_execute_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_build_webhook(
|
||||
build_id: String,
|
||||
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 = resource::get::<Build>(&build_id).await?;
|
||||
if !build.config.webhook_enabled {
|
||||
return Err(anyhow!("build does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != build.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::RunBuild(
|
||||
execute::RunBuild { build: build_id },
|
||||
);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::RunBuild(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_repo_clone_webhook(
|
||||
repo_id: String,
|
||||
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 = resource::get::<Repo>(&repo_id).await?;
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != repo.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::CloneRepo(
|
||||
execute::CloneRepo { repo: repo_id },
|
||||
);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::CloneRepo(req) = req
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_repo_pull_webhook(
|
||||
repo_id: String,
|
||||
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 = resource::get::<Repo>(&repo_id).await?;
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != repo.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::PullRepo(
|
||||
execute::PullRepo { repo: repo_id },
|
||||
);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::PullRepo(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_procedure_webhook(
|
||||
procedure_id: String,
|
||||
target_branch: String,
|
||||
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 {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let procedure = resource::get::<Procedure>(&procedure_id).await?;
|
||||
if !procedure.config.webhook_enabled {
|
||||
return Err(anyhow!("procedure does not have webhook enabled"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::RunProcedure(
|
||||
execute::RunProcedure {
|
||||
procedure: procedure_id,
|
||||
},
|
||||
);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::RunProcedure(req) = req
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_sync_refresh_webhook(
|
||||
sync_id: String,
|
||||
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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
State
|
||||
.resolve(RefreshResourceSyncPending { sync: sync_id }, user)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_sync_execute_webhook(
|
||||
sync_id: String,
|
||||
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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req =
|
||||
crate::api::execute::ExecuteRequest::RunSync(execute::RunSync {
|
||||
sync: sync_id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::RunSync(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn verify_gh_signature(
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// wait random amount of time
|
||||
tokio::time::sleep(random_duration(0, 500)).await;
|
||||
|
||||
let signature = headers.get("x-hub-signature-256");
|
||||
if signature.is_none() {
|
||||
return Err(anyhow!("no signature in headers"));
|
||||
}
|
||||
let signature = signature.unwrap().to_str();
|
||||
if signature.is_err() {
|
||||
return Err(anyhow!("failed to unwrap signature"));
|
||||
}
|
||||
let signature = signature.unwrap().replace("sha256=", "");
|
||||
let mut mac = HmacSha256::new_from_slice(
|
||||
core_config().github_webhook_secret.as_bytes(),
|
||||
)
|
||||
.expect("github webhook | failed to create hmac sha256");
|
||||
mac.update(body.as_bytes());
|
||||
let expected = mac.finalize().into_bytes().encode_hex::<String>();
|
||||
if signature == expected {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("signature does not equal expected"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GithubWebhookBody {
|
||||
#[serde(rename = "ref")]
|
||||
branch: String,
|
||||
}
|
||||
|
||||
fn extract_branch(body: &str) -> anyhow::Result<String> {
|
||||
let branch = serde_json::from_str::<GithubWebhookBody>(body)
|
||||
.context("failed to parse github request body")?
|
||||
.branch
|
||||
.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)
|
||||
}
|
||||
|
||||
fn sync_locks() -> &'static ListenerLockCache {
|
||||
static SYNC_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
SYNC_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::http::HeaderMap;
|
||||
use monitor_client::{
|
||||
api::execute::RunBuild,
|
||||
entities::{build::Build, user::github_user},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
helpers::update::init_execution_update, resource, state::State,
|
||||
};
|
||||
|
||||
use super::{extract_branch, verify_gh_signature, ListenerLockCache};
|
||||
|
||||
fn build_locks() -> &'static ListenerLockCache {
|
||||
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_build_webhook(
|
||||
build_id: String,
|
||||
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 = resource::get::<Build>(&build_id).await?;
|
||||
if !build.config.webhook_enabled {
|
||||
return Err(anyhow!("build does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != build.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = ExecuteRequest::RunBuild(RunBuild { build: build_id });
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunBuild(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{extract::Path, http::HeaderMap, routing::post, Router};
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{cache::Cache, random_duration},
|
||||
};
|
||||
|
||||
mod build;
|
||||
mod procedure;
|
||||
mod repo;
|
||||
mod sync;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IdBranch {
|
||||
id: String,
|
||||
branch: String,
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
"/build/:id",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("build_webhook", id);
|
||||
async {
|
||||
let res = build::handle_build_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run build webook for build {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/repo/:id/clone",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_clone_webhook", id);
|
||||
async {
|
||||
let res = repo::handle_repo_clone_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo clone webook for repo {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/repo/:id/pull",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_pull_webhook", id);
|
||||
async {
|
||||
let res = repo::handle_repo_pull_webhook(id.clone(), headers, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo pull webook for repo {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/procedure/:id/:branch",
|
||||
post(
|
||||
|Path(IdBranch { id, branch }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("procedure_webhook", id, branch);
|
||||
async {
|
||||
let res = procedure::handle_procedure_webhook(
|
||||
id.clone(),
|
||||
branch,
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run procedure webook for procedure {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/refresh",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_refresh_webhook", id);
|
||||
async {
|
||||
let res = sync::handle_sync_refresh_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/sync",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_execute_webhook", id);
|
||||
async {
|
||||
let res = sync::handle_sync_execute_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn verify_gh_signature(
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// wait random amount of time
|
||||
tokio::time::sleep(random_duration(0, 500)).await;
|
||||
|
||||
let signature = headers.get("x-hub-signature-256");
|
||||
if signature.is_none() {
|
||||
return Err(anyhow!("no signature in headers"));
|
||||
}
|
||||
let signature = signature.unwrap().to_str();
|
||||
if signature.is_err() {
|
||||
return Err(anyhow!("failed to unwrap signature"));
|
||||
}
|
||||
let signature = signature.unwrap().replace("sha256=", "");
|
||||
let mut mac = HmacSha256::new_from_slice(
|
||||
core_config().github_webhook_secret.as_bytes(),
|
||||
)
|
||||
.expect("github webhook | failed to create hmac sha256");
|
||||
mac.update(body.as_bytes());
|
||||
let expected = mac.finalize().into_bytes().encode_hex::<String>();
|
||||
if signature == expected {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("signature does not equal expected"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GithubWebhookBody {
|
||||
#[serde(rename = "ref")]
|
||||
branch: String,
|
||||
}
|
||||
|
||||
fn extract_branch(body: &str) -> anyhow::Result<String> {
|
||||
let branch = serde_json::from_str::<GithubWebhookBody>(body)
|
||||
.context("failed to parse github request body")?
|
||||
.branch
|
||||
.replace("refs/heads/", "");
|
||||
Ok(branch)
|
||||
}
|
||||
|
||||
type ListenerLockCache = Cache<String, Arc<Mutex<()>>>;
|
||||
@@ -0,0 +1,55 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::http::HeaderMap;
|
||||
use monitor_client::{
|
||||
api::execute::RunProcedure,
|
||||
entities::{procedure::Procedure, user::github_user},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
helpers::update::init_execution_update, resource, state::State,
|
||||
};
|
||||
|
||||
use super::{extract_branch, verify_gh_signature, ListenerLockCache};
|
||||
|
||||
fn procedure_locks() -> &'static ListenerLockCache {
|
||||
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_procedure_webhook(
|
||||
procedure_id: String,
|
||||
target_branch: String,
|
||||
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 {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let procedure = resource::get::<Procedure>(&procedure_id).await?;
|
||||
if !procedure.config.webhook_enabled {
|
||||
return Err(anyhow!("procedure does not have webhook enabled"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = ExecuteRequest::RunProcedure(RunProcedure {
|
||||
procedure: procedure_id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunProcedure(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::http::HeaderMap;
|
||||
use monitor_client::{
|
||||
api::execute::{CloneRepo, PullRepo},
|
||||
entities::{repo::Repo, user::github_user},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::update::init_execution_update, resource, state::State,
|
||||
};
|
||||
|
||||
use super::{extract_branch, verify_gh_signature, ListenerLockCache};
|
||||
|
||||
fn repo_locks() -> &'static ListenerLockCache {
|
||||
static REPO_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
REPO_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_repo_clone_webhook(
|
||||
repo_id: String,
|
||||
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 = resource::get::<Repo>(&repo_id).await?;
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != repo.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req =
|
||||
crate::api::execute::ExecuteRequest::CloneRepo(CloneRepo {
|
||||
repo: repo_id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::CloneRepo(req) = req
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_repo_pull_webhook(
|
||||
repo_id: String,
|
||||
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 = resource::get::<Repo>(&repo_id).await?;
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != repo.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::PullRepo(PullRepo {
|
||||
repo: repo_id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::PullRepo(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::http::HeaderMap;
|
||||
use monitor_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
entities::{sync::ResourceSync, user::github_user},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
helpers::update::init_execution_update, resource, state::State,
|
||||
};
|
||||
|
||||
use super::{extract_branch, verify_gh_signature, ListenerLockCache};
|
||||
|
||||
fn sync_locks() -> &'static ListenerLockCache {
|
||||
static SYNC_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
SYNC_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_sync_refresh_webhook(
|
||||
sync_id: String,
|
||||
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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
State
|
||||
.resolve(RefreshResourceSyncPending { sync: sync_id }, user)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_sync_execute_webhook(
|
||||
sync_id: String,
|
||||
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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req = ExecuteRequest::RunSync(RunSync { sync: sync_id });
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunSync(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,7 +5,6 @@ use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::Router;
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
services::{ServeDir, ServeFile},
|
||||
@@ -35,6 +34,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
// Spawn monitoring loops
|
||||
monitor::spawn_monitor_loop()?;
|
||||
helpers::prune::spawn_prune_loop();
|
||||
helpers::sync::spawn_sync_refresh_loop();
|
||||
resource::spawn_build_state_refresh_loop();
|
||||
resource::spawn_repo_state_refresh_loop();
|
||||
resource::spawn_procedure_state_refresh_loop();
|
||||
@@ -76,13 +76,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -74,7 +74,7 @@ pub async fn alert_deployments(
|
||||
return;
|
||||
}
|
||||
send_alerts(&alerts).await;
|
||||
let res = db_client().await.alerts.insert_many(alerts, None).await;
|
||||
let res = db_client().await.alerts.insert_many(alerts).await;
|
||||
if let Err(e) = res {
|
||||
error!("failed to record deployment status alerts to db | {e:#}");
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ pub async fn alert_servers(
|
||||
|
||||
let mut alerts_to_open = Vec::<(Alert, SendAlerts)>::new();
|
||||
let mut alerts_to_update = Vec::<(Alert, SendAlerts)>::new();
|
||||
let mut alert_ids_to_close = Vec::<(String, SendAlerts)>::new();
|
||||
let mut alert_ids_to_close = Vec::<(Alert, SendAlerts)>::new();
|
||||
|
||||
for server_status in server_statuses {
|
||||
let Some(server) = servers.remove(&server_status.id) else {
|
||||
@@ -101,13 +101,10 @@ pub async fn alert_servers(
|
||||
}
|
||||
|
||||
// Close an open alert
|
||||
(
|
||||
ServerState::Ok | ServerState::Disabled,
|
||||
Some(health_alert),
|
||||
) => alert_ids_to_close.push((
|
||||
health_alert.id.clone(),
|
||||
server.info.send_unreachable_alerts,
|
||||
)),
|
||||
(ServerState::Ok | ServerState::Disabled, Some(alert)) => {
|
||||
alert_ids_to_close
|
||||
.push((alert.clone(), server.info.send_unreachable_alerts));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -165,8 +162,20 @@ pub async fn alert_servers(
|
||||
alerts_to_update.push((alert, server.info.send_cpu_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => alert_ids_to_close
|
||||
.push((alert.id.clone(), server.info.send_cpu_alerts)),
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
percentage: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
.map(|s| s.cpu_perc as f64)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close.push((alert, server.info.send_cpu_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -185,7 +194,7 @@ pub async fn alert_servers(
|
||||
ts,
|
||||
resolved: false,
|
||||
resolved_ts: None,
|
||||
level: health.cpu,
|
||||
level: health.mem,
|
||||
target: ResourceTarget::Server(server_status.id.clone()),
|
||||
data: AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
@@ -229,8 +238,25 @@ pub async fn alert_servers(
|
||||
alerts_to_update.push((alert, server.info.send_mem_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => alert_ids_to_close
|
||||
.push((alert.id.clone(), server.info.send_mem_alerts)),
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
total_gb: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
.map(|s| s.mem_total_gb)
|
||||
.unwrap_or(0.0),
|
||||
used_gb: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
.map(|s| s.mem_used_gb)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close.push((alert, server.info.send_mem_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -291,8 +317,23 @@ pub async fn alert_servers(
|
||||
.push((alert, server.info.send_disk_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => alert_ids_to_close
|
||||
.push((alert.id.clone(), server.info.send_disk_alerts)),
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
let mut alert = alert.clone();
|
||||
let disk = server_status.stats.as_ref().and_then(|stats| {
|
||||
stats.disks.iter().find(|disk| disk.mount == *path)
|
||||
});
|
||||
alert.level = *health;
|
||||
alert.data = AlertData::ServerDisk {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
path: path.to_owned(),
|
||||
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
|
||||
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
|
||||
};
|
||||
alert_ids_to_close
|
||||
.push((alert, server.info.send_disk_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -316,7 +357,7 @@ async fn open_alerts(alerts: &[(Alert, SendAlerts)]) {
|
||||
let open = || async {
|
||||
let ids = db
|
||||
.alerts
|
||||
.insert_many(alerts.iter().map(|(alert, _)| alert), None)
|
||||
.insert_many(alerts.iter().map(|(alert, _)| alert))
|
||||
.await?
|
||||
.inserted_ids
|
||||
.into_iter()
|
||||
@@ -402,22 +443,20 @@ async fn update_alerts(alerts: &[(Alert, SendAlerts)]) {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
async fn resolve_alerts(alert_ids: &[(String, SendAlerts)]) {
|
||||
if alert_ids.is_empty() {
|
||||
async fn resolve_alerts(alerts: &[(Alert, SendAlerts)]) {
|
||||
if alerts.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let send_alerts_map =
|
||||
alert_ids.iter().cloned().collect::<HashMap<_, _>>();
|
||||
|
||||
let close = || async {
|
||||
let alert_ids = alert_ids
|
||||
let close = || async move {
|
||||
let alert_ids = alerts
|
||||
.iter()
|
||||
.map(|(id, _)| {
|
||||
ObjectId::from_str(id)
|
||||
.map(|(alert, _)| {
|
||||
ObjectId::from_str(&alert.id)
|
||||
.context("failed to convert alert id to ObjectId")
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
|
||||
db_client()
|
||||
.await
|
||||
.alerts
|
||||
@@ -429,31 +468,25 @@ async fn resolve_alerts(alert_ids: &[(String, SendAlerts)]) {
|
||||
"resolved_ts": monitor_timestamp()
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to resolve alerts on db")?;
|
||||
let mut closed = find_collect(
|
||||
&db_client().await.alerts,
|
||||
doc! { "_id": { "$in": &alert_ids } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to get closed alerts from db")?;
|
||||
.context("failed to resolve alerts on db")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
.ok();
|
||||
|
||||
for closed in &mut closed {
|
||||
closed.level = SeverityLevel::Ok;
|
||||
}
|
||||
let ts = monitor_timestamp();
|
||||
|
||||
let closed = closed
|
||||
.into_iter()
|
||||
.filter(|closed| {
|
||||
if let ResourceTarget::Server(id) = &closed.target {
|
||||
send_alerts_map.get(id).cloned().unwrap_or(true)
|
||||
} else {
|
||||
error!("got resource target other than server in resolve_server_alerts");
|
||||
true
|
||||
}
|
||||
let closed = alerts
|
||||
.iter()
|
||||
.filter(|(_, send)| *send)
|
||||
.map(|(alert, _)| {
|
||||
let mut alert = alert.clone();
|
||||
|
||||
alert.resolved = true;
|
||||
alert.resolved_ts = Some(ts);
|
||||
alert.level = SeverityLevel::Ok;
|
||||
|
||||
alert
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ pub async fn insert_repos_status_unknown(repos: Vec<Repo>) {
|
||||
.insert(
|
||||
repo.id.clone(),
|
||||
CachedRepoStatus {
|
||||
id: repo.id,
|
||||
latest_hash: None,
|
||||
latest_message: None,
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ pub struct CachedDeploymentStatus {
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedRepoStatus {
|
||||
pub id: String,
|
||||
pub latest_hash: Option<String>,
|
||||
pub latest_message: Option<String>,
|
||||
}
|
||||
@@ -229,9 +228,8 @@ pub async fn update_cache_for_server(server: &Server) {
|
||||
.unzip();
|
||||
status_cache
|
||||
.insert(
|
||||
repo.id.clone(),
|
||||
repo.id,
|
||||
CachedRepoStatus {
|
||||
id: repo.id,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ pub async fn record_server_stats(ts: i64) {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !records.is_empty() {
|
||||
let res =
|
||||
db_client().await.stats.insert_many(records, None).await;
|
||||
let res = db_client().await.stats.insert_many(records).await;
|
||||
if let Err(e) = res {
|
||||
error!("failed to record server stats | {e:#}");
|
||||
}
|
||||
|
||||
@@ -137,7 +137,6 @@ impl super::MonitorResource for Builder {
|
||||
mungos::update::Update::Set(
|
||||
doc! { "config.builder.params.builder_id": "" },
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update_many builds on database")?;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::entities::{
|
||||
build::Build,
|
||||
deployment::{
|
||||
@@ -16,7 +17,6 @@ use monitor_client::entities::{
|
||||
};
|
||||
use mungos::mongodb::Collection;
|
||||
use periphery_client::api::container::RemoveContainer;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
@@ -187,10 +187,12 @@ impl super::MonitorResource for Deployment {
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to retrieve server at {} from db.\n\nerror: {}",
|
||||
deployment.config.server_id,
|
||||
serialize_error_pretty(&e)
|
||||
format_serror(
|
||||
&e.context(format!(
|
||||
"failed to retrieve server at {} from db.",
|
||||
deployment.config.server_id
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
@@ -211,9 +213,8 @@ impl super::MonitorResource for Deployment {
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container on periphery.\n\nerror: {}",
|
||||
serialize_error_pretty(&e),
|
||||
format_serror(
|
||||
&e.context("failed to get periphery client").into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
@@ -230,9 +231,8 @@ impl super::MonitorResource for Deployment {
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container.\n\nerror: {}",
|
||||
serialize_error_pretty(&e)
|
||||
format_serror(
|
||||
&e.context("failed to remove container").into(),
|
||||
),
|
||||
),
|
||||
};
|
||||
@@ -270,7 +270,7 @@ async fn validate_config(
|
||||
.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(),
|
||||
version: *version,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::format_serror;
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::write::CreateTag,
|
||||
@@ -26,12 +27,11 @@ use mungos::{
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff};
|
||||
use resolver_api::Resolve;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
create_permission,
|
||||
create_permission, flatten_document,
|
||||
query::{
|
||||
get_resource_ids_for_non_admin, get_tag,
|
||||
get_user_permission_on_resource, id_or_name_filter,
|
||||
@@ -78,7 +78,10 @@ pub trait MonitorResource {
|
||||
+ From<Self::PartialConfig>
|
||||
+ PartialDiff<Self::PartialConfig, Self::ConfigDiff>
|
||||
+ 'static;
|
||||
type PartialConfig: From<Self::Config> + Serialize + MaybeNone;
|
||||
type PartialConfig: Default
|
||||
+ From<Self::Config>
|
||||
+ Serialize
|
||||
+ MaybeNone;
|
||||
type ConfigDiff: Into<Self::PartialConfig>
|
||||
+ Serialize
|
||||
+ Diff
|
||||
@@ -184,7 +187,7 @@ pub async fn get<T: MonitorResource>(
|
||||
) -> anyhow::Result<Resource<T::Config, T::Info>> {
|
||||
T::coll()
|
||||
.await
|
||||
.find_one(id_or_name_filter(id_or_name), None)
|
||||
.find_one(id_or_name_filter(id_or_name))
|
||||
.await
|
||||
.context("failed to query db for resource")?
|
||||
.with_context(|| {
|
||||
@@ -333,7 +336,7 @@ pub async fn create<T: MonitorResource>(
|
||||
|
||||
let resource_id = T::coll()
|
||||
.await
|
||||
.insert_one(&resource, None)
|
||||
.insert_one(&resource)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to add {} to db", T::resource_type())
|
||||
@@ -407,7 +410,7 @@ pub async fn update<T: MonitorResource>(
|
||||
|
||||
for FieldDiff { field, from, to } in diff.iter_field_diffs() {
|
||||
diff_log.push_str(&format!(
|
||||
"\n\n<span class=\"text-muted-foreground\">field</span>: '{field}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-500\">{from}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-500\">{to}</span>",
|
||||
"\n\n<span class=\"text-muted-foreground\">field</span>: '{field}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-700 dark:text-red-400\">{from}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-700 dark:text-green-400\">{to}</span>",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -419,10 +422,12 @@ pub async fn update<T: MonitorResource>(
|
||||
let config_doc = T::update_document(resource, config)
|
||||
.context("failed to serialize config to bson document")?;
|
||||
|
||||
let update_doc = flatten_document(doc! { "config": config_doc });
|
||||
|
||||
update_one_by_id(
|
||||
T::coll().await,
|
||||
&id,
|
||||
mungos::update::Update::FlattenSet(doc! { "config": config_doc }),
|
||||
doc! { "$set": update_doc },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
@@ -484,7 +489,6 @@ pub async fn update_description<T: MonitorResource>(
|
||||
.update_one(
|
||||
id_or_name_filter(id_or_name),
|
||||
doc! { "$set": { "description": description } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -519,7 +523,6 @@ pub async fn update_tags<T: MonitorResource>(
|
||||
.update_one(
|
||||
id_or_name_filter(id_or_name),
|
||||
doc! { "$set": { "tags": tags } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -530,7 +533,7 @@ pub async fn remove_tag_from_all<T: MonitorResource>(
|
||||
) -> anyhow::Result<()> {
|
||||
T::coll()
|
||||
.await
|
||||
.update_many(doc! {}, doc! { "$pull": { "tags": tag_id } }, None)
|
||||
.update_many(doc! {}, doc! { "$pull": { "tags": tag_id } })
|
||||
.await
|
||||
.context("failed to remove tag from resources")?;
|
||||
Ok(())
|
||||
@@ -577,7 +580,7 @@ pub async fn delete<T: MonitorResource>(
|
||||
);
|
||||
|
||||
if let Err(e) = T::post_delete(&resource, &mut update).await {
|
||||
update.push_error_log("post delete", serialize_error_pretty(&e));
|
||||
update.push_error_log("post delete", format_serror(&e.into()));
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
@@ -614,7 +617,6 @@ where
|
||||
"resource_target.type": variant.as_ref(),
|
||||
"resource_target.id": &id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -647,7 +649,6 @@ where
|
||||
recent_field: id
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to remove resource from users recently viewed")
|
||||
|
||||
@@ -337,12 +337,12 @@ async fn get_procedure_state_from_db(id: &str) -> ProcedureState {
|
||||
let state = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "Procedure",
|
||||
"target.id": id,
|
||||
"operation": "RunProcedure"
|
||||
},
|
||||
.find_one(doc! {
|
||||
"target.type": "Procedure",
|
||||
"target.id": id,
|
||||
"operation": "RunProcedure"
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::entities::{
|
||||
permission::PermissionLevel,
|
||||
repo::{
|
||||
@@ -18,7 +19,6 @@ use mungos::{
|
||||
mongodb::{bson::doc, options::FindOneOptions, Collection},
|
||||
};
|
||||
use periphery_client::api::git::DeleteRepo;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client,
|
||||
@@ -158,7 +158,7 @@ impl super::MonitorResource for Repo {
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"delete repo on periphery",
|
||||
serialize_error_pretty(&e),
|
||||
format_serror(&e.context("failed to delete repo").into()),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -244,15 +244,15 @@ async fn get_repo_state_from_db(id: &str) -> RepoState {
|
||||
let state = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "Repo",
|
||||
"target.id": id,
|
||||
"$or": [
|
||||
{ "operation": "CloneRepo" },
|
||||
{ "operation": "PullRepo" },
|
||||
],
|
||||
},
|
||||
.find_one(doc! {
|
||||
"target.type": "Repo",
|
||||
"target.id": id,
|
||||
"$or": [
|
||||
{ "operation": "CloneRepo" },
|
||||
{ "operation": "PullRepo" },
|
||||
],
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
|
||||
@@ -130,7 +130,6 @@ impl super::MonitorResource for Server {
|
||||
.update_many(
|
||||
doc! { "config.params.server_id": &id },
|
||||
doc! { "$set": { "config.params.server_id": "" } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from builders")?;
|
||||
@@ -139,7 +138,6 @@ impl super::MonitorResource for Server {
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from deployments")?;
|
||||
@@ -148,7 +146,6 @@ impl super::MonitorResource for Server {
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from repos")?;
|
||||
@@ -160,7 +157,6 @@ impl super::MonitorResource for Server {
|
||||
"resolved": true,
|
||||
"resolved_ts": monitor_timestamp()
|
||||
} },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from repos")?;
|
||||
|
||||
@@ -213,12 +213,12 @@ async fn get_resource_sync_state_from_db(
|
||||
let state = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": id,
|
||||
"operation": "RunSync"
|
||||
},
|
||||
.find_one(doc! {
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": id,
|
||||
"operation": "RunSync"
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
|
||||
@@ -13,7 +13,6 @@ repository.workspace = true
|
||||
monitor_client.workspace = true
|
||||
logger.workspace = true
|
||||
#
|
||||
termination_signal.workspace = true
|
||||
mungos.workspace = true
|
||||
#
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#[allow(unused)]
|
||||
pub mod v0;
|
||||
pub mod v1_6;
|
||||
|
||||
@@ -198,11 +198,6 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
// permissions: value
|
||||
// .permissions
|
||||
// .into_iter()
|
||||
// .map(|(id, p)| (id, p.into()))
|
||||
// .collect(),
|
||||
updated_at: unix_from_monitor_ts(&value.updated_at)?,
|
||||
tags: Vec::new(),
|
||||
info: BuildInfo {
|
||||
@@ -233,6 +228,7 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
build_args,
|
||||
secret_args: Default::default(),
|
||||
extra_args,
|
||||
use_buildx,
|
||||
labels: Default::default(),
|
||||
|
||||
@@ -134,6 +134,7 @@ impl From<BuildConfig>
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
secret_args: Default::default(),
|
||||
labels: value.labels.into_iter().map(Into::into).collect(),
|
||||
extra_args: value.extra_args,
|
||||
use_buildx: value.use_buildx,
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
use mungos::{init::MongoBuilder, mongodb::Collection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod build;
|
||||
pub mod deployment;
|
||||
pub mod resource;
|
||||
|
||||
pub struct DbClient {
|
||||
pub deployments: Collection<deployment::Deployment>,
|
||||
pub builds: Collection<build::Build>,
|
||||
}
|
||||
|
||||
impl DbClient {
|
||||
pub async fn new(
|
||||
legacy_uri: &str,
|
||||
legacy_db_name: &str,
|
||||
) -> DbClient {
|
||||
let client = MongoBuilder::default()
|
||||
.uri(legacy_uri)
|
||||
.build()
|
||||
.await
|
||||
.expect("failed to init legacy mongo client");
|
||||
let db = client.database(legacy_db_name);
|
||||
DbClient {
|
||||
deployments: db.collection("Deployment"),
|
||||
builds: db.collection("Build"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
)]
|
||||
|
||||
@@ -11,8 +11,15 @@ use serde::Deserialize;
|
||||
mod legacy;
|
||||
mod migrate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum AppMode {
|
||||
V0,
|
||||
V1_6,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
app_mode: AppMode,
|
||||
legacy_uri: String,
|
||||
legacy_db_name: String,
|
||||
target_uri: String,
|
||||
@@ -28,13 +35,26 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let env: Env = envy::from_env()?;
|
||||
|
||||
let legacy_db =
|
||||
legacy::v0::DbClient::new(&env.legacy_uri, &env.legacy_db_name)
|
||||
match env.app_mode {
|
||||
AppMode::V0 => {
|
||||
let legacy_db = legacy::v0::DbClient::new(
|
||||
&env.legacy_uri,
|
||||
&env.legacy_db_name,
|
||||
)
|
||||
.await;
|
||||
let target_db =
|
||||
DbClient::new(&env.target_uri, &env.target_db_name).await?;
|
||||
|
||||
migrate::migrate_all(&legacy_db, &target_db).await?;
|
||||
let target_db =
|
||||
DbClient::new(&env.target_uri, &env.target_db_name).await?;
|
||||
migrate::v0::migrate_all(&legacy_db, &target_db).await?
|
||||
}
|
||||
AppMode::V1_6 => {
|
||||
let db = legacy::v1_6::DbClient::new(
|
||||
&env.target_uri,
|
||||
&env.target_db_name,
|
||||
)
|
||||
.await;
|
||||
migrate::v1_6::migrate_all_in_place(&db).await?
|
||||
}
|
||||
}
|
||||
|
||||
info!("finished!");
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod v0;
|
||||
pub mod v1_6;
|
||||
@@ -46,7 +46,7 @@ pub async fn migrate_users(
|
||||
|
||||
target_db
|
||||
.users
|
||||
.insert_many(users, None)
|
||||
.insert_many(users)
|
||||
.await
|
||||
.context("failed to insert users on target")?;
|
||||
|
||||
@@ -91,7 +91,7 @@ pub async fn migrate_servers(
|
||||
if !new_servers.is_empty() {
|
||||
target_db
|
||||
.servers
|
||||
.insert_many(new_servers, None)
|
||||
.insert_many(new_servers)
|
||||
.await
|
||||
.context("failed to insert servers on target")?;
|
||||
}
|
||||
@@ -99,7 +99,7 @@ pub async fn migrate_servers(
|
||||
if !permissions.is_empty() {
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions, None)
|
||||
.insert_many(permissions)
|
||||
.await
|
||||
.context("failed to insert server permissions on target")?;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ pub async fn migrate_deployments(
|
||||
if !new_deployments.is_empty() {
|
||||
target_db
|
||||
.deployments
|
||||
.insert_many(new_deployments, None)
|
||||
.insert_many(new_deployments)
|
||||
.await
|
||||
.context("failed to insert deployments on target")?;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ pub async fn migrate_deployments(
|
||||
if !permissions.is_empty() {
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions, None)
|
||||
.insert_many(permissions)
|
||||
.await
|
||||
.context("failed to insert deployment permissions on target")?;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ pub async fn migrate_builds(
|
||||
if !new_builds.is_empty() {
|
||||
target_db
|
||||
.builds
|
||||
.insert_many(new_builds, None)
|
||||
.insert_many(new_builds)
|
||||
.await
|
||||
.context("failed to insert builds on target")?;
|
||||
}
|
||||
@@ -209,7 +209,7 @@ pub async fn migrate_builds(
|
||||
if !permissions.is_empty() {
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions, None)
|
||||
.insert_many(permissions)
|
||||
.await
|
||||
.context("failed to insert build permissions on target")?;
|
||||
}
|
||||
@@ -239,7 +239,7 @@ pub async fn migrate_updates(
|
||||
|
||||
target_db
|
||||
.updates
|
||||
.insert_many(updates, None)
|
||||
.insert_many(updates)
|
||||
.await
|
||||
.context("failed to insert updates on target")?;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use anyhow::Context;
|
||||
use monitor_client::entities::{
|
||||
build::Build, deployment::Deployment,
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::bson::{doc, to_document},
|
||||
};
|
||||
|
||||
use crate::legacy::v1_6;
|
||||
|
||||
pub async fn migrate_all_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_deployments_in_place(db).await?;
|
||||
migrate_builds_in_place(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_deployments_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let deployments = find_collect(&db.deployments, None, None)
|
||||
.await
|
||||
.context("failed to get deployments")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<Deployment>>();
|
||||
|
||||
info!("migrating {} deployments...", deployments.len());
|
||||
|
||||
for deployment in deployments {
|
||||
db.deployments
|
||||
.update_one(
|
||||
doc! { "name": &deployment.name },
|
||||
doc! { "$set": to_document(&deployment)? },
|
||||
)
|
||||
.await
|
||||
.context("failed to insert deployments on target")?;
|
||||
}
|
||||
|
||||
info!("deployments have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_builds_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let builds = find_collect(&db.builds, None, None)
|
||||
.await
|
||||
.context("failed to get builds")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<Build>>();
|
||||
|
||||
info!("migrating {} builds...", builds.len());
|
||||
|
||||
for build in builds {
|
||||
db.builds
|
||||
.update_one(
|
||||
doc! { "name": &build.name },
|
||||
doc! { "$set": to_document(&build)? },
|
||||
)
|
||||
.await
|
||||
.context("failed to insert builds on target")?;
|
||||
}
|
||||
|
||||
info!("builds have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+14
-14
@@ -17,29 +17,29 @@ path = "src/main.rs"
|
||||
# local
|
||||
monitor_client = { workspace = true, features = ["docker"] }
|
||||
periphery_client.workspace = true
|
||||
formatting.workspace = true
|
||||
command.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
async_timing_util.workspace = true
|
||||
merge_config_files.workspace = true
|
||||
parse_csl.workspace = true
|
||||
termination_signal.workspace = true
|
||||
run_command.workspace = true
|
||||
svi.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
resolver_api.workspace = true
|
||||
run_command.workspace = true
|
||||
parse_csl.workspace = true
|
||||
svi.workspace = true
|
||||
# external
|
||||
tokio.workspace = true
|
||||
axum.workspace = true
|
||||
axum-extra.workspace = true
|
||||
dotenv.workspace = true
|
||||
envy.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
bollard.workspace = true
|
||||
clap.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
bollard.workspace = true
|
||||
sysinfo.workspace = true
|
||||
dotenv.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
axum.workspace = true
|
||||
envy.workspace = true
|
||||
clap.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
+182
-12
@@ -1,31 +1,200 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::run_monitor_command;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::entities::{
|
||||
server::docker_image::ImageSummary, update::Log,
|
||||
};
|
||||
use periphery_client::api::build::{
|
||||
Build, GetImageList, PruneImages,
|
||||
build::{Build, BuildConfig},
|
||||
get_image_name, optional_string,
|
||||
server::docker_image::ImageSummary,
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
use periphery_client::api::build::{self, GetImageList, PruneImages};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
docker::{self, client::docker_client},
|
||||
config::periphery_config,
|
||||
docker::docker_client,
|
||||
helpers::{docker_login, parse_extra_args, parse_labels},
|
||||
State,
|
||||
};
|
||||
|
||||
impl Resolve<Build> for State {
|
||||
#[instrument(name = "Build", skip(self, replacers))]
|
||||
impl Resolve<build::Build> for State {
|
||||
#[instrument(name = "Build", skip_all)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
Build {
|
||||
build::Build {
|
||||
build,
|
||||
aws_ecr,
|
||||
registry_token,
|
||||
replacers,
|
||||
}: Build,
|
||||
replacers: core_replacers,
|
||||
}: build::Build,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
docker::build::build(&build, registry_token, replacers).await
|
||||
let Build {
|
||||
name,
|
||||
config:
|
||||
BuildConfig {
|
||||
version,
|
||||
skip_secret_interp,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
build_args,
|
||||
secret_args,
|
||||
labels,
|
||||
extra_args,
|
||||
use_buildx,
|
||||
image_registry,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = &build;
|
||||
|
||||
let mut logs = Vec::new();
|
||||
|
||||
// Maybe docker login
|
||||
let should_push = match docker_login(
|
||||
image_registry,
|
||||
registry_token.as_deref(),
|
||||
aws_ecr.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(should_push) => should_push,
|
||||
Err(e) => {
|
||||
logs.push(Log::error(
|
||||
"docker login",
|
||||
format_serror(
|
||||
&e.context("failed to login to docker registry").into(),
|
||||
),
|
||||
));
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
|
||||
let name = to_monitor_name(name);
|
||||
|
||||
// Get paths
|
||||
let build_dir =
|
||||
periphery_config().repo_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = match optional_string(dockerfile_path) {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
|
||||
// Get command parts
|
||||
let image_name = get_image_name(&build, |_| aws_ecr)
|
||||
.context("failed to make image name")?;
|
||||
let build_args = parse_build_args(build_args);
|
||||
let _secret_args =
|
||||
parse_secret_args(secret_args, *skip_secret_interp)?;
|
||||
let labels = parse_labels(labels);
|
||||
let extra_args = parse_extra_args(extra_args);
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
let image_tags = image_tags(&image_name, version);
|
||||
let push_command = should_push
|
||||
.then(|| {
|
||||
format!(" && docker image push --all-tags {image_name}")
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"cd {} && docker{buildx} build{build_args}{_secret_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
|
||||
build_dir.display()
|
||||
);
|
||||
|
||||
if *skip_secret_interp {
|
||||
let build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
info!("finished building docker image");
|
||||
logs.push(build_log);
|
||||
} else {
|
||||
// Interpolate any missing secrets
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate secrets into docker build command",
|
||||
)?;
|
||||
replacers.extend(core_replacers);
|
||||
|
||||
let mut build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
build_log.command =
|
||||
svi::replace_in_string(&build_log.command, &replacers);
|
||||
build_log.stdout =
|
||||
svi::replace_in_string(&build_log.stdout, &replacers);
|
||||
build_log.stderr =
|
||||
svi::replace_in_string(&build_log.stderr, &replacers);
|
||||
logs.push(build_log);
|
||||
}
|
||||
|
||||
cleanup_secret_env_vars(secret_args);
|
||||
|
||||
Ok(logs)
|
||||
}
|
||||
}
|
||||
|
||||
fn image_tags(image_name: &str, version: &Version) -> String {
|
||||
let Version { major, minor, .. } = version;
|
||||
format!(
|
||||
" -t {image_name}:latest -t {image_name}:{version} -t {image_name}:{major}.{minor} -t {image_name}:{major}",
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
|
||||
build_args
|
||||
.iter()
|
||||
.map(|p| format!(" --build-arg {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn parse_secret_args(
|
||||
secret_args: &[EnvironmentVar],
|
||||
skip_secret_interp: bool,
|
||||
) -> anyhow::Result<String> {
|
||||
let periphery_config = periphery_config();
|
||||
Ok(
|
||||
secret_args
|
||||
.iter()
|
||||
.map(|EnvironmentVar { variable, value }| {
|
||||
if variable.is_empty() {
|
||||
return Err(anyhow!("secret variable cannot be empty string"))
|
||||
} else if variable.contains('=') {
|
||||
return Err(anyhow!("invalid variable {variable}. variable cannot contain '='"))
|
||||
}
|
||||
let value = if skip_secret_interp {
|
||||
value.to_string()
|
||||
} else {
|
||||
svi::interpolate_variables(
|
||||
value,
|
||||
&periphery_config.secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate periphery secrets into build secrets",
|
||||
)?.0
|
||||
};
|
||||
std::env::set_var(variable, value);
|
||||
anyhow::Ok(format!(" --secret id={variable}"))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?
|
||||
.join(""),
|
||||
)
|
||||
}
|
||||
|
||||
fn cleanup_secret_env_vars(secret_args: &[EnvironmentVar]) {
|
||||
secret_args.iter().for_each(
|
||||
|EnvironmentVar { variable, .. }| std::env::remove_var(variable),
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<GetImageList> for State {
|
||||
@@ -48,6 +217,7 @@ impl Resolve<PruneImages> for State {
|
||||
_: PruneImages,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::build::prune_images().await)
|
||||
let command = String::from("docker image prune -a -f");
|
||||
Ok(run_monitor_command("prune images", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::run_monitor_command;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::entities::{
|
||||
deployment::{ContainerSummary, DockerContainerStats},
|
||||
deployment::{
|
||||
ContainerSummary, Conversion, Deployment, DeploymentConfig,
|
||||
DeploymentImage, DockerContainerStats, RestartMode,
|
||||
TerminationSignal,
|
||||
},
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, SearchCombinator,
|
||||
};
|
||||
use periphery_client::api::container::*;
|
||||
use resolver_api::Resolve;
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::{
|
||||
docker::{self, client::docker_client},
|
||||
config::periphery_config,
|
||||
docker::docker_client,
|
||||
helpers::{docker_login, parse_extra_args, parse_labels},
|
||||
State,
|
||||
};
|
||||
|
||||
@@ -34,10 +45,11 @@ impl Resolve<GetContainerLog> for State {
|
||||
#[instrument(name = "GetContainerLog", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: GetContainerLog,
|
||||
GetContainerLog { name, tail }: GetContainerLog,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::container::container_log(&req.name, req.tail).await)
|
||||
let command = format!("docker logs {name} --tail {tail}");
|
||||
Ok(run_monitor_command("get container log", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,17 +63,29 @@ impl Resolve<GetContainerLogSearch> for State {
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: GetContainerLogSearch,
|
||||
GetContainerLogSearch {
|
||||
name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
}: GetContainerLogSearch,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
docker::container::container_log_search(
|
||||
&req.name,
|
||||
&req.terms,
|
||||
req.combinator,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
let maybe_invert = invert.then_some(" -v").unwrap_or_default();
|
||||
let grep = match combinator {
|
||||
SearchCombinator::Or => {
|
||||
format!("grep{maybe_invert} -E '{}'", terms.join("|"))
|
||||
}
|
||||
SearchCombinator::And => {
|
||||
format!(
|
||||
"grep{maybe_invert} -P '^(?=.*{})'",
|
||||
terms.join(")(?=.*")
|
||||
)
|
||||
}
|
||||
};
|
||||
let command =
|
||||
format!("docker logs {name} --tail 5000 2>&1 | {grep}");
|
||||
Ok(run_monitor_command("get container log grep", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +103,7 @@ impl Resolve<GetContainerStats> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<DockerContainerStats> {
|
||||
let error = anyhow!("no stats matching {}", req.name);
|
||||
let mut stats =
|
||||
docker::container::container_stats(Some(req.name)).await?;
|
||||
let mut stats = container_stats(Some(req.name)).await?;
|
||||
let stats = stats.pop().ok_or(error)?;
|
||||
Ok(stats)
|
||||
}
|
||||
@@ -99,7 +122,7 @@ impl Resolve<GetContainerStatsList> for State {
|
||||
_: GetContainerStatsList,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<DockerContainerStats>> {
|
||||
docker::container::container_stats(None).await
|
||||
container_stats(None).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +132,16 @@ impl Resolve<StartContainer> for State {
|
||||
#[instrument(name = "StartContainer", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: StartContainer,
|
||||
StartContainer { name }: StartContainer,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::container::start_container(&req.name).await)
|
||||
Ok(
|
||||
run_monitor_command(
|
||||
"docker start",
|
||||
format!("docker start {name}"),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,15 +151,26 @@ impl Resolve<StopContainer> for State {
|
||||
#[instrument(name = "StopContainer", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: StopContainer,
|
||||
StopContainer { name, signal, time }: StopContainer,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
docker::container::stop_container(
|
||||
&req.name, req.signal, req.time,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
let command = stop_container_command(&name, signal, time);
|
||||
let log = run_monitor_command("docker stop", command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(&name, None, time);
|
||||
let mut log = run_monitor_command("docker stop", command).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
format!("\n\n{}", log.stderr)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
Ok(log)
|
||||
} else {
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,15 +180,31 @@ impl Resolve<RemoveContainer> for State {
|
||||
#[instrument(name = "RemoveContainer", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: RemoveContainer,
|
||||
RemoveContainer { name, signal, time }: RemoveContainer,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
docker::container::stop_and_remove_container(
|
||||
&req.name, req.signal, req.time,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
let stop_command = stop_container_command(&name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let log =
|
||||
run_monitor_command("docker stop and remove", command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command = stop_container_command(&name, None, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let mut log = run_monitor_command("docker stop", command).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
format!("\n\n{}", log.stderr)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
Ok(log)
|
||||
} else {
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,16 +214,15 @@ impl Resolve<RenameContainer> for State {
|
||||
#[instrument(name = "RenameContainer", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: RenameContainer,
|
||||
RenameContainer {
|
||||
curr_name,
|
||||
new_name,
|
||||
}: RenameContainer,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
docker::container::rename_container(
|
||||
&req.curr_name,
|
||||
&req.new_name,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
let new = to_monitor_name(&new_name);
|
||||
let command = format!("docker rename {curr_name} {new}");
|
||||
Ok(run_monitor_command("docker rename", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,14 +235,18 @@ impl Resolve<PruneContainers> for State {
|
||||
_: PruneContainers,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::container::prune_containers().await)
|
||||
let command = String::from("docker container prune -f");
|
||||
Ok(run_monitor_command("prune containers", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<Deploy> for State {
|
||||
#[instrument(name = "Deploy", skip(self, replacers))]
|
||||
#[instrument(
|
||||
name = "Deploy",
|
||||
skip(self, core_replacers, aws_ecr, registry_token)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
Deploy {
|
||||
@@ -195,22 +254,206 @@ impl Resolve<Deploy> for State {
|
||||
stop_signal,
|
||||
stop_time,
|
||||
registry_token,
|
||||
replacers,
|
||||
replacers: core_replacers,
|
||||
aws_ecr,
|
||||
}: Deploy,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let res = docker::container::deploy(
|
||||
&deployment,
|
||||
stop_signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
.into(),
|
||||
stop_time
|
||||
.unwrap_or(deployment.config.termination_timeout)
|
||||
.into(),
|
||||
registry_token,
|
||||
replacers,
|
||||
if let Err(e) = docker_login(
|
||||
&deployment.config.image_registry,
|
||||
registry_token.as_deref(),
|
||||
aws_ecr.as_ref(),
|
||||
)
|
||||
.await;
|
||||
Ok(res)
|
||||
.await
|
||||
{
|
||||
return Ok(Log::error(
|
||||
"docker login",
|
||||
format_serror(
|
||||
&e.context("failed to login to docker registry").into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let image = if let DeploymentImage::Image { image } =
|
||||
&deployment.config.image
|
||||
{
|
||||
if image.is_empty() {
|
||||
return Ok(Log::error(
|
||||
"get image",
|
||||
String::from("deployment does not have image attached"),
|
||||
));
|
||||
}
|
||||
image
|
||||
} else {
|
||||
return Ok(Log::error(
|
||||
"get image",
|
||||
String::from("deployment does not have image attached"),
|
||||
));
|
||||
};
|
||||
|
||||
let _ = pull_image(image).await;
|
||||
debug!("image pulled");
|
||||
let _ = State
|
||||
.resolve(
|
||||
RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: stop_signal,
|
||||
time: stop_time,
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await;
|
||||
debug!("container stopped and removed");
|
||||
|
||||
let command = docker_run_command(&deployment, image);
|
||||
debug!("docker run command: {command}");
|
||||
|
||||
if deployment.config.skip_secret_interp {
|
||||
Ok(run_monitor_command("docker run", command).await)
|
||||
} else {
|
||||
let command = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate secrets into docker run command",
|
||||
);
|
||||
if let Err(e) = command {
|
||||
return Ok(Log::error("docker run", format!("{e:?}")));
|
||||
}
|
||||
let (command, mut replacers) = command.unwrap();
|
||||
replacers.extend(core_replacers);
|
||||
let mut log = run_monitor_command("docker run", command).await;
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
fn docker_run_command(
|
||||
Deployment {
|
||||
name,
|
||||
config:
|
||||
DeploymentConfig {
|
||||
volumes,
|
||||
ports,
|
||||
network,
|
||||
command,
|
||||
restart,
|
||||
environment,
|
||||
labels,
|
||||
extra_args,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &Deployment,
|
||||
image: &str,
|
||||
) -> String {
|
||||
let name = to_monitor_name(name);
|
||||
let ports = parse_conversions(ports, "-p");
|
||||
let volumes = volumes.to_owned();
|
||||
let volumes = parse_conversions(&volumes, "-v");
|
||||
let network = parse_network(network);
|
||||
let restart = parse_restart(restart);
|
||||
let environment = parse_environment(environment);
|
||||
let labels = parse_labels(labels);
|
||||
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}{command}")
|
||||
}
|
||||
|
||||
fn parse_conversions(
|
||||
conversions: &[Conversion],
|
||||
flag: &str,
|
||||
) -> String {
|
||||
conversions
|
||||
.iter()
|
||||
.map(|p| format!(" {flag} {}:{}", p.local, p.container))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn parse_environment(environment: &[EnvironmentVar]) -> String {
|
||||
environment
|
||||
.iter()
|
||||
.map(|p| format!(" --env {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn parse_network(network: &str) -> String {
|
||||
format!(" --network {network}")
|
||||
}
|
||||
|
||||
fn parse_restart(restart: &RestartMode) -> String {
|
||||
let restart = match restart {
|
||||
RestartMode::OnFailure => "on-failure:10".to_string(),
|
||||
_ => restart.to_string(),
|
||||
};
|
||||
format!(" --restart {restart}")
|
||||
}
|
||||
|
||||
fn parse_command(command: &str) -> String {
|
||||
if command.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {command}")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
async fn container_stats(
|
||||
container_name: Option<String>,
|
||||
) -> anyhow::Result<Vec<DockerContainerStats>> {
|
||||
let format = "--format \"{{ json . }}\"";
|
||||
let container_name = match container_name {
|
||||
Some(name) => format!(" {name}"),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let command =
|
||||
format!("docker stats{container_name} --no-stream {format}");
|
||||
let output = async_run_command(&command).await;
|
||||
if output.success() {
|
||||
let res = output
|
||||
.stdout
|
||||
.split('\n')
|
||||
.filter(|e| !e.is_empty())
|
||||
.map(|e| {
|
||||
let parsed = serde_json::from_str(e)
|
||||
.context(format!("failed at parsing entry {e}"))?;
|
||||
Ok(parsed)
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<DockerContainerStats>>>()?;
|
||||
Ok(res)
|
||||
} else {
|
||||
Err(anyhow!("{}", output.stderr.replace('\n', "")))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn pull_image(image: &str) -> Log {
|
||||
let command = format!("docker pull {image}");
|
||||
run_monitor_command("docker pull", command).await
|
||||
}
|
||||
|
||||
fn stop_container_command(
|
||||
container_name: &str,
|
||||
signal: Option<TerminationSignal>,
|
||||
time: Option<i32>,
|
||||
) -> String {
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let signal = signal
|
||||
.map(|signal| format!(" --signal {signal}"))
|
||||
.unwrap_or_default();
|
||||
let time = time
|
||||
.map(|time| format!(" --time {time}"))
|
||||
.unwrap_or_default();
|
||||
format!("docker stop{signal}{time} {container_name}")
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{accounts_response, secrets_response},
|
||||
docker, State,
|
||||
State,
|
||||
};
|
||||
|
||||
mod build;
|
||||
@@ -154,6 +154,7 @@ impl Resolve<PruneSystem> for State {
|
||||
PruneSystem {}: PruneSystem,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::prune_system().await)
|
||||
let command = String::from("docker system prune -a -f");
|
||||
Ok(run_monitor_command("prune system", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
server::docker_network::DockerNetwork, update::Log,
|
||||
};
|
||||
@@ -6,10 +7,7 @@ use periphery_client::api::network::{
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
docker::{self, client::docker_client},
|
||||
State,
|
||||
};
|
||||
use crate::{docker::docker_client, State};
|
||||
|
||||
//
|
||||
|
||||
@@ -33,7 +31,12 @@ impl Resolve<CreateNetwork> for State {
|
||||
CreateNetwork { name, driver }: CreateNetwork,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::network::create_network(&name, driver).await)
|
||||
let driver = match driver {
|
||||
Some(driver) => format!(" -d {driver}"),
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
Ok(run_monitor_command("create network", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +49,8 @@ impl Resolve<DeleteNetwork> for State {
|
||||
DeleteNetwork { name }: DeleteNetwork,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::network::delete_network(&name).await)
|
||||
let command = format!("docker network rm {name}");
|
||||
Ok(run_monitor_command("delete network", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +63,7 @@ impl Resolve<PruneNetworks> for State {
|
||||
_: PruneNetworks,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(docker::network::prune_networks().await)
|
||||
let command = String::from("docker network prune -f");
|
||||
Ok(run_monitor_command("prune networks", command).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
build::{Build, BuildConfig},
|
||||
get_image_name, optional_string, to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_images() -> Log {
|
||||
let command = String::from("docker image prune -a -f");
|
||||
run_monitor_command("prune images", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn build(
|
||||
build: &Build,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
let Build {
|
||||
name,
|
||||
config:
|
||||
BuildConfig {
|
||||
version,
|
||||
skip_secret_interp,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
build_args,
|
||||
labels,
|
||||
extra_args,
|
||||
use_buildx,
|
||||
image_registry,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = build;
|
||||
|
||||
let mut logs = Vec::new();
|
||||
|
||||
// Maybe docker login
|
||||
let should_push =
|
||||
match docker_login(image_registry, registry_token.as_deref())
|
||||
.await
|
||||
{
|
||||
Ok(should_push) => should_push,
|
||||
Err(e) => {
|
||||
logs.push(Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
));
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
|
||||
// Get paths
|
||||
let name = to_monitor_name(name);
|
||||
let build_dir =
|
||||
periphery_config().repo_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = match optional_string(dockerfile_path) {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
|
||||
// Get command parts
|
||||
let image_name = get_image_name(build);
|
||||
let build_args = parse_build_args(build_args);
|
||||
let labels = parse_labels(labels);
|
||||
let extra_args = parse_extra_args(extra_args);
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
let image_tags = image_tags(&image_name, version);
|
||||
let push_command = should_push
|
||||
.then(|| format!(" && docker image push --all-tags {image_name}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
|
||||
build_dir.display()
|
||||
);
|
||||
|
||||
if *skip_secret_interp {
|
||||
let build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
info!("finished building docker image");
|
||||
logs.push(build_log);
|
||||
} else {
|
||||
// Interpolate any missing secrets
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate secrets into docker build command",
|
||||
)?;
|
||||
replacers.extend(core_replacers);
|
||||
|
||||
let mut build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
build_log.command =
|
||||
svi::replace_in_string(&build_log.command, &replacers);
|
||||
build_log.stdout =
|
||||
svi::replace_in_string(&build_log.stdout, &replacers);
|
||||
build_log.stderr =
|
||||
svi::replace_in_string(&build_log.stderr, &replacers);
|
||||
logs.push(build_log);
|
||||
}
|
||||
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
fn image_tags(image_name: &str, version: &Version) -> String {
|
||||
let Version { major, minor, .. } = version;
|
||||
format!(
|
||||
" -t {image_name}:latest -t {image_name}:{version} -t {image_name}:{major}.{minor} -t {image_name}:{major}",
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
|
||||
build_args
|
||||
.iter()
|
||||
.map(|p| format!(" --build-arg {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
deployment::{
|
||||
Conversion, Deployment, DeploymentConfig, DeploymentImage,
|
||||
DockerContainerStats, RestartMode, TerminationSignal,
|
||||
},
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, SearchCombinator,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn container_log(container_name: &str, tail: u64) -> Log {
|
||||
let command = format!("docker logs {container_name} --tail {tail}");
|
||||
run_monitor_command("get container log", command).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn container_log_search(
|
||||
container_name: &str,
|
||||
terms: &[String],
|
||||
combinator: SearchCombinator,
|
||||
) -> Log {
|
||||
let grep = match combinator {
|
||||
SearchCombinator::Or => {
|
||||
format!("grep -E '{}'", terms.join("|"))
|
||||
}
|
||||
SearchCombinator::And => {
|
||||
format!("grep -P '^(?=.*{})'", terms.join(")(?=.*"))
|
||||
}
|
||||
};
|
||||
let command =
|
||||
format!("docker logs {container_name} --tail 5000 2>&1 | {grep}");
|
||||
run_monitor_command("get container log grep", command).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn container_stats(
|
||||
container_name: Option<String>,
|
||||
) -> anyhow::Result<Vec<DockerContainerStats>> {
|
||||
let format = "--format \"{{ json . }}\"";
|
||||
let container_name = match container_name {
|
||||
Some(name) => format!(" {name}"),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let command =
|
||||
format!("docker stats{container_name} --no-stream {format}");
|
||||
let output = async_run_command(&command).await;
|
||||
if output.success() {
|
||||
let res = output
|
||||
.stdout
|
||||
.split('\n')
|
||||
.filter(|e| !e.is_empty())
|
||||
.map(|e| {
|
||||
let parsed = serde_json::from_str(e)
|
||||
.context(format!("failed at parsing entry {e}"))?;
|
||||
Ok(parsed)
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<DockerContainerStats>>>()?;
|
||||
Ok(res)
|
||||
} else {
|
||||
Err(anyhow!("{}", output.stderr.replace('\n', "")))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_containers() -> Log {
|
||||
let command = String::from("docker container prune -f");
|
||||
run_monitor_command("prune containers", command).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn start_container(container_name: &str) -> Log {
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let command = format!("docker start {container_name}");
|
||||
run_monitor_command("docker start", command).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn stop_container(
|
||||
container_name: &str,
|
||||
signal: Option<TerminationSignal>,
|
||||
time: Option<i32>,
|
||||
) -> Log {
|
||||
let command = stop_container_command(container_name, signal, time);
|
||||
let log = run_monitor_command("docker stop", command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(container_name, None, time);
|
||||
let mut log = run_monitor_command("docker stop", command).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
format!("\n\n{}", log.stderr)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
log
|
||||
} else {
|
||||
log
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn stop_and_remove_container(
|
||||
container_name: &str,
|
||||
signal: Option<TerminationSignal>,
|
||||
time: Option<i32>,
|
||||
) -> Log {
|
||||
let stop_command =
|
||||
stop_container_command(container_name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {container_name}");
|
||||
let log =
|
||||
run_monitor_command("docker stop and remove", command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command =
|
||||
stop_container_command(container_name, None, time);
|
||||
let command = format!(
|
||||
"{stop_command} && docker container rm {container_name}"
|
||||
);
|
||||
let mut log = run_monitor_command("docker stop", command).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
format!("\n\n{}", log.stderr)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
log
|
||||
} else {
|
||||
log
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_container_command(
|
||||
container_name: &str,
|
||||
signal: Option<TerminationSignal>,
|
||||
time: Option<i32>,
|
||||
) -> String {
|
||||
let container_name = to_monitor_name(container_name);
|
||||
let signal = signal
|
||||
.map(|signal| format!(" --signal {signal}"))
|
||||
.unwrap_or_default();
|
||||
let time = time
|
||||
.map(|time| format!(" --time {time}"))
|
||||
.unwrap_or_default();
|
||||
format!("docker stop{signal}{time} {container_name}")
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn rename_container(
|
||||
curr_name: &str,
|
||||
new_name: &str,
|
||||
) -> Log {
|
||||
let curr = to_monitor_name(curr_name);
|
||||
let new = to_monitor_name(new_name);
|
||||
let command = format!("docker rename {curr} {new}");
|
||||
run_monitor_command("docker rename", command).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn pull_image(image: &str) -> Log {
|
||||
let command = format!("docker pull {image}");
|
||||
run_monitor_command("docker pull", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn deploy(
|
||||
deployment: &Deployment,
|
||||
stop_signal: Option<TerminationSignal>,
|
||||
stop_time: Option<i32>,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> Log {
|
||||
if let Err(e) = docker_login(
|
||||
&deployment.config.image_registry,
|
||||
registry_token.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let image = if let DeploymentImage::Image { image } =
|
||||
&deployment.config.image
|
||||
{
|
||||
if image.is_empty() {
|
||||
return Log::error(
|
||||
"get image",
|
||||
String::from("deployment does not have image attached"),
|
||||
);
|
||||
}
|
||||
image
|
||||
} else {
|
||||
return Log::error(
|
||||
"get image",
|
||||
String::from("deployment does not have image attached"),
|
||||
);
|
||||
};
|
||||
|
||||
let _ = pull_image(image).await;
|
||||
debug!("image pulled");
|
||||
let _ = stop_and_remove_container(
|
||||
&deployment.name,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
)
|
||||
.await;
|
||||
debug!("container stopped and removed");
|
||||
|
||||
let command = docker_run_command(deployment, image);
|
||||
debug!("docker run command: {command}");
|
||||
|
||||
if deployment.config.skip_secret_interp {
|
||||
run_monitor_command("docker run", command).await
|
||||
} else {
|
||||
let command = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context("failed to interpolate secrets into docker run command");
|
||||
if let Err(e) = command {
|
||||
return Log::error("docker run", format!("{e:?}"));
|
||||
}
|
||||
let (command, mut replacers) = command.unwrap();
|
||||
replacers.extend(core_replacers);
|
||||
let mut log = run_monitor_command("docker run", command).await;
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
log
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docker_run_command(
|
||||
Deployment {
|
||||
name,
|
||||
config:
|
||||
DeploymentConfig {
|
||||
volumes,
|
||||
ports,
|
||||
network,
|
||||
command,
|
||||
restart,
|
||||
environment,
|
||||
labels,
|
||||
extra_args,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &Deployment,
|
||||
image: &str,
|
||||
) -> String {
|
||||
let name = to_monitor_name(name);
|
||||
let ports = parse_conversions(ports, "-p");
|
||||
let volumes = volumes.to_owned();
|
||||
let volumes = parse_conversions(&volumes, "-v");
|
||||
let network = parse_network(network);
|
||||
let restart = parse_restart(restart);
|
||||
let environment = parse_environment(environment);
|
||||
let labels = parse_labels(labels);
|
||||
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}{command}")
|
||||
}
|
||||
|
||||
fn parse_conversions(
|
||||
conversions: &[Conversion],
|
||||
flag: &str,
|
||||
) -> String {
|
||||
conversions
|
||||
.iter()
|
||||
.map(|p| format!(" {flag} {}:{}", p.local, p.container))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn parse_environment(environment: &[EnvironmentVar]) -> String {
|
||||
environment
|
||||
.iter()
|
||||
.map(|p| format!(" --env {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn parse_network(network: &str) -> String {
|
||||
format!(" --network {network}")
|
||||
}
|
||||
|
||||
fn parse_restart(restart: &RestartMode) -> String {
|
||||
let restart = match restart {
|
||||
RestartMode::OnFailure => "on-failure:10".to_string(),
|
||||
_ => restart.to_string(),
|
||||
};
|
||||
format!(" --restart {restart}")
|
||||
}
|
||||
|
||||
fn parse_command(command: &str) -> String {
|
||||
if command.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {command}")
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
update::Log,
|
||||
EnvironmentVar,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::helpers::{get_docker_token, get_github_token};
|
||||
|
||||
pub mod build;
|
||||
pub mod client;
|
||||
pub mod container;
|
||||
pub mod network;
|
||||
|
||||
/// Returns whether build result should be pushed after build
|
||||
pub async fn docker_login(
|
||||
registry: &ImageRegistry,
|
||||
// For local token override from core.
|
||||
registry_token: Option<&str>,
|
||||
) -> anyhow::Result<bool> {
|
||||
match registry {
|
||||
ImageRegistry::None(_) => Ok(false),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for DockerHub registry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_docker_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::Ghcr(CloudRegistryConfig {
|
||||
account,
|
||||
..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_github_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login ghcr.io -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_extra_args(extra_args: &[String]) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if !args.is_empty() {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_labels(labels: &[EnvironmentVar]) -> String {
|
||||
labels
|
||||
.iter()
|
||||
.map(|p| format!(" --label {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_system() -> Log {
|
||||
let command = String::from("docker system prune -a -f");
|
||||
run_monitor_command("prune system", command).await
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::update::Log;
|
||||
|
||||
#[instrument]
|
||||
pub async fn create_network(
|
||||
name: &str,
|
||||
driver: Option<String>,
|
||||
) -> Log {
|
||||
let driver = match driver {
|
||||
Some(driver) => format!(" -d {driver}"),
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
run_monitor_command("create network", command).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn delete_network(name: &str) -> Log {
|
||||
let command = format!("docker network rm {name}");
|
||||
run_monitor_command("delete network", command).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_networks() -> Log {
|
||||
let command = String::from("docker network prune -f");
|
||||
run_monitor_command("prune networks", command).await
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::ConnectInfo,
|
||||
http::{Request, StatusCode},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use serror::{AddStatusCode, AddStatusCodeError};
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn guard_request_by_passkey(
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
) -> serror::Result<Response> {
|
||||
if periphery_config().passkeys.is_empty() {
|
||||
return Ok(next.run(req).await);
|
||||
}
|
||||
let Some(req_passkey) = req.headers().get("authorization") else {
|
||||
return Err(
|
||||
anyhow!("request was not sent with passkey")
|
||||
.status_code(StatusCode::UNAUTHORIZED),
|
||||
);
|
||||
};
|
||||
let req_passkey = req_passkey
|
||||
.to_str()
|
||||
.context("failed to convert passkey to str")
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
if periphery_config()
|
||||
.passkeys
|
||||
.iter()
|
||||
.any(|passkey| passkey == req_passkey)
|
||||
{
|
||||
Ok(next.run(req).await)
|
||||
} else {
|
||||
Err(
|
||||
anyhow!("request passkey invalid")
|
||||
.status_code(StatusCode::UNAUTHORIZED),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn guard_request_by_ip(
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
) -> serror::Result<Response> {
|
||||
if periphery_config().allowed_ips.is_empty() {
|
||||
return Ok(next.run(req).await);
|
||||
}
|
||||
let ConnectInfo(socket_addr) = req
|
||||
.extensions()
|
||||
.get::<ConnectInfo<SocketAddr>>()
|
||||
.context("could not get ConnectionInfo of request")
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
let ip = socket_addr.ip();
|
||||
if periphery_config().allowed_ips.contains(&ip) {
|
||||
Ok(next.run(req).await)
|
||||
} else {
|
||||
Err(
|
||||
anyhow!("requesting ip {ip} not allowed")
|
||||
.status_code(StatusCode::UNAUTHORIZED),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum_extra::{headers::ContentType, TypedHeader};
|
||||
use resolver_api::Resolver;
|
||||
use serror::Json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub async fn handler(
|
||||
Json(request): Json<crate::api::PeripheryRequest>,
|
||||
) -> serror::Result<(TypedHeader<ContentType>, String)> {
|
||||
let req_id = Uuid::new_v4();
|
||||
|
||||
let res = tokio::spawn(task(req_id, request))
|
||||
.await
|
||||
.context("task handler spawn error");
|
||||
|
||||
if let Err(e) = &res {
|
||||
warn!("request {req_id} spawn error: {e:#}");
|
||||
}
|
||||
|
||||
Ok((TypedHeader(ContentType::json()), res??))
|
||||
}
|
||||
|
||||
#[instrument(name = "PeripheryHandler")]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: crate::api::PeripheryRequest,
|
||||
) -> anyhow::Result<String> {
|
||||
info!("request {req_id} | {request:?}");
|
||||
let timer = Instant::now();
|
||||
|
||||
let res =
|
||||
State
|
||||
.resolve_request(request, ())
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
resolver_api::Error::Serialization(e) => {
|
||||
anyhow!("{e:?}").context("response serialization error")
|
||||
}
|
||||
resolver_api::Error::Inner(e) => e,
|
||||
});
|
||||
|
||||
if let Err(e) = &res {
|
||||
warn!("request {req_id} error: {e:#}");
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
config::core::AwsEcrConfig,
|
||||
EnvironmentVar,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
@@ -19,3 +25,103 @@ pub fn get_docker_token(
|
||||
.get(docker_account)
|
||||
.with_context(|| format!("did not find token in config for docker account {docker_account}"))
|
||||
}
|
||||
|
||||
pub fn parse_extra_args(extra_args: &[String]) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if !args.is_empty() {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_labels(labels: &[EnvironmentVar]) -> String {
|
||||
labels
|
||||
.iter()
|
||||
.map(|p| format!(" --label {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
/// Returns whether build result should be pushed after build
|
||||
#[instrument(skip(registry_token))]
|
||||
pub async fn docker_login(
|
||||
registry: &ImageRegistry,
|
||||
// For local token override from core.
|
||||
registry_token: Option<&str>,
|
||||
// For local config override from core.
|
||||
aws_ecr: Option<&AwsEcrConfig>,
|
||||
) -> anyhow::Result<bool> {
|
||||
match registry {
|
||||
ImageRegistry::None(_) => Ok(false),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for DockerHub registry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_docker_token(account)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"docker login -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"dockerhub login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
ImageRegistry::Ghcr(CloudRegistryConfig { account, .. }) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_github_token(account)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"docker login ghcr.io -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"ghcr login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let AwsEcrConfig { region, account_id } = aws_ecr
|
||||
.with_context(|| {
|
||||
format!("Could not find aws ecr config for label {label}")
|
||||
})?;
|
||||
let registry_token = registry_token
|
||||
.context("aws ecr build missing registry token from core")?;
|
||||
let log = async_run_command(&format!("docker login {account_id}.dkr.ecr.{region}.amazonaws.com -u AWS -p {registry_token}")).await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"aws ecr login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user