Compare commits

...

21 Commits
v0.3.2 ... v0

Author SHA1 Message Date
mbecker20
9c0be07ae1 refactor caching to use custom Cache struct 2023-05-28 08:31:18 +00:00
mbecker20
ab945aadde fix set termination timeout 0 2023-05-25 20:18:39 +00:00
mbecker20
c1c461c273 add command crud and run api 2023-05-24 07:45:52 +00:00
mbecker20
336742ee69 update author and clap args 2023-05-24 05:28:46 +00:00
beckerinj
405dacce1c sanitize container logs for any script tags 2023-05-13 02:23:05 -04:00
beckerinj
9acd45aa93 fix log whitespace non preservation issue 2023-05-13 02:14:14 -04:00
beckerinj
c889c2cc03 clean up log component imports 2023-05-13 01:49:06 -04:00
mbecker20
7ac91ef416 view images on server 2023-05-12 07:22:56 +00:00
mbecker20
8e28669aa1 potentially fix deployment update getting crossed with another deployment 2023-05-09 21:17:10 +00:00
mbecker20
6cdb91f8b8 more readable container state in header 2023-05-04 01:12:55 +00:00
mbecker20
e892474713 modify create deployment initializer 2023-05-03 21:03:57 +00:00
mbecker20
abdae98816 core handle term signal 2023-05-03 20:02:37 +00:00
mbecker20
ab4fe49f33 deployment / build config reset 2023-05-03 19:31:59 +00:00
mbecker20
1ace35103b fix ansi-to-html install 2023-05-03 07:21:38 +00:00
beckerinj
dbee729eee show ansi colors in the logs correctly 2023-05-03 03:13:42 -04:00
mbecker20
792576ce59 add auto redeploy user 2023-05-02 17:21:25 +00:00
mbecker20
a07624e9b9 0.3.4 fix docker stop --signal on older docker versions 2023-05-01 21:03:25 +00:00
mbecker20
bb8054af8a log version first 2023-05-01 08:43:00 +00:00
mbecker20
7738f3e066 core logs version on startup 2023-05-01 08:34:20 +00:00
mbecker20
5dee16a100 0.3.3 add default term signal and timeout to deployment 2023-05-01 08:28:12 +00:00
mbecker20
35f3bcdf2f update core version 2023-05-01 03:05:51 +00:00
70 changed files with 2440 additions and 687 deletions

352
Cargo.lock generated
View File

@@ -126,9 +126,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
version = "0.55.0"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1854be4730cc87602316707045a5c0585287419d54f293bbb52a82c895d9086a"
checksum = "fc00553f5f3c06ffd4510a9d576f92143618706c45ea6ff81e84ad9be9588abd"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -156,9 +156,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4232d3729eefc287adc0d5a8adc97b7d94eefffe6bbe94312cc86c7ab6b06ce"
checksum = "4cb57ac6088805821f78d282c0ba8aec809f11cbee10dda19a97b03ab040ccc2"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -170,9 +170,9 @@ dependencies = [
[[package]]
name = "aws-endpoint"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f04ab03b3f1cca91f7cccaa213056d732accb14e2e65debfacc1d28627d162"
checksum = "9c5f6f84a4f46f95a9bb71d9300b73cd67eb868bc43ae84f66ad34752299f4ac"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -184,9 +184,9 @@ dependencies = [
[[package]]
name = "aws-http"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad8c53f7560baaf635b6aa811f3213d39b50555d100f83e43801652d4e318e"
checksum = "a754683c322f7dc5167484266489fdebdcd04d26e53c162cad1f3f949f2c5671"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -203,9 +203,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74a7d11843d7b0234b874ed430cba258ac272aec352508371944eceb64d57dd0"
checksum = "5ab77050ecd90bf116dcbff6a7b8b2fb294b1399900a97e870c4879fe2010827"
dependencies = [
"aws-credential-types",
"aws-endpoint",
@@ -231,9 +231,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "0.25.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c10657158e12163d6b3fb1e0c9154e43656843794a3071d9ee63ec82a4de2d"
checksum = "babfd626348836a31785775e3c08a4c345a5ab4c6e06dfd9167f2bee0e6295d6"
dependencies = [
"aws-credential-types",
"aws-endpoint",
@@ -256,9 +256,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "0.25.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706a308b7277ac9aac78ab37933509659c6f978a8473e95d8e7a8103093133c3"
checksum = "2d0fbe3c2c342bc8dfea4bb43937405a8ec06f99140a0dcb9c7b59e54dfa93a1"
dependencies = [
"aws-credential-types",
"aws-endpoint",
@@ -282,9 +282,9 @@ dependencies = [
[[package]]
name = "aws-sig-auth"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d77d879ab210e958ba65a6d3842969a596738c024989cd3e490cf9f9b560ec"
checksum = "84dc92a63ede3c2cbe43529cb87ffa58763520c96c6a46ca1ced80417afba845"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -296,9 +296,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ab4eebc8ec484fb9eab04b15a5d1e71f3dc13bee8fdd2d9ed78bcd6ecbd7192"
checksum = "392fefab9d6fcbd76d518eb3b1c040b84728ab50f58df0c3c53ada4bea9d327e"
dependencies = [
"aws-smithy-http",
"form_urlencoded",
@@ -315,9 +315,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88573bcfbe1dcfd54d4912846df028b42d6255cbf9ce07be216b1bbfd11fc4b9"
checksum = "ae23b9fe7a07d0919000116c4c5c0578303fbce6fc8d32efca1f7759d4c20faf"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -327,9 +327,9 @@ dependencies = [
[[package]]
name = "aws-smithy-client"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f52352bae50d3337d5d6151b695d31a8c10ebea113eca5bead531f8301b067"
checksum = "5230d25d244a51339273b8870f0f77874cd4449fb4f8f629b21188ae10cfc0ba"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -351,9 +351,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03bcc02d7ed9649d855c8ce4a735e9848d7b8f7568aad0504c158e3baa955df8"
checksum = "b60e2133beb9fe6ffe0b70deca57aaeff0a35ad24a9c6fab2fd3b4f45b99fdb5"
dependencies = [
"aws-smithy-types",
"bytes",
@@ -373,9 +373,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http-tower"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da88b3a860f65505996c29192d800f1aeb9480440f56d63aad33a3c12045017a"
checksum = "3a4d94f556c86a0dd916a5d7c39747157ea8cb909ca469703e20fee33e448b67"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -389,18 +389,18 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b0c1e87d75cac889dca2a7f5ba280da2cde8122448e7fec1d614194dfa00c70"
checksum = "5ce3d6e6ebb00b2cce379f079ad5ec508f9bcc3a9510d9b9c1840ed1d6f8af39"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-query"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b50d15f446c19e088009ecb00e2fb2d13133d6fe1db702e9aa67ad135bf6a6"
checksum = "d58edfca32ef9bfbc1ca394599e17ea329cb52d6a07359827be74235b64b3298"
dependencies = [
"aws-smithy-types",
"urlencoding",
@@ -408,9 +408,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0afc731fd1417d791f9145a1e0c30e23ae0beaab9b4814017708ead2fc20f1"
checksum = "58db46fc1f4f26be01ebdb821751b4e2482cd43aa2b64a0348fb89762defaffa"
dependencies = [
"base64-simd",
"itoa",
@@ -421,18 +421,18 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5398c1c25dfc6f8c282b1552a66aa807c9d6e15e1b3a84b94aa44e7859bec3"
checksum = "fb557fe4995bd9ec87fb244bbb254666a971dc902a783e9da8b7711610e9664c"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
version = "0.55.1"
version = "0.55.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9b082e329d9a304d39e193ad5c7ab363a0d6507aca6965e0673a746686fb0cc"
checksum = "de0869598bfe46ec44ffe17e063ed33336e59df90356ca8ff0e8da6f7c1d994b"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -473,9 +473,9 @@ dependencies = [
"sha1",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.18.0",
"tower",
"tower-http 0.4.0",
"tower-http",
"tower-layer",
"tower-service",
]
@@ -497,25 +497,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "axum-extra"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51227033e4d3acad15c879092ac8a228532707b5db5ff2628f638334f63e1b7a"
dependencies = [
"axum",
"bytes",
"futures-util",
"http",
"mime",
"pin-project-lite",
"tokio",
"tower",
"tower-http 0.3.5",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum_oauth2"
version = "0.1.0"
@@ -572,6 +553,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.3"
@@ -632,14 +625,16 @@ dependencies = [
[[package]]
name = "bson"
version = "2.5.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8746d07211bb12a7c34d995539b4a2acd4e0b0e757de98ce2ab99bcf17443fad"
checksum = "9aeb8bae494e49dbc330dd23cf78f6f7accee22f640ce3ab17841badaa4ce232"
dependencies = [
"ahash",
"base64 0.13.1",
"bitvec",
"hex",
"indexmap",
"js-sys",
"lazy_static",
"rand",
"serde",
@@ -804,14 +799,13 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "core"
version = "0.3.0"
version = "0.3.4"
dependencies = [
"anyhow",
"async_timing_util",
"aws-config",
"aws-sdk-ec2",
"axum",
"axum-extra",
"axum_oauth2",
"bcrypt",
"db_client",
@@ -824,19 +818,19 @@ dependencies = [
"jwt",
"merge_config_files",
"monitor_helpers",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"mungos",
"periphery_client",
"serde",
"serde_derive",
"serde_json",
"sha2",
"slack_client_rs",
"termination_signal",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.19.0",
"tokio-util",
"tower",
"tower-http 0.4.0",
"tower-http",
"typeshare",
]
@@ -971,15 +965,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "daemonize"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e"
dependencies = [
"libc",
]
[[package]]
name = "darling"
version = "0.13.4"
@@ -1058,10 +1043,10 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "db_client"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"mungos",
]
@@ -1265,6 +1250,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.26"
@@ -1282,9 +1273,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
@@ -1292,9 +1283,9 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
@@ -1309,38 +1300,38 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.12",
]
[[package]]
name = "futures-sink"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.26"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
@@ -1884,9 +1875,9 @@ dependencies = [
[[package]]
name = "mongodb"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37fe10c1485a0cd603468e284a1a8535b4ecf46808f5f7de3639a1e1252dbf8"
checksum = "ebe15399de63ad4294c80069967736cbb87ebe467a8cd0629df9cab88a6fbde6"
dependencies = [
"async-trait",
"base64 0.13.1",
@@ -1934,14 +1925,14 @@ dependencies = [
[[package]]
name = "monitor_cli"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"async_timing_util",
"clap",
"colored",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"rand",
"run_command",
"run_command 0.0.5",
"serde",
"serde_derive",
"strum",
@@ -1951,65 +1942,59 @@ dependencies = [
[[package]]
name = "monitor_client"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"envy",
"futures-util",
"monitor_types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest",
"serde",
"serde_derive",
"serde_json",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.19.0",
"tokio-util",
]
[[package]]
name = "monitor_helpers"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"axum",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"rand",
"serde",
"serde_json",
"toml",
]
[[package]]
name = "monitor_periphery"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"async_timing_util",
"axum",
"bollard",
"clap",
"daemonize",
"dotenv",
"envy",
"futures",
"merge_config_files",
"monitor_helpers",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"parse_csl",
"run_command",
"run_command 0.0.6",
"serde",
"serde_derive",
"serde_json",
"svi",
"sysinfo",
"tokio",
"toml",
"tower",
]
[[package]]
name = "monitor_types"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"bollard",
@@ -2018,7 +2003,6 @@ dependencies = [
"derive_builder",
"diff-struct",
"serde",
"serde_derive",
"strum",
"strum_macros",
"typeshare",
@@ -2026,9 +2010,9 @@ dependencies = [
[[package]]
name = "monitor_types"
version = "0.3.2"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50f6e811dc59b9f19b13b58bee4636f6f1bee2815d9c05f523b530284db75d"
checksum = "24ce7541ee0fad1452ae11d0db73285cfbb9df79ad3e49c0680d0bc27c64db24"
dependencies = [
"anyhow",
"bollard",
@@ -2045,18 +2029,16 @@ dependencies = [
[[package]]
name = "mungos"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b8fabef8c6e29f25a64c58736ab58b191e28aa3bafc3e84a3b0e78a1ba00665"
checksum = "bb7d2b7c11b7b18b1436117633a8ec1d7d037530456c6603d5296499f0e38185"
dependencies = [
"anyhow",
"envy",
"futures",
"futures-util",
"mongodb",
"serde",
"serde_derive",
"serde_json",
"tokio",
]
[[package]]
@@ -2275,16 +2257,16 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "periphery_client"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"futures-util",
"monitor_types 0.3.2",
"monitor_types 0.3.4",
"reqwest",
"serde",
"serde_json",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.19.0",
]
[[package]]
@@ -2355,6 +2337,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@@ -2500,6 +2488,12 @@ name = "run_command"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb00d9c9c67906454ba83e62aaa52368814512ac1c5b65b8791bb313a9257b0e"
[[package]]
name = "run_command"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388e1106aa4bd809ba57afecb8b3efc60b6599cd06a774d5798a7c5a29675307"
dependencies = [
"tokio",
]
@@ -2818,6 +2812,16 @@ dependencies = [
"digest",
]
[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@@ -2827,6 +2831,18 @@ 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.8"
@@ -2922,9 +2938,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "svi"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec1ee5e6cf961310f3b4ba037f6a3680fc264f9077e0b9f16a0d7cc8d0ade140"
checksum = "78a65bb8a7e77c020a52094de0c5cab44506727339282368418b285fe9bc92ef"
dependencies = [
"thiserror",
]
@@ -2959,9 +2975,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sysinfo"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727220a596b4ca0af040a07091e49f5c105ec8f2592674339a5bf35be592f76e"
checksum = "02f1dc6930a439cc5d154221b5387d153f8183529b07c19aca24ea31e0a167e1"
dependencies = [
"cfg-if",
"core-foundation-sys",
@@ -2978,6 +2994,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.4.0"
@@ -3000,6 +3022,19 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "termination_signal"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c8af403325efec5c1f2ab0f69732a47680c40c5379b088958d053cf946b6bfa"
dependencies = [
"anyhow",
"futures",
"signal-hook",
"signal-hook-tokio",
"tokio",
]
[[package]]
name = "tests"
version = "0.1.0"
@@ -3155,13 +3190,25 @@ name = "tokio-tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite 0.18.0",
]
[[package]]
name = "tokio-tungstenite"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c"
dependencies = [
"futures-util",
"log",
"native-tls",
"tokio",
"tokio-native-tls",
"tungstenite",
"tungstenite 0.19.0",
]
[[package]]
@@ -3236,9 +3283,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.3.5"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
dependencies = [
"bitflags",
"bytes",
@@ -3254,27 +3301,10 @@ dependencies = [
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@@ -3385,6 +3415,25 @@ dependencies = [
"http",
"httparse",
"log",
"rand",
"sha1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"native-tls",
"rand",
"sha1",
@@ -3850,6 +3899,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xmlparser"
version = "0.13.5"

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_cli"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
authors = ["MoghTech"]
description = "monitor cli | tools to setup monitor system"

View File

@@ -1,6 +1,6 @@
[package]
name = "core"
version = "0.3.0"
version = "0.3.4"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,17 +11,16 @@ types = { package = "monitor_types", path = "../lib/types" }
db = { package = "db_client", path = "../lib/db_client" }
periphery = { package = "periphery_client", path = "../lib/periphery_client" }
axum_oauth2 = { path = "../lib/axum_oauth2" }
tokio = { version = "1.26", features = ["full"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio-util = "0.7"
tokio = { version = "1.28", features = ["full"] }
tokio-tungstenite = { version = "0.19", features=["native-tls"] }
tokio-util = { version = "0.7"}
axum = { version = "0.6", features = ["ws", "json"] }
axum-extra = { version = "0.5.0", features = ["spa"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.4.0", features = ["cors"] }
tower = { version = "0.4", features = ["timeout"] }
tower-http = { version = "0.4", features = ["fs", "cors"] }
slack = { package = "slack_client_rs", version = "0.0.8" }
mungos = "0.3.14"
futures-util = "0.3"
mungos = "0.3.19"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
dotenv = "0.15"
envy = "0.4"
@@ -31,10 +30,10 @@ jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"
async_timing_util = "0.1.14"
futures-util = "0.3"
diff-struct = "0.5"
typeshare = "1.0.0"
typeshare = "1.0.1"
hex = "0.4"
aws-config = "0.55"
aws-sdk-ec2 = "0.26"
merge_config_files = "0.1.3"
aws-config = "0.55.2"
aws-sdk-ec2 = "0.27.0"
merge_config_files = "0.1.3"
termination_signal = "0.1.2"

View File

@@ -5,12 +5,10 @@ use aws_sdk_ec2::Client;
use diff::Diff;
use futures_util::future::join_all;
use helpers::{all_logs_success, to_monitor_name};
use mungos::{doc, to_bson};
use mungos::mongodb::bson::{doc, to_bson};
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
AwsBuilderBuildConfig, Build, DockerContainerState, Log, Operation, PermissionLevel, Update,
UpdateStatus, UpdateTarget, Version,
monitor_timestamp, traits::Permissioned, AwsBuilderBuildConfig, Build, DockerContainerState,
Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget, Version,
};
use crate::{
@@ -43,13 +41,6 @@ impl State {
}
}
pub async fn build_busy(&self, id: &str) -> bool {
match self.build_action_states.lock().await.get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_build(&self, name: &str, user: &RequestUser) -> anyhow::Result<Build> {
if !user.is_admin && !user.create_build_permissions {
return Err(anyhow!("user does not have permission to create builds"));
@@ -117,7 +108,7 @@ impl State {
}
pub async fn delete_build(&self, build_id: &str, user: &RequestUser) -> anyhow::Result<Build> {
if self.build_busy(build_id).await {
if self.build_action_states.busy(build_id).await {
return Err(anyhow!("build busy"));
}
let build = self
@@ -147,21 +138,23 @@ impl State {
new_build: Build,
user: &RequestUser,
) -> anyhow::Result<Build> {
if self.build_busy(&new_build.id).await {
if self.build_action_states.busy(&new_build.id).await {
return Err(anyhow!("build busy"));
}
let id = new_build.id.clone();
{
let mut lock = self.build_action_states.lock().await;
let entry = lock.entry(id.clone()).or_default();
entry.updating = true;
}
self.build_action_states
.update_entry(id.clone(), |entry| {
entry.updating = true;
})
.await;
let res = self.update_build_inner(new_build, user).await;
{
let mut lock = self.build_action_states.lock().await;
let entry = lock.entry(id).or_default();
entry.updating = false;
}
self.build_action_states
.update_entry(id.clone(), |entry| {
entry.updating = false;
})
.await;
res
}
@@ -252,20 +245,20 @@ impl State {
}
pub async fn build(&self, build_id: &str, user: &RequestUser) -> anyhow::Result<Update> {
if self.build_busy(build_id).await {
if self.build_action_states.busy(build_id).await {
return Err(anyhow!("build busy"));
}
{
let mut lock = self.build_action_states.lock().await;
let entry = lock.entry(build_id.to_string()).or_default();
entry.building = true;
}
self.build_action_states
.update_entry(build_id.to_string(), |entry| {
entry.building = true;
})
.await;
let res = self.build_inner(build_id, user).await;
{
let mut lock = self.build_action_states.lock().await;
let entry = lock.entry(build_id.to_string()).or_default();
entry.building = false;
}
self.build_action_states
.update_entry(build_id.to_string(), |entry| {
entry.building = false;
})
.await;
res
}

238
core/src/actions/command.rs Normal file
View File

@@ -0,0 +1,238 @@
use anyhow::{anyhow, Context};
use diff::Diff;
use helpers::all_logs_success;
use types::{
monitor_timestamp, traits::Permissioned, Log, Operation, PeripheryCommand,
PeripheryCommandBuilder, PermissionLevel, Update, UpdateStatus, UpdateTarget,
};
use crate::{auth::RequestUser, state::State};
impl State {
pub async fn get_command_check_permissions(
&self,
command_id: &str,
user: &RequestUser,
permission_level: PermissionLevel,
) -> anyhow::Result<PeripheryCommand> {
let command = self.db.get_command(command_id).await?;
let permissions = command.get_user_permissions(&user.id);
if user.is_admin || permissions >= permission_level {
Ok(command)
} else {
Err(anyhow!(
"user does not have required permissions on this command"
))
}
}
pub async fn create_command(
&self,
name: &str,
server_id: String,
user: &RequestUser,
) -> anyhow::Result<PeripheryCommand> {
self.get_server_check_permissions(&server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let command = PeripheryCommandBuilder::default()
.name(name.to_string())
.server_id(server_id)
.build()
.context("failed to build command")?;
let command_id = self
.db
.commands
.create_one(command)
.await
.context("failed at adding command to db")?;
let command = self.db.get_command(&command_id).await?;
let update = Update {
target: UpdateTarget::Command(command_id),
operation: Operation::CreateCommand,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
..Default::default()
};
self.add_update(update).await?;
Ok(command)
}
pub async fn create_full_command(
&self,
mut command: PeripheryCommand,
user: &RequestUser,
) -> anyhow::Result<PeripheryCommand> {
command.id = self
.create_command(&command.name, command.server_id.clone(), user)
.await?
.id;
let command = self.update_command(command, user).await?;
Ok(command)
}
pub async fn copy_command(
&self,
target_id: &str,
new_name: String,
new_server_id: String,
user: &RequestUser,
) -> anyhow::Result<PeripheryCommand> {
let mut command = self
.get_command_check_permissions(target_id, user, PermissionLevel::Update)
.await?;
command.name = new_name;
command.server_id = new_server_id;
let command = self.create_full_command(command, user).await?;
Ok(command)
}
pub async fn delete_command(
&self,
command_id: &str,
user: &RequestUser,
) -> anyhow::Result<PeripheryCommand> {
if self.command_action_states.busy(command_id).await {
return Err(anyhow!("command busy"));
}
let command = self
.get_command_check_permissions(command_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
self.db.commands.delete_one(command_id).await?;
let update = Update {
target: UpdateTarget::Command(command_id.to_string()),
operation: Operation::DeleteCommand,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
logs: vec![Log::simple(
"delete command",
format!("deleted command {}", command.name),
)],
success: true,
..Default::default()
};
self.add_update(update).await?;
Ok(command)
}
pub async fn update_command(
&self,
mut new_command: PeripheryCommand,
user: &RequestUser,
) -> anyhow::Result<PeripheryCommand> {
let current_command = self
.get_command_check_permissions(&new_command.id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
// none of these should be changed through this method
new_command.permissions = current_command.permissions.clone();
new_command.server_id = current_command.server_id.clone();
new_command.created_at = current_command.created_at.clone();
new_command.updated_at = start_ts.clone();
self.db
.commands
.update_one(
&new_command.id,
mungos::Update::Regular(new_command.clone()),
)
.await
.context("failed at update one command")?;
let diff = current_command.diff(&new_command);
let update = Update {
operation: Operation::UpdateCommand,
target: UpdateTarget::Command(new_command.id.clone()),
start_ts,
status: UpdateStatus::Complete,
logs: vec![Log::simple(
"command update",
serde_json::to_string_pretty(&diff).unwrap(),
)],
operator: user.id.clone(),
success: true,
..Default::default()
};
self.add_update(update.clone()).await?;
self.update_update(update).await?;
Ok(new_command)
}
pub async fn run_command(
&self,
command_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.command_action_states.busy(command_id).await {
return Err(anyhow!("command busy"));
}
self.command_action_states
.update_entry(command_id.to_string(), |entry| {
entry.running = true;
})
.await;
let res = self.run_command_inner(command_id, user).await;
self.command_action_states
.update_entry(command_id.to_string(), |entry| {
entry.running = false;
})
.await;
res
}
async fn run_command_inner(
&self,
command_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
let start_ts = monitor_timestamp();
let command = self
.get_command_check_permissions(command_id, user, PermissionLevel::Execute)
.await?;
if command.command.path.is_empty() || command.command.command.is_empty() {
return Err(anyhow!("command or path is empty, aborting"));
}
let server = self.db.get_server(&command.server_id).await?;
let mut update = Update {
target: UpdateTarget::Command(command_id.to_string()),
operation: Operation::RunCommand,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
match self.periphery.run_command(&server, &command.command).await {
Ok(log) => {
update.logs.push(log);
}
Err(e) => {
update
.logs
.push(Log::error("clone repo", format!("{e:#?}")));
}
}
update.success = all_logs_success(&update.logs);
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
self.update_update(update.clone()).await?;
Ok(update)
}
}

View File

@@ -1,13 +1,11 @@
use anyhow::{anyhow, Context};
use diff::Diff;
use helpers::{all_logs_success, to_monitor_name};
use mungos::doc;
use mungos::mongodb::bson::doc;
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
Deployment, DeploymentWithContainerState, DockerContainerState, Log, Operation,
PermissionLevel, ServerStatus, ServerWithStatus, TerminationSignal, Update, UpdateStatus,
UpdateTarget,
monitor_timestamp, traits::Permissioned, Deployment, DeploymentBuilder,
DeploymentWithContainerState, DockerContainerState, Log, Operation, PermissionLevel,
ServerStatus, ServerWithStatus, TerminationSignal, Update, UpdateStatus, UpdateTarget,
};
use crate::{
@@ -34,13 +32,6 @@ impl State {
}
}
pub async fn deployment_busy(&self, id: &str) -> bool {
match self.deployment_action_states.lock().await.get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_deployment(
&self,
name: &str,
@@ -50,16 +41,18 @@ impl State {
self.get_server_check_permissions(&server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let deployment = Deployment {
name: to_monitor_name(name),
server_id,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
created_at: start_ts.clone(),
updated_at: start_ts.clone(),
..Default::default()
};
let mut deployment = DeploymentBuilder::default()
.name(to_monitor_name(name))
.server_id(server_id)
.build()
.context("failed to build deployment")?;
deployment.permissions = [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect();
deployment.created_at = start_ts.clone();
deployment.updated_at = start_ts.clone();
let deployment_id = self
.db
.deployments
@@ -116,7 +109,7 @@ impl State {
stop_signal: Option<TerminationSignal>,
stop_time: Option<i32>,
) -> anyhow::Result<Deployment> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
let deployment = self
@@ -167,21 +160,25 @@ impl State {
new_deployment: Deployment,
user: &RequestUser,
) -> anyhow::Result<Deployment> {
if self.deployment_busy(&new_deployment.id).await {
if self.deployment_action_states.busy(&new_deployment.id).await {
return Err(anyhow!("deployment busy"));
}
let id = new_deployment.id.clone();
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(id.clone()).or_default();
entry.updating = true;
}
self.deployment_action_states
.update_entry(id.clone(), |entry| {
entry.updating = true;
})
.await;
let res = self.update_deployment_inner(new_deployment, user).await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(id).or_default();
entry.updating = false;
}
self.deployment_action_states
.update_entry(id.clone(), |entry| {
entry.updating = false;
})
.await;
res
}
@@ -285,22 +282,25 @@ impl State {
new_name: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(&deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.renaming = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.renaming = true;
})
.await;
let res = self
.rename_deployment_inner(deployment_id, new_name, user)
.await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.renaming = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.renaming = false;
})
.await;
res
}
@@ -451,20 +451,23 @@ impl State {
user: &RequestUser,
check_deployment_busy: bool,
) -> anyhow::Result<Update> {
if check_deployment_busy && self.deployment_busy(deployment_id).await {
if check_deployment_busy && self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.recloning = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.recloning = true;
})
.await;
let res = self.reclone_deployment_inner(deployment_id, user).await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.recloning = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.recloning = false;
})
.await;
res
}
@@ -488,19 +491,18 @@ impl State {
};
update.id = self.add_update(update.clone()).await?;
update.success = match self.periphery.clone_repo(&server, &deployment).await {
match self.periphery.clone_repo(&server, &deployment).await {
Ok(clone_logs) => {
update.logs.extend(clone_logs);
true
}
Err(e) => {
update
.logs
.push(Log::error("clone repo", format!("{e:#?}")));
false
}
};
update.success = all_logs_success(&update.logs);
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
@@ -516,22 +518,24 @@ impl State {
stop_signal: Option<TerminationSignal>,
stop_time: Option<i32>,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.deploying = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.deploying = true;
})
.await;
let res = self
.deploy_container_inner(deployment_id, user, stop_signal, stop_time)
.await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.deploying = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.deploying = false;
})
.await;
res
}
@@ -578,6 +582,9 @@ impl State {
update.id = self.add_update(update.clone()).await?;
let stop_signal = stop_signal.unwrap_or(deployment.termination_signal).into();
let stop_time = stop_time.unwrap_or(deployment.termination_timeout).into();
let deploy_log = match self
.periphery
.deploy(&server, &deployment, stop_signal, stop_time)
@@ -602,20 +609,22 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.starting = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.starting = true;
})
.await;
let res = self.start_container_inner(deployment_id, user).await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.starting = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.starting = false;
})
.await;
res
}
@@ -674,22 +683,24 @@ impl State {
stop_signal: Option<TerminationSignal>,
stop_time: Option<i32>,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.stopping = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.stopping = true;
})
.await;
let res = self
.stop_container_inner(deployment_id, user, stop_signal, stop_time)
.await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.stopping = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.stopping = false;
})
.await;
res
}
@@ -716,6 +727,9 @@ impl State {
};
update.id = self.add_update(update.clone()).await?;
let stop_signal = stop_signal.unwrap_or(deployment.termination_signal).into();
let stop_time = stop_time.unwrap_or(deployment.termination_timeout).into();
let log = self
.periphery
.container_stop(&server, &deployment.name, stop_signal, stop_time)
@@ -750,22 +764,24 @@ impl State {
stop_signal: Option<TerminationSignal>,
stop_time: Option<i32>,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.removing = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.removing = true;
})
.await;
let res = self
.remove_container_inner(deployment_id, user, stop_signal, stop_time)
.await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.removing = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.removing = false;
})
.await;
res
}
@@ -792,6 +808,9 @@ impl State {
};
update.id = self.add_update(update.clone()).await?;
let stop_signal = stop_signal.unwrap_or(deployment.termination_signal).into();
let stop_time = stop_time.unwrap_or(deployment.termination_timeout).into();
let log = self
.periphery
.container_remove(&server, &deployment.name, stop_signal, stop_time)
@@ -824,20 +843,22 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id).await {
if self.deployment_action_states.busy(deployment_id).await {
return Err(anyhow!("deployment busy"));
}
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.pulling = true;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.pulling = true;
})
.await;
let res = self.pull_deployment_repo_inner(deployment_id, user).await;
{
let mut lock = self.deployment_action_states.lock().await;
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.pulling = false;
}
self.deployment_action_states
.update_entry(deployment_id.to_string(), |entry| {
entry.pulling = false;
})
.await;
res
}

View File

@@ -4,6 +4,7 @@ use types::Update;
use crate::state::State;
mod build;
mod command;
mod deployment;
mod group;
mod procedure;

View File

@@ -1,11 +1,10 @@
use anyhow::{anyhow, Context};
use diff::Diff;
use futures_util::future::join_all;
use mungos::doc;
use mungos::mongodb::bson::doc;
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
Log, Operation, PermissionLevel, Server, Update, UpdateStatus, UpdateTarget,
monitor_timestamp, traits::Permissioned, Log, Operation, PermissionLevel, Server, Update,
UpdateStatus, UpdateTarget,
};
use crate::{auth::RequestUser, state::State};
@@ -28,13 +27,6 @@ impl State {
}
}
pub async fn server_busy(&self, id: &str) -> bool {
match self.server_action_states.lock().await.get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_server(
&self,
name: &str,
@@ -96,7 +88,7 @@ impl State {
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Server> {
if self.server_busy(server_id).await {
if self.server_action_states.busy(server_id).await {
return Err(anyhow!("server busy"));
}
let server = self
@@ -164,7 +156,7 @@ impl State {
mut new_server: Server,
user: &RequestUser,
) -> anyhow::Result<Server> {
if self.server_busy(&new_server.id).await {
if self.server_action_states.busy(&new_server.id).await {
return Err(anyhow!("server busy"));
}
let current_server = self
@@ -208,20 +200,22 @@ impl State {
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id).await {
if self.server_action_states.busy(server_id).await {
return Err(anyhow!("server busy"));
}
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_networks = true;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_networks = true;
})
.await;
let res = self.prune_networks_inner(server_id, user).await;
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_networks = false;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_networks = false;
})
.await;
res
}
@@ -269,20 +263,22 @@ impl State {
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id).await {
if self.server_action_states.busy(server_id).await {
return Err(anyhow!("server busy"));
}
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_images = true;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_images = true;
})
.await;
let res = self.prune_images_inner(server_id, user).await;
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_images = false;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_images = false;
})
.await;
res
}
@@ -331,20 +327,22 @@ impl State {
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id).await {
if self.server_action_states.busy(server_id).await {
return Err(anyhow!("server busy"));
}
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_containers = true;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_containers = true;
})
.await;
let res = self.prune_containers_inner(server_id, user).await;
{
let mut lock = self.server_action_states.lock().await;
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_containers = false;
}
self.server_action_states
.update_entry(server_id.to_string(), |entry| {
entry.pruning_containers = false;
})
.await;
res
}

View File

@@ -9,7 +9,11 @@ use axum::{
};
use futures_util::TryStreamExt;
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize, Document, FindOptions, Serialize};
use mungos::mongodb::{
bson::{doc, Document},
options::FindOptions,
};
use serde::{Deserialize, Serialize};
use types::{
monitor_ts_from_unix, traits::Permissioned, unix_from_monitor_ts, AwsBuilderConfig, Build,
BuildActionState, BuildVersionsReponse, Operation, PermissionLevel, UpdateStatus,
@@ -293,13 +297,7 @@ impl State {
) -> anyhow::Result<BuildActionState> {
self.get_build_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self
.build_action_states
.lock()
.await
.entry(id)
.or_default()
.clone();
let action_state = self.build_action_states.get_or_default(id).await;
Ok(action_state)
}

220
core/src/api/command.rs Normal file
View File

@@ -0,0 +1,220 @@
use anyhow::Context;
use axum::{
extract::{Path, Query},
routing::{delete, get, patch, post},
Json, Router,
};
use helpers::handle_anyhow_error;
use mungos::mongodb::bson::Document;
use serde::{Deserialize, Serialize};
use types::{traits::Permissioned, CommandActionState, PeripheryCommand, PermissionLevel};
use typeshare::typeshare;
use crate::{
api::spawn_request_action,
auth::{RequestUser, RequestUserExtension},
response,
state::{State, StateExtension},
};
#[derive(Serialize, Deserialize)]
pub struct CommandId {
id: String,
}
#[typeshare]
#[derive(Serialize, Deserialize)]
pub struct CreateCommandBody {
name: String,
server_id: String,
}
#[typeshare]
#[derive(Serialize, Deserialize)]
pub struct CopyCommandBody {
name: String,
server_id: String,
}
pub fn router() -> Router {
Router::new()
.route(
"/:id",
get(
|state: StateExtension,
user: RequestUserExtension,
Path(CommandId { id })| async move {
let command = state
.get_command_check_permissions(&id, &user, PermissionLevel::Read)
.await
.map_err(handle_anyhow_error)?;
response!(Json(command))
},
),
)
.route(
"/list",
get(
|state: StateExtension,
user: RequestUserExtension,
Query(query): Query<Document>| async move {
let commands = state
.list_commands(&user, query)
.await
.map_err(handle_anyhow_error)?;
response!(Json(commands))
},
),
)
.route(
"/create",
post(
|state: StateExtension,
user: RequestUserExtension,
Json(command): Json<CreateCommandBody>| async move {
let command = state
.create_command(&command.name, command.server_id, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(command))
},
),
)
.route(
"/create_full",
post(
|state: StateExtension,
user: RequestUserExtension,
Json(command): Json<PeripheryCommand>| async move {
let command = spawn_request_action(async move {
state
.create_full_command(command, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(command))
},
),
)
.route(
"/:id/copy",
post(
|state: StateExtension,
user: RequestUserExtension,
Path(CommandId { id }),
Json(command): Json<CopyCommandBody>| async move {
let command = spawn_request_action(async move {
state
.copy_command(&id, command.name, command.server_id, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(command))
},
),
)
.route(
"/:id/delete",
delete(
|state: StateExtension,
user: RequestUserExtension,
Path(CommandId { id })| async move {
let build = spawn_request_action(async move {
state
.delete_command(&id, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(build))
},
),
)
.route(
"/update",
patch(
|state: StateExtension,
user: RequestUserExtension,
Json(command): Json<PeripheryCommand>| async move {
let command = spawn_request_action(async move {
state
.update_command(command, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(command))
},
),
)
.route(
"/:id/action_state",
get(
|state: StateExtension,
user: RequestUserExtension,
Path(CommandId { id })| async move {
let action_state = state
.get_command_action_states(id, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(action_state))
},
),
)
.route(
"/:id/run",
post(
|state: StateExtension,
user: RequestUserExtension,
Path(CommandId { id })| async move {
let update = spawn_request_action(async move {
state
.run_command(&id, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(update))
},
),
)
}
impl State {
async fn list_commands(
&self,
user: &RequestUser,
query: impl Into<Option<Document>>,
) -> anyhow::Result<Vec<PeripheryCommand>> {
let commands: Vec<PeripheryCommand> = self
.db
.commands
.get_some(query, None)
.await
.context("failed at get all commands query")?
.into_iter()
.filter(|s| {
if user.is_admin {
true
} else {
let permissions = s.get_user_permissions(&user.id);
permissions != PermissionLevel::None
}
})
.collect();
Ok(commands)
}
async fn get_command_action_states(
&self,
id: String,
user: &RequestUser,
) -> anyhow::Result<CommandActionState> {
self.get_command_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self.command_action_states.get_or_default(id).await;
Ok(action_state)
}
}

View File

@@ -8,7 +8,11 @@ use axum::{
};
use futures_util::future::join_all;
use helpers::handle_anyhow_error;
use mungos::{doc, options::FindOneOptions, Deserialize, Document, Serialize};
use mungos::mongodb::{
bson::{doc, Document},
options::FindOneOptions,
};
use serde::{Deserialize, Serialize};
use types::{
traits::Permissioned, Deployment, DeploymentActionState, DeploymentWithContainerState,
DockerContainerState, DockerContainerStats, Log, Operation, PermissionLevel, Server,
@@ -451,13 +455,7 @@ impl State {
) -> anyhow::Result<DeploymentActionState> {
self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self
.deployment_action_states
.lock()
.await
.entry(id)
.or_default()
.clone();
let action_state = self.deployment_action_states.get_or_default(id).await;
Ok(action_state)
}

View File

@@ -4,7 +4,7 @@ use axum_oauth2::random_duration;
use helpers::handle_anyhow_error;
use hex::ToHex;
use hmac::{Hmac, Mac};
use mungos::Deserialize;
use serde::Deserialize;
use sha2::Sha256;
use types::GITHUB_WEBHOOK_USER_ID;

View File

@@ -5,7 +5,8 @@ use axum::{
Extension, Json, Router,
};
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Document, Serialize};
use mungos::mongodb::bson::Document;
use serde::{Deserialize, Serialize};
use types::{traits::Permissioned, Group, PermissionLevel};
use typeshare::typeshare;

View File

@@ -9,7 +9,8 @@ use axum::{
};
use futures_util::Future;
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize};
use mungos::mongodb::bson::doc;
use serde::Deserialize;
use types::{PermissionLevel, UpdateTarget, User};
use typeshare::typeshare;
@@ -20,15 +21,16 @@ use crate::{
ResponseResult,
};
pub mod build;
pub mod deployment;
mod build;
mod command;
mod deployment;
mod github_listener;
pub mod group;
pub mod permissions;
pub mod procedure;
pub mod secret;
pub mod server;
pub mod update;
mod group;
mod permissions;
mod procedure;
mod secret;
mod server;
mod update;
#[typeshare]
#[derive(Deserialize)]
@@ -94,6 +96,7 @@ pub fn router() -> Router {
.nest("/build", build::router())
.nest("/deployment", deployment::router())
.nest("/server", server::router())
.nest("/command", command::router())
.nest("/procedure", procedure::router())
.nest("/group", group::router())
.nest("/update", update::router())

View File

@@ -1,7 +1,8 @@
use anyhow::{anyhow, Context};
use axum::{routing::post, Extension, Json, Router};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize, Document, Serialize};
use mungos::mongodb::bson::{doc, Document};
use serde::{Deserialize, Serialize};
use types::{
monitor_timestamp, Build, Deployment, Group, Log, Operation, PermissionLevel,
PermissionsTarget, Procedure, Server, Update, UpdateStatus, UpdateTarget,

View File

@@ -5,7 +5,8 @@ use axum::{
Extension, Json, Router,
};
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Document, Serialize};
use mungos::mongodb::bson::Document;
use serde::{Deserialize, Serialize};
use types::{traits::Permissioned, PermissionLevel, Procedure};
use typeshare::typeshare;

View File

@@ -5,7 +5,11 @@ use axum::{
Extension, Json, Router,
};
use helpers::{generate_secret, handle_anyhow_error};
use mungos::{doc, to_bson, Deserialize, Document, Serialize, Update};
use mungos::{
mongodb::bson::{doc, to_bson, Document},
Update,
};
use serde::{Deserialize, Serialize};
use types::{monitor_timestamp, ApiSecret};
use typeshare::typeshare;

View File

@@ -8,7 +8,11 @@ use axum::{
};
use futures_util::{future::join_all, SinkExt, StreamExt};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize, Document, FindOptions};
use mungos::mongodb::{
bson::{doc, Document},
options::FindOptions,
};
use serde::Deserialize;
use tokio::select;
use tokio_tungstenite::tungstenite::Message;
use tokio_util::sync::CancellationToken;
@@ -676,13 +680,7 @@ impl State {
) -> anyhow::Result<ServerActionState> {
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self
.server_action_states
.lock()
.await
.entry(id)
.or_default()
.clone();
let action_state = self.server_action_states.get_or_default(id).await;
Ok(action_state)
}
}

View File

@@ -1,7 +1,7 @@
use anyhow::{anyhow, Context};
use axum::{extract::Query, routing::get, Extension, Json, Router};
use helpers::handle_anyhow_error;
use mungos::{doc, to_bson};
use mungos::mongodb::bson::{doc, to_bson};
use serde_json::Value;
use types::{PermissionLevel, Update, UpdateTarget};
@@ -92,6 +92,10 @@ impl State {
.get_group_check_permissions(id, user, PermissionLevel::Read)
.await
.map(|_| ()),
UpdateTarget::Command(id) => self
.get_command_check_permissions(id, user, PermissionLevel::Read)
.await
.map(|_| ()),
}
}
}

View File

@@ -4,7 +4,8 @@ use anyhow::{anyhow, Context};
use axum::{extract::Query, response::Redirect, routing::get, Extension, Router};
use axum_oauth2::github::{GithubOauthClient, GithubOauthExtension};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize};
use mungos::mongodb::bson::doc;
use serde::Deserialize;
use types::{monitor_timestamp, CoreConfig, User};
use crate::{response, state::StateExtension};

View File

@@ -4,7 +4,8 @@ use anyhow::{anyhow, Context};
use axum::{extract::Query, response::Redirect, routing::get, Extension, Router};
use axum_oauth2::google::{GoogleOauthClient, GoogleOauthExtension};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize};
use mungos::mongodb::bson::doc;
use serde::Deserialize;
use types::{monitor_timestamp, CoreConfig, User};
use crate::{response, state::StateExtension};

View File

@@ -9,7 +9,7 @@ use axum::{body::Body, http::Request, Extension};
use axum_oauth2::random_string;
use hmac::{Hmac, Mac};
use jwt::{SignWithKey, VerifyWithKey};
use mungos::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use types::{CoreConfig, User};

View File

@@ -1,7 +1,7 @@
use anyhow::{anyhow, Context};
use axum::{extract::Json, routing::post, Extension, Router};
use helpers::handle_anyhow_error;
use mungos::doc;
use mungos::mongodb::bson::doc;
use types::{monitor_timestamp, User, UserCredentials};
use crate::state::StateExtension;

View File

@@ -9,7 +9,7 @@ use axum::{
Extension, Json, Router,
};
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use types::CoreConfig;
use typeshare::typeshare;

View File

@@ -2,7 +2,11 @@ use anyhow::{anyhow, Context};
use async_timing_util::unix_timestamp_ms;
use axum::{routing::post, Extension, Json, Router};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize, Document, Update};
use mungos::{
mongodb::bson::{doc, Document},
Update,
};
use serde::Deserialize;
use types::unix_from_monitor_ts;
use crate::state::StateExtension;

View File

@@ -1,9 +1,12 @@
use axum_extra::routing::SpaRouter;
use axum::Router;
use dotenv::dotenv;
use merge_config_files::parse_config_file;
use mungos::Deserialize;
use serde::Deserialize;
use tower_http::services::{ServeDir, ServeFile};
use types::CoreConfig;
type SpaRouter = Router;
#[derive(Deserialize, Debug)]
struct Env {
#[serde(default = "default_config_path")]
@@ -12,12 +15,17 @@ struct Env {
pub frontend_path: String,
}
pub fn load() -> (CoreConfig, SpaRouter) {
pub fn load() -> (CoreConfig, SpaRouter, ServeFile) {
dotenv().ok();
let env: Env = envy::from_env().expect("failed to parse environment variables");
let config = parse_config_file(env.config_path).expect("failed to parse config");
let spa_router = SpaRouter::new("/assets", env.frontend_path);
(config, spa_router)
let spa_router = Router::new().nest_service(
"/assets",
ServeDir::new(&env.frontend_path)
.not_found_service(ServeFile::new(format!("{}/index.html", env.frontend_path))),
);
let index_html_service = ServeFile::new(format!("{}/index.html", env.frontend_path));
(config, spa_router, index_html_service)
}
pub fn default_config_path() -> String {

View File

@@ -1,9 +1,10 @@
use std::str::FromStr;
use std::{collections::HashMap, str::FromStr};
use anyhow::anyhow;
use diff::{Diff, OptionDiff};
use helpers::to_monitor_name;
use types::Build;
use tokio::sync::RwLock;
use types::{traits::Busy, Build};
#[macro_export]
macro_rules! response {
@@ -66,3 +67,37 @@ pub fn empty_or_only_spaces(word: &str) -> bool {
}
return true;
}
#[derive(Default)]
pub struct Cache<T: Clone + Default> {
cache: RwLock<HashMap<String, T>>,
}
impl<T: Clone + Default> Cache<T> {
pub async fn get(&self, key: &str) -> Option<T> {
self.cache.read().await.get(key).map(|e| e.clone())
}
pub async fn get_or_default(&self, key: String) -> T {
let mut cache = self.cache.write().await;
cache.entry(key).or_default().clone()
}
pub async fn update_entry(&self, key: String, handler: impl Fn(&mut T) -> ()) {
let mut cache = self.cache.write().await;
handler(cache.entry(key).or_default());
}
pub async fn clear(&self) {
self.cache.write().await.clear();
}
}
impl<T: Clone + Default + Busy> Cache<T> {
pub async fn busy(&self, id: &str) -> bool {
match self.get(id).await {
Some(state) => state.busy(),
None => false,
}
}
}

View File

@@ -4,6 +4,7 @@ use ::helpers::get_socket_addr;
use auth::JwtClient;
use axum::{http::StatusCode, Router};
use state::State;
use termination_signal::tokio::immediate_term_handle;
use tower_http::cors::{Any, CorsLayer};
mod actions;
@@ -20,29 +21,43 @@ type ResponseResult<T> = Result<T, (StatusCode, String)>;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (config, spa_router) = config::load();
println!("version: v{}", env!("CARGO_PKG_VERSION"));
println!("starting monitor core on port {}...", config.port);
let term_signal = immediate_term_handle()?;
let app = Router::new()
.merge(spa_router)
.nest("/api", api::router())
.nest("/auth", auth::router(&config))
.nest("/ws", ws::router())
.layer(JwtClient::extension(&config))
.layer(State::extension(config.clone()).await)
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any),
);
let app = tokio::spawn(async move {
let (config, spa_router, index_html_service) = config::load();
println!("started monitor core on port {}", config.port);
println!("starting monitor core on port {}...", config.port);
axum::Server::bind(&get_socket_addr(config.port))
.serve(app.into_make_service())
.await?;
let app = Router::new()
.nest("/api", api::router())
.nest("/auth", auth::router(&config))
.nest("/ws", ws::router())
.layer(JwtClient::extension(&config))
.layer(State::extension(config.clone()).await)
.merge(spa_router)
.fallback_service(index_html_service)
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any),
);
println!("started monitor core on port {}", config.port);
axum::Server::bind(&get_socket_addr(config.port))
.serve(app.into_make_service())
.await?;
anyhow::Ok(())
});
tokio::select! {
res = app => return res?,
_ = term_signal => {},
}
Ok(())
}

View File

@@ -4,13 +4,13 @@ use async_timing_util::{
unix_timestamp_ms, wait_until_timelength, Timelength, ONE_DAY_MS, ONE_HOUR_MS,
};
use futures_util::future::join_all;
use mungos::doc;
use mungos::mongodb::bson::doc;
use slack::types::Block;
use types::{Server, SystemStats, SystemStatsQuery, SystemStatsRecord};
use crate::state::State;
#[derive(Default)]
#[derive(Default, Clone)]
pub struct AlertStatus {
cpu_alert: bool,
mem_alert: bool,
@@ -100,16 +100,16 @@ impl State {
}
async fn check_cpu(&self, server: &Server, stats: &SystemStats) {
let server_alert_status = self.server_alert_status.lock().await;
if self.slack.is_none()
|| server_alert_status
|| self
.server_alert_status
.get(&server.id)
.await
.map(|s| s.cpu_alert)
.unwrap_or(false)
{
return;
}
drop(server_alert_status);
if stats.cpu_perc > server.cpu_alert {
let region = if let Some(region) = &server.region {
format!(" ({region})")
@@ -171,24 +171,26 @@ impl State {
server.name, stats.cpu_perc
)
} else {
let mut lock = self.server_alert_status.lock().await;
let entry = lock.entry(server.id.clone()).or_default();
entry.cpu_alert = true;
self.server_alert_status
.update_entry(server.id.clone(), |entry| {
entry.cpu_alert = true;
})
.await;
}
}
}
async fn check_mem(&self, server: &Server, stats: &SystemStats) {
let server_alert_status = self.server_alert_status.lock().await;
if self.slack.is_none()
|| server_alert_status
|| self
.server_alert_status
.get(&server.id)
.await
.map(|s| s.mem_alert)
.unwrap_or(false)
{
return;
}
drop(server_alert_status);
let usage_perc = (stats.mem_used_gb / stats.mem_total_gb) * 100.0;
if usage_perc > server.mem_alert {
let region = if let Some(region) = &server.region {
@@ -254,25 +256,27 @@ impl State {
server.name, stats.mem_used_gb, stats.mem_total_gb,
)
} else {
let mut lock = self.server_alert_status.lock().await;
let entry = lock.entry(server.id.clone()).or_default();
entry.mem_alert = true;
self.server_alert_status
.update_entry(server.id.clone(), |entry| {
entry.mem_alert = true;
})
.await;
}
}
}
async fn check_disk(&self, server: &Server, stats: &SystemStats) {
for disk in &stats.disk.disks {
let server_alert_status = self.server_alert_status.lock().await;
if self.slack.is_none()
|| server_alert_status
|| self
.server_alert_status
.get(&server.id)
.await
.map(|s| *s.disk_alert.get(&disk.mount).unwrap_or(&false))
.unwrap_or(false)
{
return;
}
drop(server_alert_status);
let usage_perc = (disk.used_gb / disk.total_gb) * 100.0;
if usage_perc > server.disk_alert {
let region = if let Some(region) = &server.region {
@@ -315,25 +319,27 @@ impl State {
server.name, stats.disk.used_gb, stats.disk.total_gb,
)
} else {
let mut lock = self.server_alert_status.lock().await;
let entry = lock.entry(server.id.clone()).or_default();
entry.disk_alert.insert(disk.mount.clone(), true);
self.server_alert_status
.update_entry(server.id.clone(), |entry| {
entry.disk_alert.insert(disk.mount.clone(), true);
})
.await;
}
}
}
}
async fn check_components(&self, server: &Server, stats: &SystemStats) {
let lock = self.server_alert_status.lock().await;
if self.slack.is_none()
|| lock
|| self
.server_alert_status
.get(&server.id)
.await
.map(|s| s.component_alert)
.unwrap_or(false)
{
return;
}
drop(lock);
let info = stats
.components
.iter()
@@ -393,9 +399,11 @@ impl State {
info.join(" | "),
)
} else {
let mut lock = self.server_alert_status.lock().await;
let entry = lock.entry(server.id.clone()).or_default();
entry.component_alert = true;
self.server_alert_status
.update_entry(server.id.clone(), |entry| {
entry.component_alert = true;
})
.await;
}
}
}
@@ -487,7 +495,7 @@ impl State {
);
}
{
self.server_alert_status.lock().await.clear();
self.server_alert_status.clear().await;
}
}
}

View File

@@ -1,30 +1,31 @@
use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
use async_timing_util::{unix_timestamp_ms, wait_until_timelength, Timelength, ONE_HOUR_MS};
use axum::Extension;
use db::DbClient;
use futures_util::future::join_all;
use mungos::doc;
use mungos::mongodb::bson::doc;
use periphery::PeripheryClient;
use tokio::sync::Mutex;
use types::{BuildActionState, CoreConfig, DeploymentActionState, ServerActionState};
use types::{
BuildActionState, CommandActionState, CoreConfig, DeploymentActionState, ServerActionState,
};
use crate::{monitoring::AlertStatus, ws::update::UpdateWsChannel};
use crate::{helpers::Cache, monitoring::AlertStatus, ws::update::UpdateWsChannel};
pub type StateExtension = Extension<Arc<State>>;
pub type ActionStateMap<T> = Mutex<HashMap<String, T>>;
// pub type Cache<T> = RwLock<HashMap<String, T>>;
pub struct State {
pub config: CoreConfig,
pub db: DbClient,
pub update: UpdateWsChannel,
pub periphery: PeripheryClient,
pub slack: Option<slack::Client>,
pub build_action_states: ActionStateMap<BuildActionState>,
pub deployment_action_states: ActionStateMap<DeploymentActionState>,
pub server_action_states: ActionStateMap<ServerActionState>,
pub server_alert_status: Mutex<HashMap<String, AlertStatus>>, // (server_id, AlertStatus)
pub build_action_states: Cache<BuildActionState>,
pub deployment_action_states: Cache<DeploymentActionState>,
pub server_action_states: Cache<ServerActionState>,
pub command_action_states: Cache<CommandActionState>,
pub server_alert_status: Cache<AlertStatus>, // (server_id, AlertStatus)
}
impl State {
@@ -38,6 +39,7 @@ impl State {
build_action_states: Default::default(),
deployment_action_states: Default::default(),
server_action_states: Default::default(),
command_action_states: Default::default(),
server_alert_status: Default::default(),
};
let state = Arc::new(state);

View File

@@ -6,9 +6,9 @@ use axum::{
};
use futures_util::{SinkExt, StreamExt};
use helpers::handle_anyhow_error;
use mungos::Deserialize;
use serde::Deserialize;
use tokio::select;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
use tokio_util::sync::CancellationToken;
use types::{traits::Permissioned, PermissionLevel, SystemStatsQuery};
@@ -67,7 +67,7 @@ pub async fn ws_handler(
},
stats = stats_recv.next() => stats,
};
if let Some(Ok(Message::Text(msg))) = stats {
if let Some(Ok(TungsteniteMessage::Text(msg))) = stats {
let _ = ws_sender.send(AxumMessage::Text(msg)).await;
} else {
let _ = stats_recv.close(None).await;

View File

@@ -140,6 +140,12 @@ async fn user_can_see_update(
.await?;
(permissions, "group")
}
UpdateTarget::Command(command_id) => {
let permissions = db_client
.get_user_permission_on_command(user_id, command_id)
.await?;
(permissions, "command")
}
UpdateTarget::System => {
return Err(anyhow!("user not admin, can't recieve system updates"))
}

View File

@@ -11,6 +11,7 @@
},
"license": "GPL v3.0",
"devDependencies": {
"@types/sanitize-html": "^2.9.0",
"sass": "^1.57.1",
"typescript": "^4.9.4",
"vite": "^4.0.3",
@@ -19,10 +20,12 @@
"dependencies": {
"@solidjs/router": "^0.6.0",
"@tanstack/solid-query": "^4.26.0",
"ansi-to-html": "^0.7.2",
"axios": "^1.2.1",
"js-file-download": "^0.4.12",
"lightweight-charts": "^3.8.0",
"reconnecting-websocket": "^4.4.0",
"sanitize-html": "^2.10.0",
"solid-js": "^1.6.6"
}
}

View File

@@ -33,7 +33,7 @@ const Build: Component<{}> = (p) => {
}
});
});
onCleanup(() => unsub);
onCleanup(() => unsub());
const userCanUpdate = () =>
user().admin ||
build()?.permissions![user_id()] === PermissionLevel.Update;

View File

@@ -7,7 +7,7 @@ import {
useContext,
} from "solid-js";
import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../..";
import { client, pushNotification } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { Build, Operation, PermissionLevel, ServerWithStatus } from "../../../types";
@@ -55,10 +55,17 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
client.get_build(params.id).then((build) => {
set({
...build,
_id: { $oid: params.id } as any,
repo: build.repo,
branch: build.branch,
pre_build: build.pre_build,
docker_build_args: build.docker_build_args,
docker_build_args: {
build_path: build.docker_build_args?.build_path!,
dockerfile_path: build.docker_build_args?.dockerfile_path,
build_args: build.docker_build_args?.build_args,
extra_args: build.docker_build_args?.extra_args,
use_buildx: build.docker_build_args?.use_buildx,
},
docker_account: build.docker_account,
github_account: build.github_account,
aws_config: build.aws_config,
@@ -72,7 +79,13 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const save = () => {
setBuild("saving", true);
client.update_build(build)
client
.update_build(build)
.catch((e) => {
console.error(e);
pushNotification("bad", "update build failed");
setBuild("saving", false);
});
};
let update_unsub = () => {};

View File

@@ -1,4 +1,12 @@
import { Component, Match, Setter, Show, Switch, createSignal } from "solid-js";
import {
Component,
Match,
Setter,
Show,
Signal,
Switch,
createSignal,
} from "solid-js";
import { client } from "../..";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
@@ -13,10 +21,12 @@ import { combineClasses } from "../../util/helpers";
import { A, useParams } from "@solidjs/router";
import {
DockerContainerState,
Operation,
PermissionLevel,
ServerStatus,
TerminationSignal,
TerminationSignalLabel,
UpdateStatus,
} from "../../types";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
import Selector from "../shared/menu/Selector";
@@ -164,18 +174,12 @@ const Build: Component = () => {
};
const Deploy: Component<{ redeploy?: boolean }> = (p) => {
// const { deployments } = useAppState();
const params = useParams();
// const deployment = () => deployments.get(params.id)!;
const actions = useActionStates();
const { deployments } = useAppState();
const deployment = () => deployments.get(params.id);
const name = () => deployment()?.deployment.name;
const [termSignalLabel, setTermSignalLabel] =
createSignal<TerminationSignalLabel>({
signal: "default" as TerminationSignal,
label: "",
});
const [termSignalLabel, setTermSignalLabel] = useTermSignalLabel();
return (
<Show
when={!actions.deploying}
@@ -235,11 +239,7 @@ const RemoveContainer = () => {
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
const [termSignalLabel, setTermSignalLabel] =
createSignal<TerminationSignalLabel>({
signal: "default" as TerminationSignal,
label: "",
});
const [termSignalLabel, setTermSignalLabel] = useTermSignalLabel();
return (
<Show
when={!actions.removing}
@@ -316,11 +316,7 @@ const Stop = () => {
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
const [termSignalLabel, setTermSignalLabel] =
createSignal<TerminationSignalLabel>({
signal: "default" as TerminationSignal,
label: "",
});
const [termSignalLabel, setTermSignalLabel] = useTermSignalLabel();
return (
<Show
when={!actions.stopping}
@@ -336,9 +332,7 @@ const Stop = () => {
class="orange"
onConfirm={() => {
client.stop_container(params.id, {
stop_signal: ((termSignalLabel().signal as any) === "default"
? undefined
: termSignalLabel().signal) as TerminationSignal,
stop_signal: termSignalLabel().signal,
});
}}
title="stop container"
@@ -434,7 +428,7 @@ const TermSignalSelector: Component<{
<Show
when={
deployment()?.state === DockerContainerState.Running &&
(deployment()?.deployment.term_signal_labels?.length || 0) > 0
(deployment()?.deployment.term_signal_labels?.length || 0) > 1
}
>
<Flex
@@ -446,10 +440,7 @@ const TermSignalSelector: Component<{
<Selector
targetClass="blue"
selected={p.termSignalLabel}
items={[
{ signal: "default", label: "" },
...(deployment()?.deployment.term_signal_labels || []),
]}
items={deployment()?.deployment.term_signal_labels || []}
itemMap={({ signal, label }) => (
<Flex gap="0.5rem" alignItems="center">
<div>{signal}</div>
@@ -458,9 +449,7 @@ const TermSignalSelector: Component<{
</Show>
</Flex>
)}
onSelect={(signal) =>
p.setTermSignalLabel(signal as TerminationSignalLabel)
}
onSelect={(signal) => p.setTermSignalLabel(signal)}
position="bottom right"
/>
</Flex>
@@ -468,4 +457,28 @@ const TermSignalSelector: Component<{
);
};
function useTermSignalLabel(): Signal<TerminationSignalLabel> {
const params = useParams();
const { deployments, ws } = useAppState();
const deployment = () => deployments.get(params.id)?.deployment;
const term_signal = () =>
deployment()?.termination_signal || TerminationSignal.SigTerm;
const default_term_signal_label = () => ({
signal: term_signal(),
label:
deployment()?.term_signal_labels?.find(
({ signal }) => signal === term_signal()
)?.label || "",
});
const [label, setLabel] = createSignal<TerminationSignalLabel>(
default_term_signal_label()
);
ws.subscribe([Operation.UpdateDeployment], (update) => {
if (update.status === UpdateStatus.Complete) {
setTimeout(() => setLabel(default_term_signal_label()), 100);
}
});
return [label, setLabel];
}
export default Actions;

View File

@@ -2,7 +2,6 @@ import { Component, createResource, createSignal, Show } from "solid-js";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import {
combineClasses,
deploymentHeaderStateClass,
getId,
readableVersion,
@@ -79,7 +78,7 @@ const Header: Component<{}> = (p) => {
<>
<Grid
gap="0.5rem"
class={combineClasses("card shadow")}
class="card shadow"
style={{
position: "relative",
cursor: isSemiMobile() ? "pointer" : undefined,
@@ -173,7 +172,7 @@ const Header: Component<{}> = (p) => {
{server()?.server.name || "unknown"}
</A>
<div class={deploymentHeaderStateClass(deployment().state)}>
{deployment().state}
{deployment().state.replaceAll("_", " ")}
</div>
</Flex>
<Show when={status()}>

View File

@@ -22,7 +22,7 @@ import SimpleTabs from "../../../shared/tabs/SimpleTabs";
import ExtraArgs from "./container/ExtraArgs";
import WebhookUrl from "./container/WebhookUrl";
import RedeployOnBuild from "./container/RedeployOnBuild";
import TerminationSignals from "./container/TerminationSignals";
import TerminationSignals, { DefaultTerminationSignal, DefaultTerminationTimeout } from "./termination/TerminationSignals";
const Config: Component<{}> = () => {
const { deployment, reset, save, userCanUpdate } = useConfig();
@@ -42,7 +42,6 @@ const Config: Component<{}> = () => {
<Grid class="config-items scroller" placeItems="start center">
<Image />
<DockerAccount />
<TerminationSignals />
<Network />
<Restart />
<Env />
@@ -57,6 +56,19 @@ const Config: Component<{}> = () => {
</Grid>
),
},
{
title: "termination",
element: () => (
<Grid class="config-items" placeItems="start center" style={{ "margin-bottom": "" }}>
<TerminationSignals />
<DefaultTerminationSignal />
<DefaultTerminationTimeout />
<Show when={isMobile()}>
<div style={{ height: "1rem" }} />
</Show>
</Grid>
),
},
(userCanUpdate() || deployment.repo ? true : false) && {
title: "frontend",
element: () => (

View File

@@ -62,6 +62,8 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
// console.log("loading deployment");
client.get_deployment(params.id).then((deployment) =>
set({
...deployment,
_id: { $oid: params.id } as any,
name: deployment.deployment.name,
server_id: deployment.deployment.server_id,
permissions: deployment.deployment.permissions,
@@ -87,6 +89,10 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
repo_mount: deployment.deployment.repo_mount,
created_at: deployment.deployment.created_at,
updated_at: deployment.deployment.updated_at,
redeploy_on_build: deployment.deployment.redeploy_on_build,
term_signal_labels: deployment.deployment.term_signal_labels,
termination_signal: deployment.deployment.termination_signal,
termination_timeout: deployment.deployment.termination_timeout,
loaded: true,
updated: false,
updating: false,
@@ -108,11 +114,13 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const save = () => {
setDeployment("updating", true);
client.update_deployment(deployment).catch((e) => {
console.error(e);
pushNotification("bad", "update deployment failed");
setDeployment("updating", false);
});
client
.update_deployment(deployment)
.catch((e) => {
console.error(e);
pushNotification("bad", "update deployment failed");
setDeployment("updating", false);
});
};
let update_unsub = () => {};

View File

@@ -7,6 +7,8 @@ import { TERM_SIGNALS } from "../../../Deployment";
import { TerminationSignal } from "../../../../../types";
import Input from "../../../../shared/Input";
import Menu from "../../../../shared/menu/Menu";
import Selector from "../../../../shared/menu/Selector";
import { pushNotification } from "../../../../..";
const TerminationSignals: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
@@ -33,7 +35,7 @@ const TerminationSignals: Component<{}> = (p) => {
<Grid class="config-item shadow">
<Flex alignItems="center" justifyContent="space-between">
<h1>termination signals</h1>
<Show when={userCanUpdate()}>
<Show when={userCanUpdate() && signals_to_add().length > 0}>
<Menu
show={menuOpen()}
close={() => setMenuOpen(false)}
@@ -90,3 +92,65 @@ const TerminationSignals: Component<{}> = (p) => {
};
export default TerminationSignals;
export const DefaultTerminationSignal: Component<{}> = () => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const term_signal = () =>
deployment.termination_signal || TerminationSignal.SigTerm;
const selected = () => ({
signal: term_signal(),
label:
deployment.term_signal_labels?.find(
({ signal }) => signal === term_signal()
)?.label || "",
});
return (
<Show when={deployment.term_signal_labels?.length || 0 > 0}>
<Flex
class="config-item shadow"
alignItems="center"
justifyContent="space-between"
>
<h1>default termination signal</h1>
<Selector
disabled={!userCanUpdate()}
targetClass="blue"
selected={selected()}
items={deployment.term_signal_labels || []}
onSelect={({ signal }) => setDeployment("termination_signal", signal)}
itemMap={({ signal }) => signal}
/>
</Flex>
</Show>
);
};
export const DefaultTerminationTimeout: Component<{}> = () => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
return (
<Flex
class="config-item shadow"
alignItems="center"
justifyContent="space-between"
>
<h1>termination timeout</h1>
<div style={{ position: "relative" }}>
<Input
disabled={!userCanUpdate()}
style={{ width: "10rem" }}
placeholder="10"
value={deployment.termination_timeout}
onConfirm={(value) => {
const val = Number(value);
if (!isNaN(val)) {
setDeployment("termination_timeout", val);
} else {
pushNotification("bad", "timeout must be number");
}
}}
/>
<div class="dimmed" style={{ position: "absolute", right: "1rem", top: "50%", transform: "translateY(-50%)" }}>seconds</div>
</div>
</Flex>
);
};

View File

@@ -6,22 +6,24 @@ import {
onCleanup,
Show,
} from "solid-js";
import Convert from "ansi-to-html";
import { pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { DockerContainerState, Log as LogType } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import { combineClasses, sanitizeLog } from "../../../../util/helpers";
import { useBuffer, useLocalStorageToggle } from "../../../../util/hooks";
import Icon from "../../../shared/Icon";
import Flex from "../../../shared/layout/Flex";
import Grid from "../../../shared/layout/Grid";
import Selector from "../../../shared/menu/Selector";
import { useConfig } from "../config/Provider";
import s from "./log.module.scss";
const POLLING_RATE = 5000;
let interval = -1;
const convert = new Convert();
const Log: Component<{
log?: LogType;
logTail: number;
@@ -31,7 +33,6 @@ const Log: Component<{
}> = (p) => {
const { deployments } = useAppState();
const params = useParams();
const { userCanUpdate } = useConfig();
const deployment = () => deployments.get(params.id);
let ref: HTMLDivElement | undefined;
let ignore = false;
@@ -56,14 +57,15 @@ const Log: Component<{
}, 1500);
}
});
/// the return of this closure is used as the innerHTML of the <pre /> element
/// need to make sure this can't be used to inject some script into the page
const log = () => {
if (deployment()?.state === DockerContainerState.NotDeployed) {
return "not deployed";
} else {
return (
(p.error ? p.log?.stderr : p.log?.stdout) ||
`no${p.error ? " error" : ""} log`
);
const log = p.error ? p.log?.stderr : p.log?.stdout;
const sanitized = log && sanitizeLog(log);
return sanitized ? convert.toHtml(sanitized) : `no${p.error ? " error" : ""} log`;
}
};
const buffer = useBuffer(scrolled, 250);
@@ -145,7 +147,7 @@ const Log: Component<{
}
}}
>
<pre class={s.Log}>{log()}</pre>
<pre class={s.Log} innerHTML={log()} />
</div>
<Show when={buffer()}>
<button

View File

@@ -10,9 +10,6 @@
}
.Log {
white-space: pre-line;
overflow-wrap: anywhere;
tab-size: 2;
padding: 1rem;
margin-bottom: 40vh;
}

View File

@@ -0,0 +1,58 @@
import { Component, For, Show, createEffect, createResource, onCleanup } from "solid-js";
import Grid from "../../shared/layout/Grid";
import { useParams } from "@solidjs/router";
import { client } from "../../..";
import Flex from "../../shared/layout/Flex";
import { readableBytes, readableImageNameTag, readableTimestamp } from "../../../util/helpers";
import { useAppState } from "../../../state/StateProvider";
import { Operation } from "../../../types";
const Images: Component<{}> = (p) => {
const params = useParams();
const { ws } = useAppState();
const [images, { refetch }] = createResource(() => client.get_docker_images(params.id));
let unsub = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe([Operation.PruneImagesServer], (update) => {
if (update.target.id === params.id) {
refetch()
}
});
});
onCleanup(() => unsub());
createEffect(() => console.log(images()));
return (
<Grid class="config">
<Grid class="config-items">
<Show when={images()}>
<For each={images()}>
{(image) => {
const [name, tag] = readableImageNameTag(image.RepoTags, image.RepoDigests);
return (
<Flex
class="card light hover shadow"
alignItems="center"
justifyContent="space-between"
>
<Flex alignItems="center">
<h2>{name}</h2>
<h2 class="dimmed">{tag}</h2>
</Flex>
<Flex alignItems="center">
<div>{readableBytes(image.Size)}</div>
<div class="dimmed">
{readableTimestamp(image.Created * 1000)}
</div>
</Flex>
</Flex>
);
}}
</For>
</Show>
</Grid>
</Grid>
);
}
export default Images;

View File

@@ -9,6 +9,7 @@ import Config from "./config/Config";
import { ConfigProvider } from "./config/Provider";
import Info from "./Info";
import Permissions from "./Permissions";
import Images from "./Images";
const ServerTabs: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -31,6 +32,10 @@ const ServerTabs: Component<{}> = (p) => {
title: "info",
element: () => <Info />
},
server()?.status === ServerStatus.Ok && {
title: "images",
element: () => <Images />
},
user().admin && {
title: "permissions",
element: () => <Permissions />,

View File

@@ -9,10 +9,15 @@ import {
useContext,
} from "solid-js";
import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../../..";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Server, Operation, PermissionLevel, ServerStatus } from "../../../../types";
import {
Server,
Operation,
PermissionLevel,
ServerStatus,
} from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigServer = Server & { loaded: boolean; updated: boolean };
@@ -49,6 +54,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
client.get_server(params.id).then((server) => {
set({
...server.server,
_id: { $oid: params.id } as any,
loaded: true,
updated: false,
});
@@ -66,10 +72,15 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
createEffect(loadNetworks);
const save = () => {
client.update_server(server);
client
.update_server(server)
.catch((e) => {
console.error(e);
pushNotification("bad", "update server failed");
});
};
let unsub = () => {}
let unsub = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe([Operation.UpdateServer], (update) => {
@@ -94,7 +105,8 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const userCanUpdate = () =>
user().admin ||
servers.get(params.id)!.server.permissions![getId(user())] === PermissionLevel.Update;
servers.get(params.id)!.server.permissions![getId(user())] ===
PermissionLevel.Update;
const state = {
server,

View File

@@ -218,6 +218,8 @@ export function useUsernames() {
const load = async (userID: string) => {
if (userID === "github") {
set((s) => ({ ...s, [userID]: "github" }));
} else if (userID === "auto redeploy") {
set((s) => ({ ...s, [userID]: "auto redeploy" }));
} else {
const username = await client.get_username(userID);
set((s) => ({ ...s, [userID]: username }));

View File

@@ -26,6 +26,9 @@ code {
pre {
margin: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
tab-size: 2;
}
h1 {

View File

@@ -106,6 +106,8 @@ export interface Deployment {
skip_secret_interp?: boolean;
docker_run_args: DockerRunArgs;
term_signal_labels?: TerminationSignalLabel[];
termination_signal?: TerminationSignal;
termination_timeout?: number;
build_id?: string;
redeploy_on_build?: boolean;
build_version?: Version;

View File

@@ -45,7 +45,6 @@ import {
ModifyUserCreateServerBody,
ModifyUserEnabledBody,
PermissionsUpdateBody,
RenameDeploymentBody,
StopContainerQuery,
UpdateDescriptionBody,
} from "./client_types";
@@ -220,20 +219,39 @@ export class Client {
return this.post(`/api/deployment/${deployment_id}/pull`);
}
deploy_container(deployment_id: string, query?: StopContainerQuery): Promise<Update> {
return this.post(`/api/deployment/${deployment_id}/deploy${generateQuery(query as any)}`);
deploy_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return this.post(
`/api/deployment/${deployment_id}/deploy${generateQuery(query as any)}`
);
}
start_container(deployment_id: string): Promise<Update> {
return this.post(`/api/deployment/${deployment_id}/start_container`);
}
stop_container(deployment_id: string, query?: StopContainerQuery): Promise<Update> {
return this.post(`/api/deployment/${deployment_id}/stop_container${generateQuery(query as any)}`);
stop_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return this.post(
`/api/deployment/${deployment_id}/stop_container${generateQuery(
query as any
)}`
);
}
remove_container(deployment_id: string, query?: StopContainerQuery): Promise<Update> {
return this.post(`/api/deployment/${deployment_id}/remove_container${generateQuery(query as any)}`);
remove_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return this.post(
`/api/deployment/${deployment_id}/remove_container${generateQuery(
query as any
)}`
);
}
async download_container_log(
@@ -367,7 +385,15 @@ export class Client {
return this.post(`/api/server/${server_id}/networks/prune`);
}
get_docker_images(server_id: string): Promise<any[]> {
get_docker_images(server_id: string): Promise<
{
RepoTags: string[];
RepoDigests: string[];
Size: number;
Created: number;
Id: string;
}[]
> {
return this.get(`/api/server/${server_id}/images`);
}

View File

@@ -0,0 +1,622 @@
import axios from "axios";
import {
BuildStatsQuery,
BuildStatsResponse,
BuildVersionsQuery,
CopyBuildBody,
CopyDeploymentBody,
CreateBuildBody,
CreateDeploymentBody,
CreateGroupBody,
CreateProcedureBody,
CreateSecretBody,
CreateServerBody,
LoginOptions,
ModifyUserCreateBuildBody,
ModifyUserCreateServerBody,
ModifyUserEnabledBody,
PermissionsUpdateBody,
StopContainerQuery,
UpdateDescriptionBody,
} from "./client_types";
import { MONITOR_BASE_URL } from "..";
import {
AwsBuilderConfig,
BasicContainerInfo,
Build,
BuildActionState,
BuildVersionsReponse,
Deployment,
DeploymentActionState,
DeploymentWithContainerState,
DockerContainerStats,
Group,
HistoricalStatsQuery,
Log,
Operation,
Procedure,
Server,
ServerActionState,
ServerWithStatus,
SystemInformation,
SystemStats,
SystemStatsQuery,
SystemStatsRecord,
Update,
UpdateTarget,
User,
UserCredentials,
} from "../types";
import fileDownload from "js-file-download";
import { QueryObject, generateQuery } from "./helpers";
export function monitor_client(base_url: string, token: string | null) {
const state = {
base_url,
token: token as string | null,
login_options: undefined as LoginOptions | undefined,
monitor_title: undefined as string | undefined,
secrets_cache: {} as Record<string, string[]>,
github_accounts_cache: {} as Record<string, string[]>,
docker_accounts_cache: {} as Record<string, string[]>,
server_version_cache: {} as Record<string, string>,
};
const request = request_client(state);
const client = {
// ===========
// AUTH / USER
// ===========
async initialize() {
const [loginOptions, monitorTitle] = await Promise.all([
client.get_login_options(),
client.get_monitor_title(),
]);
state.login_options = loginOptions;
state.monitor_title = monitorTitle;
document.title = monitorTitle;
const params = new URLSearchParams(location.search);
const exchange_token = params.get("token");
if (exchange_token) {
history.replaceState({}, "", MONITOR_BASE_URL);
try {
const jwt = await client.exchange_for_jwt(exchange_token);
token = jwt;
localStorage.setItem("access_token", jwt);
} catch (error) {
console.warn(error);
}
}
},
get_login_options(): Promise<LoginOptions> {
return request.get("/auth/options");
},
async login(credentials: UserCredentials) {
const jwt: string = await request.post("/auth/local/login", credentials);
state.token = jwt;
localStorage.setItem("access_token", state.token);
return await client.get_user();
},
async signup(credentials: UserCredentials) {
const jwt: string = await request.post(
"/auth/local/create_user",
credentials
);
state.token = jwt;
localStorage.setItem("access_token", state.token);
return await client.get_user();
},
logout() {
localStorage.removeItem("access_token");
state.token = null;
},
async get_user(): Promise<User | false> {
if (state.token) {
try {
return await request.get("/api/user");
} catch (error: any) {
client.logout();
return false;
}
} else {
return false;
}
},
get_username(user_id: string): Promise<string> {
return request.get(`/api/username/${user_id}`);
},
// admin only
list_users(): Promise<User[]> {
return request.get("/api/users");
},
// admin only
get_user_by_id(user_id: string): Promise<User> {
return request.get(`/api/user/${user_id}`);
},
exchange_for_jwt(exchange_token: string): Promise<string> {
return request.post("/auth/exchange", { token: exchange_token });
},
create_api_secret(body: CreateSecretBody): Promise<string> {
return request.post("/api/secret/create", body);
},
delete_api_secret(name: string): Promise<undefined> {
return request.delete(`/api/secret/delete/${name}`);
},
// ====
// MISC
// ====
get_github_webhook_base_url(): Promise<string> {
return request.get("/api/github_webhook_base_url");
},
update_description(body: UpdateDescriptionBody): Promise<undefined> {
return request.post("/api/update_description", body);
},
get_monitor_title(): Promise<string> {
return request.get("/api/title");
},
list_updates(
offset: number,
target?: UpdateTarget,
show_builds?: boolean,
operations?: Operation[]
): Promise<Update[]> {
return request.get(
`/api/update/list${generateQuery({
offset,
type: target && target.type,
id: target && target.id,
show_builds,
operations: operations?.join(","),
})}`
);
},
// ==========
// DEPLOYMENT
// ==========
list_deployments(
query?: QueryObject
): Promise<DeploymentWithContainerState[]> {
return request.get("/api/deployment/list" + generateQuery(query));
},
get_deployment(id: string): Promise<DeploymentWithContainerState> {
return request.get(`/api/deployment/${id}`);
},
get_deployment_action_state(id: string): Promise<DeploymentActionState> {
return request.get(`/api/deployment/${id}/action_state`);
},
get_deployment_container_log(id: string, tail?: number): Promise<Log> {
return request.get(`/api/deployment/${id}/log${generateQuery({ tail })}`);
},
get_deployment_container_stats(id: string): Promise<DockerContainerStats> {
return request.get(`/api/deployment/${id}/stats`);
},
get_deployment_deployed_version(id: string): Promise<string> {
return request.get(`/api/deployment/${id}/deployed_version`);
},
create_deployment(body: CreateDeploymentBody): Promise<Deployment> {
return request.post("/api/deployment/create", body);
},
create_full_deployment(deployment: Deployment): Promise<Deployment> {
return request.post("/api/deployment/create_full", deployment);
},
copy_deployment(
target_id: string,
body: CopyDeploymentBody
): Promise<Deployment> {
return request.post(`/api/deployment/${target_id}/copy`, body);
},
delete_deployment(id: string): Promise<Deployment> {
return request.delete(`/api/deployment/${id}/delete`);
},
update_deployment(deployment: Deployment): Promise<Deployment> {
return request.patch("/api/deployment/update", deployment);
},
rename_deployment(deployment_id: string, new_name: string) {
return request.patch(`/api/deployment/${deployment_id}/rename`, {
new_name,
});
},
reclone_deployment(deployment_id: string): Promise<Update> {
return request.post(`/api/deployment/${deployment_id}/reclone`);
},
pull_deployment(deployment_id: string): Promise<Update> {
return request.post(`/api/deployment/${deployment_id}/pull`);
},
deploy_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return request.post(
`/api/deployment/${deployment_id}/deploy${generateQuery(query as any)}`
);
},
start_container(deployment_id: string): Promise<Update> {
return request.post(`/api/deployment/${deployment_id}/start_container`);
},
stop_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return request.post(
`/api/deployment/${deployment_id}/stop_container${generateQuery(
query as any
)}`
);
},
remove_container(
deployment_id: string,
query?: StopContainerQuery
): Promise<Update> {
return request.post(
`/api/deployment/${deployment_id}/remove_container${generateQuery(
query as any
)}`
);
},
async download_container_log(
id: string,
name: string,
error?: boolean | undefined
) {
const log = await client.get_deployment_container_log(id, 5000);
const date = new Date();
fileDownload(
(error ? log.stderr : log.stdout) || "no log",
`${name}-${error ? "error-" : ""}log-${date
.toLocaleDateString()
.replaceAll("/", "-")}.txt`
);
},
// ======
// SERVER
// ======
list_servers(query?: QueryObject): Promise<ServerWithStatus[]> {
return request.get("/api/server/list" + generateQuery(query));
},
get_server(server_id: string): Promise<ServerWithStatus> {
return request.get(`/api/server/${server_id}`);
},
get_server_action_state(id: string): Promise<ServerActionState> {
return request.get(`/api/server/${id}/action_state`);
},
get_server_github_accounts(id: string): Promise<string[]> {
return request.get(`/api/server/${id}/github_accounts`);
},
get_server_docker_accounts(id: string): Promise<string[]> {
return request.get(`/api/server/${id}/docker_accounts`);
},
get_server_available_secrets(id: string): Promise<string[]> {
return request.get(`/api/server/${id}/secrets`);
},
get_server_version(id: string): Promise<string> {
return request.get(`/api/server/${id}/version`);
},
get_server_system_info(id: string): Promise<SystemInformation> {
return request.get(`/api/server/${id}/system_information`);
},
create_server(body: CreateServerBody): Promise<Server> {
return request.post("/api/server/create", body);
},
create_full_server(server: Server): Promise<Server> {
return request.post("/api/server/create_full", server);
},
delete_server(id: string): Promise<Server> {
return request.delete(`/api/server/${id}/delete`);
},
update_server(server: Server): Promise<Server> {
return request.patch("/api/server/update", server);
},
get_server_stats(
server_id: string,
query?: SystemStatsQuery
): Promise<SystemStats> {
return request.get(
`/api/server/${server_id}/stats${generateQuery(query as any)}`
);
},
get_server_stats_history(
server_id: string,
query?: HistoricalStatsQuery
): Promise<SystemStatsRecord[]> {
return request.get(
`/api/server/${server_id}/stats/history${generateQuery(query as any)}`
);
},
get_server_stats_at_ts(
server_id: string,
ts: number
): Promise<SystemStatsRecord> {
return request.get(`/api/server/${server_id}/stats/at_ts?ts=${ts}`);
},
get_docker_networks(server_id: string): Promise<any[]> {
return request.get(`/api/server/${server_id}/networks`);
},
prune_docker_networks(server_id: string): Promise<Log> {
return request.post(`/api/server/${server_id}/networks/prune`);
},
get_docker_images(server_id: string): Promise<
{
RepoTags: string[];
RepoDigests: string[];
Size: number;
Created: number;
Id: string;
}[]
> {
return request.get(`/api/server/${server_id}/images`);
},
prune_docker_images(server_id: string): Promise<Log> {
return request.post(`/api/server/${server_id}/images/prune`);
},
get_docker_containers(server_id: string): Promise<BasicContainerInfo[]> {
return request.get(`/api/server/${server_id}/containers`);
},
prune_docker_containers(server_id: string): Promise<Log> {
return request.post(`/api/server/${server_id}/containers/prune`);
},
// =====
// BUILD
// =====
list_builds(query?: QueryObject): Promise<Build[]> {
return request.get("/api/build/list" + generateQuery(query));
},
get_build(build_id: string): Promise<Build> {
return request.get(`/api/build/${build_id}`);
},
get_build_action_state(id: string): Promise<BuildActionState> {
return request.get(`/api/build/${id}/action_state`);
},
get_build_versions(
id: string,
query?: BuildVersionsQuery
): Promise<BuildVersionsReponse[]> {
return request.get(
`/api/build/${id}/versions${generateQuery(query as any)}`
);
},
get_build_stats(query?: BuildStatsQuery): Promise<BuildStatsResponse> {
return request.get(`/api/build/stats${generateQuery(query as any)}`);
},
create_build(body: CreateBuildBody): Promise<Build> {
return request.post("/api/build/create", body);
},
create_full_build(build: Build): Promise<Build> {
return request.post("/api/build/create_full", build);
},
copy_build(target_id: string, body: CopyBuildBody): Promise<Build> {
return request.post(`/api/build/${target_id}/copy`, body);
},
delete_build(id: string): Promise<Build> {
return request.delete(`/api/build/${id}/delete`);
},
update_build(build: Build): Promise<Build> {
return request.patch("/api/build/update", build);
},
build(build_id: string): Promise<Update> {
return request.post(`/api/build/${build_id}/build`);
},
reclone_build(id: string): Promise<Update> {
return request.post(`/api/build/${id}/reclone`);
},
get_aws_builder_defaults(): Promise<AwsBuilderConfig> {
return request.get("/api/build/aws_builder_defaults");
},
get_docker_organizations(): Promise<string[]> {
return request.get("/api/build/docker_organizations");
},
// =========
// PROCEDURE
// =========
list_procedures(query?: QueryObject): Promise<Procedure[]> {
return request.get("/api/procedure/list" + generateQuery(query));
},
get_procedure(procedure_id: string): Promise<Procedure> {
return request.get(`/api/procedure/${procedure_id}`);
},
create_procedure(body: CreateProcedureBody): Promise<Procedure> {
return request.post("/api/procedure/create", body);
},
create_full_procedure(procedure: Procedure): Promise<Procedure> {
return request.post("/api/procedure/create_full", procedure);
},
delete_procedure(id: string): Promise<Procedure> {
return request.delete(`/api/procedure/${id}/delete`);
},
update_procedure(procedure: Procedure): Promise<Procedure> {
return request.patch("/api/procedure/update", procedure);
},
run_procedure(id: string): Promise<Update> {
return request.post(`/api/procedure/${id}/run`);
},
// =====
// GROUP
// =====
list_groups(query?: QueryObject): Promise<Group[]> {
return request.get("/api/group/list" + generateQuery(query));
},
get_group(group_id: string): Promise<Group> {
return request.get(`/api/group/${group_id}`);
},
create_group(body: CreateGroupBody): Promise<Group> {
return request.post("/api/group/create", body);
},
create_full_group(group: Group): Promise<Group> {
return request.post("/api/group/create_full", group);
},
delete_group(id: string): Promise<Group> {
return request.delete(`/api/group/${id}/delete`);
},
update_group(group: Group): Promise<Group> {
return request.patch("/api/group/update", group);
},
// ===========
// PERMISSIONS
// ===========
update_user_permissions_on_target(
body: PermissionsUpdateBody
): Promise<Update> {
return request.post("/api/permissions/update", body);
},
modify_user_enabled(body: ModifyUserEnabledBody): Promise<Update> {
return request.post("/api/permissions/modify_enabled", body);
},
modify_user_create_server_permissions(
body: ModifyUserCreateServerBody
): Promise<Update> {
return request.post("/api/permissions/modify_create_server", body);
},
modify_user_create_build_permissions(
body: ModifyUserCreateBuildBody
): Promise<Update> {
return request.post("/api/permissions/modify_create_build", body);
},
};
return client;
}
function request_client(state: { base_url: string; token: string | null }) {
return {
async get<R = any>(url: string): Promise<R> {
return await axios({
method: "get",
url: state.base_url + url,
headers: {
authorization: state.token ? `Bearer ${state.token}` : undefined,
},
}).then(({ data }) => data);
},
async post<B = any, R = any>(url: string, body?: B): Promise<R> {
return await axios({
method: "post",
url: state.base_url + url,
headers: {
authorization: `Bearer ${state.token}`,
},
data: body,
}).then(({ data }) => data);
},
async patch<B = any, R = any>(url: string, body: B): Promise<R> {
return await axios({
method: "patch",
url: state.base_url + url,
headers: {
authorization: `Bearer ${state.token}`,
},
data: body,
}).then(({ data }) => data);
},
async delete<R = any>(url: string): Promise<R> {
return await axios({
method: "delete",
url: state.base_url + url,
headers: {
authorization: `Bearer ${state.token}`,
},
}).then(({ data }) => data);
},
};
}
export function login_with_github() {
location.replace(`${MONITOR_BASE_URL}/auth/github/login`);
}
export function login_with_google() {
location.replace(`${MONITOR_BASE_URL}/auth/google/login`);
}

View File

@@ -1,17 +1,12 @@
import {
Build,
Deployment,
DeploymentWithContainerState,
DockerContainerState,
EnvironmentVar,
Server,
ServerStatus,
ServerWithStatus,
Timelength,
UpdateTarget,
User,
Version,
} from "../types";
import sanitizeHtml from "sanitize-html";
export function combineClasses(...classes: (string | false | undefined)[]) {
return classes.filter((c) => (c ? true : false)).join(" ");
@@ -23,6 +18,15 @@ export function inPx(num: number) {
export type QueryObject = Record<string, string | number | boolean | undefined>;
export function sanitizeLog(log: string) {
return sanitizeHtml(log, {
allowedTags: sanitizeHtml.defaults.allowedTags.filter(
(tag) => tag !== "script"
),
allowedAttributes: sanitizeHtml.defaults.allowedAttributes,
});
}
export function generateQuery(query?: QueryObject) {
if (query) {
const q = Object.keys(query)
@@ -251,3 +255,33 @@ export function readableUserType(user: User) {
return "local";
}
}
const KB = 1024;
const MB = 1024 ** 2;
const GB = 1024 ** 3;
export function readableBytes(bytes: number) {
if (bytes > GB) {
return `${(bytes / GB).toFixed(1)} GiB`;
} else if (bytes > MB) {
return `${(bytes / MB).toFixed(1)} MiB`;
} else {
return `${(bytes / KB).toFixed(1)} KiB`;
}
}
export function readableImageId(id: string) {
return id.replaceAll("sha256:", "").slice(0, 12);
}
export function readableImageNameTag(repoTags?: string[], repoDigests?: string[]): [string, string] {
if (repoTags && repoTags.length > 0) {
const [name, tag] = repoTags[0].split(":");
return [name, tag];
} else if (repoDigests && repoDigests.length > 0) {
const [name] = repoDigests[0].split("@");
return [name, "none"];
} else {
return ["none", "none"]
}
}

View File

@@ -447,6 +447,13 @@
dependencies:
"@tanstack/query-core" "4.26.0"
"@types/sanitize-html@^2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.9.0.tgz#5b609f7592de22ef80a0930c39670329753dca1b"
integrity sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==
dependencies:
htmlparser2 "^8.0.0"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -454,6 +461,13 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-to-html@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.7.2.tgz#a92c149e4184b571eb29a0135ca001a8e2d710cb"
integrity sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==
dependencies:
entities "^2.2.0"
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
@@ -580,16 +594,61 @@ debug@^4.1.0:
dependencies:
ms "2.1.2"
deepmerge@^4.2.2:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
electron-to-chromium@^1.4.251:
version "1.4.284"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
entities@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.16.3:
version "0.16.10"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.10.tgz#d485c28f1626a3f9c1796c952e4cd0561f0031bb"
@@ -628,6 +687,11 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
fancy-canvas@0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/fancy-canvas/-/fancy-canvas-0.2.2.tgz#33fd4976724169a1eda5015f515a2a1302d1ec91"
@@ -698,6 +762,16 @@ html-entities@2.3.2:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488"
integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==
htmlparser2@^8.0.0:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
entities "^4.4.0"
immutable@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.0.tgz#c91f09108ed7504c2f0faec7222f40178ff97b11"
@@ -734,6 +808,11 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
is-what@^4.1.8:
version "4.1.8"
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.8.tgz#0e2a8807fda30980ddb2571c79db3d209b14cbe4"
@@ -802,6 +881,11 @@ nanoid@^3.3.4:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
node-releases@^2.0.6:
version "2.0.8"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae"
@@ -812,6 +896,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
parse-srcset@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@@ -827,6 +916,15 @@ picomatch@^2.0.4, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss@^8.3.11:
version "8.4.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.20:
version "8.4.20"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56"
@@ -869,6 +967,18 @@ rollup@^3.7.0:
optionalDependencies:
fsevents "~2.3.2"
sanitize-html@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.10.0.tgz#74d28848dfcf72c39693139131895c78900ab452"
integrity sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==
dependencies:
deepmerge "^4.2.2"
escape-string-regexp "^4.0.0"
htmlparser2 "^8.0.0"
is-plain-object "^5.0.0"
parse-srcset "^1.0.2"
postcss "^8.3.11"
sass@^1.57.1:
version "1.57.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5"

View File

@@ -1,11 +1,11 @@
[package]
name = "db_client"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { package = "monitor_types", path = "../types" }
mungos = "0.3.8"
mungos = "0.3.19"
anyhow = "1.0"

View File

@@ -1,6 +1,9 @@
use anyhow::Context;
use mungos::{Collection, Mungos};
use types::{Action, Build, Deployment, Group, Procedure, Server, SystemStatsRecord, Update, User};
use types::{
Action, Build, Deployment, Group, PeripheryCommand, Procedure, Server, SystemStatsRecord,
Update, User,
};
pub async fn users_collection(mungos: &Mungos, db_name: &str) -> anyhow::Result<Collection<User>> {
let coll = mungos.collection(db_name, "users");
@@ -133,3 +136,17 @@ pub async fn server_stats_collection(
.context("failed at creating ts index")?;
Ok(coll)
}
pub async fn commands_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<PeripheryCommand>> {
let coll = mungos.collection(db_name, "commands");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("server_id")
.await
.context("failed at creating server_id index")?;
Ok(coll)
}

View File

@@ -1,13 +1,13 @@
use anyhow::{anyhow, Context};
use collections::{
actions_collection, builds_collection, deployments_collection, groups_collection,
procedures_collection, server_stats_collection, servers_collection, updates_collection,
users_collection,
actions_collection, builds_collection, commands_collection, deployments_collection,
groups_collection, procedures_collection, server_stats_collection, servers_collection,
updates_collection, users_collection,
};
use mungos::{Collection, Mungos};
use types::{
Action, Build, Deployment, Group, MongoConfig, PermissionLevel, Procedure, Server,
SystemStatsRecord, Update, User,
Action, Build, Deployment, Group, MongoConfig, PeripheryCommand, PermissionLevel, Procedure,
Server, SystemStatsRecord, Update, User,
};
mod collections;
@@ -20,6 +20,7 @@ pub struct DbClient {
pub procedures: Collection<Procedure>,
pub actions: Collection<Action>,
pub groups: Collection<Group>,
pub commands: Collection<PeripheryCommand>,
pub updates: Collection<Update>,
pub stats: Collection<SystemStatsRecord>,
}
@@ -61,6 +62,9 @@ impl DbClient {
stats: server_stats_collection(&mungos, db_name)
.await
.expect("failed to make stats collection"),
commands: commands_collection(&mungos, db_name)
.await
.expect("failed to make commands collection"),
}
}
@@ -197,4 +201,28 @@ impl DbClient {
.unwrap_or_default();
Ok(permissions)
}
pub async fn get_command(&self, command_id: &str) -> anyhow::Result<PeripheryCommand> {
let command = self
.commands
.find_one_by_id(command_id)
.await
.context(format!("failed at mongo query for command {command_id}"))?
.ok_or(anyhow!("command at {command_id} doesn't exist"))?;
Ok(command)
}
pub async fn get_user_permission_on_command(
&self,
user_id: &str,
command_id: &str,
) -> anyhow::Result<PermissionLevel> {
let permissions = *self
.get_command(command_id)
.await?
.permissions
.get(user_id)
.unwrap_or_default();
Ok(permissions)
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_helpers"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
authors = ["MoghTech"]
description = "helpers used as dependency for mogh tech monitor"
@@ -12,7 +12,4 @@ license = "GPL-3.0-or-later"
types = { package = "monitor_types", path = "../types" }
anyhow = "1.0"
axum = "0.6"
serde = "1.0"
serde_json = "1.0"
toml = "0.7"
rand = "0.8"

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_client"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
authors = ["MoghTech"]
description = "a client to interact with the monitor system"
@@ -9,10 +9,10 @@ license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
monitor_types = "0.3.2"
monitor_types = "0.3.4"
# monitor_types = { path = "../types" }
reqwest = { version = "0.11", features = ["json"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio-tungstenite = { version = "0.19", features=["native-tls"] }
tokio = { version = "1.25", features = ["full"] }
tokio-util = "0.7"
anyhow = "1.0"

View File

@@ -1,13 +1,13 @@
[package]
name = "periphery_client"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { package = "monitor_types", path = "../types" }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio-tungstenite = { version = "0.19", features=["native-tls"] }
tokio = "1.25"
reqwest = { version = "0.11", features = ["json"] }
serde = "1.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_types"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
authors = ["MoghTech"]
description = "types for the mogh tech monitor"
@@ -10,13 +10,12 @@ license = "GPL-3.0-or-later"
[dependencies]
serde = "1.0"
serde_derive = "1.0"
bson = "2.4"
bson = "2.6"
strum = "0.24"
strum_macros = "0.24"
diff-struct = "0.5"
bollard = "0.14.0"
derive_builder = "0.12"
typeshare = "1.0.0"
typeshare = "1.0.1"
chrono = "0.4"
anyhow = "1.0"

View File

@@ -46,11 +46,21 @@ pub struct Deployment {
#[diff(attr(#[serde(skip_serializing_if = "docker_run_args_diff_no_change")]))]
pub docker_run_args: DockerRunArgs,
#[serde(default)]
#[builder(default)]
#[serde(default = "default_term_signal_labels")]
#[builder(default = "vec![TerminationSignalLabel::default()]")]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub term_signal_labels: Vec<TerminationSignalLabel>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "termination_signal_diff_no_change")]))]
pub termination_signal: TerminationSignal,
#[serde(default = "default_termination_timeout")]
#[builder(default = "10")]
#[diff(attr(#[serde(skip_serializing_if = "i32_diff_no_change")]))]
pub termination_timeout: i32,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub build_id: Option<String>,
@@ -99,6 +109,14 @@ pub struct Deployment {
pub updated_at: String,
}
fn default_termination_timeout() -> i32 {
10
}
fn default_term_signal_labels() -> Vec<TerminationSignalLabel> {
vec![TerminationSignalLabel::default()]
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DeploymentWithContainerState {

View File

@@ -2,7 +2,7 @@ use diff::{Diff, OptionDiff, VecDiff};
use crate::{
deployment::{DockerRunArgsDiff, RestartModeDiff, TerminationSignalDiff},
TimelengthDiff,
CommandDiff, TimelengthDiff,
};
pub fn f64_diff_no_change(f64_diff: &f64) -> bool {
@@ -13,6 +13,10 @@ pub fn f32_diff_no_change(f32_diff: &f32) -> bool {
*f32_diff == 0.0
}
pub fn i32_diff_no_change(i32_diff: &i32) -> bool {
*i32_diff == 0
}
pub fn option_diff_no_change<T: Diff>(option_diff: &OptionDiff<T>) -> bool
where
<T as Diff>::Repr: PartialEq,
@@ -52,3 +56,7 @@ pub fn timelength_diff_no_change(timelength: &TimelengthDiff) -> bool {
pub fn termination_signal_diff_no_change(term_signal: &TerminationSignalDiff) -> bool {
term_signal == &TerminationSignalDiff::NoChange
}
pub fn command_diff_no_change(command: &CommandDiff) -> bool {
command.command.is_none() && command.path.is_none()
}

View File

@@ -17,6 +17,7 @@ mod config;
mod deployment;
mod diff;
mod group;
mod periphery_command;
mod procedure;
mod server;
mod update;
@@ -27,6 +28,7 @@ pub use build::*;
pub use config::*;
pub use deployment::*;
pub use group::*;
pub use periphery_command::*;
pub use procedure::*;
pub use server::*;
pub use update::*;
@@ -49,7 +51,7 @@ pub struct CloneArgs {
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Diff)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Diff)]
#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))]
pub struct Command {
#[serde(default)]
@@ -125,6 +127,12 @@ pub enum Operation {
UpdateProcedure,
DeleteProcedure,
// command
CreateCommand,
UpdateCommand,
DeleteCommand,
RunCommand,
// group
CreateGroup,
UpdateGroup,

View File

@@ -0,0 +1,58 @@
use bson::serde_helpers::hex_string_as_object_id;
use derive_builder::Builder;
use diff::Diff;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::{diff::command_diff_no_change, Command, PermissionsMap};
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default, Diff, Builder)]
#[diff(attr(#[derive(Debug, Serialize)]))]
pub struct PeripheryCommand {
#[serde(
default,
rename = "_id",
skip_serializing_if = "String::is_empty",
with = "hex_string_as_object_id"
)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
#[builder(setter(skip))]
pub id: String,
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub name: String, // must be formatted to be compat with docker
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub description: String,
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub server_id: String,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing)]))]
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "command_diff_no_change")]))]
pub command: Command,
#[serde(default, skip_serializing_if = "String::is_empty")]
#[diff(attr(#[serde(skip)]))]
#[builder(setter(skip))]
pub created_at: String,
#[serde(default)]
#[diff(attr(#[serde(skip)]))]
#[builder(setter(skip))]
pub updated_at: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct CommandActionState {
pub running: bool,
}

View File

@@ -1,6 +1,6 @@
use crate::{
Build, BuildActionState, CloneArgs, Deployment, DeploymentActionState, Group, PermissionLevel,
PermissionsMap, Procedure, Server, ServerActionState,
Build, BuildActionState, CloneArgs, CommandActionState, Deployment, DeploymentActionState,
Group, PeripheryCommand, PermissionLevel, PermissionsMap, Procedure, Server, ServerActionState,
};
pub trait Permissioned {
@@ -41,6 +41,12 @@ impl Permissioned for Group {
}
}
impl Permissioned for PeripheryCommand {
fn permissions_map(&self) -> &PermissionsMap {
&self.permissions
}
}
pub trait Busy {
fn busy(&self) -> bool;
}
@@ -70,6 +76,12 @@ impl Busy for BuildActionState {
}
}
impl Busy for CommandActionState {
fn busy(&self) -> bool {
self.running
}
}
impl From<&Deployment> for CloneArgs {
fn from(d: &Deployment) -> Self {
CloneArgs {

View File

@@ -75,6 +75,7 @@ pub enum UpdateTarget {
Server(String),
Procedure(String),
Group(String),
Command(String),
}
impl Default for UpdateTarget {

View File

@@ -1,9 +1,9 @@
[package]
name = "monitor_periphery"
version = "0.3.2"
version = "0.3.4"
edition = "2021"
authors = ["MoghTech"]
description = "monitor periphery binary"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
description = "monitor periphery client"
license = "GPL-3.0-or-later"
[[bin]]
@@ -15,23 +15,21 @@ path = "src/main.rs"
[dependencies]
helpers = { package = "monitor_helpers", path = "../lib/helpers" }
types = { package = "monitor_types", path = "../lib/types" }
run_command = { version = "0.0.5", features = ["async_tokio"] }
async_timing_util = "0.1.14"
tokio = { version = "1.26", features = ["full"] }
run_command = { version = "0.0.6", features = ["async_tokio"] }
tokio = { version = "1.28", features = ["full"] }
axum = { version = "0.6", features = ["ws"] }
tower = { version = "0.4", features = ["full"] }
clap = { version = "4.2", features = ["derive"] }
futures = "0.3"
dotenv = "0.15"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
bollard = "0.14.0"
anyhow = "1.0"
envy = "0.4"
sysinfo = "0.28"
toml = "0.7"
daemonize = "0.5.0"
clap = { version = "4.2", features = ["derive"] }
svi = "0.1.3"
sysinfo = "0.29"
async_timing_util = "0.1.14"
svi = "0.1.4"
merge_config_files = "0.1.3"
parse_csl = "0.1.0"

View File

@@ -11,21 +11,8 @@ use types::PeripheryConfig;
use crate::{HomeDirExtension, PeripheryConfigExtension};
#[derive(Parser)]
#[command(author = "mbecker20 <becker.maxh@gmail.com>")]
#[command(about = "monitor periphery client")]
#[command(author, about, version)]
pub struct Args {
/// Run this program as a system daemon
#[arg(short, long)]
pub daemon: bool,
/// Sets destination file of periphery stdout logs
#[arg(long, default_value = "~/.monitor/periphery.log.out")]
pub stdout: String,
/// Sets destination file of periphery stderr logs
#[arg(long, default_value = "~/.monitor/periphery.log.err")]
pub stderr: String,
/// Sets the path of a config file or directory to use. can use multiple times
#[arg(short, long)]
pub config_path: Option<Vec<String>>,
@@ -34,14 +21,18 @@ pub struct Args {
#[arg(long)]
pub config_keyword: Option<Vec<String>>,
#[arg(short, long)]
/// Merges nested configs, eg. secrets, docker_accounts, github_accounts
#[arg(long)]
pub merge_nested_config: bool,
/// Extends config arrays, eg. allowed_ips, passkeys
#[arg(long)]
pub extend_config_arrays: bool,
/// Sets the periphery home directory to use instead of $HOME
/// ~ will be replaced with this directory
#[arg(long)]
pub home_dir: Option<String>,
#[arg(short, long)]
version: bool,
}
#[derive(Deserialize)]
@@ -52,14 +43,10 @@ struct Env {
config_keywords: String,
}
pub fn load() -> (Args, u16, PeripheryConfigExtension, HomeDirExtension) {
pub fn load() -> (u16, PeripheryConfigExtension, HomeDirExtension) {
dotenv().ok();
let env: Env = envy::from_env().expect("failed to parse env");
let args = Args::parse();
if args.version {
println!("v{}", env!("CARGO_PKG_VERSION"));
std::process::exit(0)
}
let home_dir = get_home_dir(&args.home_dir);
let config_paths = args
.config_path
@@ -83,20 +70,19 @@ pub fn load() -> (Args, u16, PeripheryConfigExtension, HomeDirExtension) {
config_paths.clone(),
match_keywords,
args.merge_nested_config,
args.merge_nested_config,
args.extend_config_arrays,
)
.expect("failed at parsing config");
let _ = std::fs::create_dir(&config.repo_dir);
print_startup_log(config_paths, &args, &config);
print_startup_log(config_paths, &config);
(
args,
config.port,
Extension(Arc::new(config)),
Extension(Arc::new(home_dir)),
)
}
fn print_startup_log(config_paths: Vec<String>, args: &Args, config: &PeripheryConfig) {
fn print_startup_log(config_paths: Vec<String>, config: &PeripheryConfig) {
println!("\nconfig paths: {config_paths:?}");
let mut config_for_print = config.clone();
config_for_print.github_accounts = config_for_print
@@ -120,9 +106,6 @@ fn print_startup_log(config_paths: Vec<String>, args: &Args, config: &PeripheryC
.map(|_| "<SECRET>".to_string())
.collect();
println!("{config_for_print:#?}");
if args.daemon {
println!("daemon mode enabled");
}
println!("starting montior periphery on port {}\n", config.port);
}

View File

@@ -64,7 +64,22 @@ pub async fn stop_container(
time: Option<i32>,
) -> Log {
let command = stop_container_command(container_name, signal, time);
run_monitor_command("docker stop", command).await
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.len() > 0 {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
log
} else {
log
}
}
pub async fn stop_and_remove_container(
@@ -74,7 +89,23 @@ pub async fn stop_and_remove_container(
) -> Log {
let stop_command = stop_container_command(container_name, signal, time);
let command = format!("{stop_command} && docker container rm {container_name}");
run_monitor_command("docker stop and remove", command).await
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.len() > 0 {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
log
} else {
log
}
}
fn stop_container_command(

View File

@@ -1,10 +1,9 @@
// #![allow(unused)]
use std::{fs::File, net::SocketAddr, sync::Arc};
use std::{net::SocketAddr, sync::Arc};
use ::helpers::get_socket_addr;
use axum::Extension;
use daemonize::Daemonize;
use types::PeripheryConfig;
mod api;
@@ -15,19 +14,7 @@ type PeripheryConfigExtension = Extension<Arc<PeripheryConfig>>;
type HomeDirExtension = Extension<Arc<String>>;
fn main() -> anyhow::Result<()> {
let (args, port, config, home_dir) = config::load();
if args.daemon {
let stdout = File::create(args.stdout.replace("~", &home_dir))
.expect("failed to create stdout log file");
let stderr = File::create(args.stderr.replace("~", &home_dir))
.expect("failed to create stderr log file");
let daemon = Daemonize::new().stdout(stdout).stderr(stderr);
match daemon.start() {
Ok(_) => println!("monitor periphery"),
Err(e) => eprintln!("Error, {}", e),
}
}
let (port, config, home_dir) = config::load();
run_periphery_server(port, config, home_dir)?;

View File

@@ -17,74 +17,81 @@ use tests::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let monitor = config::load().await;
println!("\nstarting tests\n");
let start_ts = unix_timestamp_ms();
let server = monitor
.list_servers(None)
.await?
.pop()
.ok_or(anyhow!("no servers"))?;
let build = BuildBuilder::default()
.name("monitor_core".into())
.server_id(server.server.id.clone().into())
.repo("mbecker20/monitor".to_string().into())
.branch("main".to_string().into())
.docker_build_args(
DockerBuildArgs {
build_path: ".".into(),
dockerfile_path: "core/Dockerfile".to_string().into(),
..Default::default()
}
.into(),
)
.pre_build(
Command {
path: "frontend".into(),
command: "yarn && yarn build".into(),
}
.into(),
)
let d = DeploymentBuilder::default()
.name(String::from("test"))
.server_id(String::from("any"))
.build()?;
let build = monitor.create_full_build(&build).await?;
println!("{d:#?}");
println!("{build:#?}");
// let monitor = config::load().await;
let build_update = monitor.build(&build.id).await?;
// println!("\nstarting tests\n");
println!("{build_update:#?}");
// let start_ts = unix_timestamp_ms();
let deployment = DeploymentBuilder::default()
.name("monitor_core_1".into())
.server_id(server.server.id.clone())
.build_id(build.id.clone().into())
.docker_run_args(
DockerRunArgsBuilder::default()
.volumes(vec![Conversion {
local: "/home/max/.monitor/core.config.toml".into(),
container: "/config/config.toml".into(),
}])
.build()?,
)
.build()?;
// let server = monitor
// .list_servers(None)
// .await?
// .pop()
// .ok_or(anyhow!("no servers"))?;
let deployment = monitor.create_full_deployment(&deployment).await?;
// let build = BuildBuilder::default()
// .name("monitor_core".into())
// .server_id(server.server.id.clone().into())
// .repo("mbecker20/monitor".to_string().into())
// .branch("main".to_string().into())
// .docker_build_args(
// DockerBuildArgs {
// build_path: ".".into(),
// dockerfile_path: "core/Dockerfile".to_string().into(),
// ..Default::default()
// }
// .into(),
// )
// .pre_build(
// Command {
// path: "frontend".into(),
// command: "yarn && yarn build".into(),
// }
// .into(),
// )
// .build()?;
println!("{deployment:#?}");
// let build = monitor.create_full_build(&build).await?;
let deploy_update = monitor.deploy_container(&deployment.id).await?;
// println!("{build:#?}");
println!("{deploy_update:#?}");
// let build_update = monitor.build(&build.id).await?;
let update = test_aws_build(&monitor).await?;
// println!("{build_update:#?}");
let end_ts = unix_timestamp_ms();
let finished_in = (end_ts - start_ts) as f64 / 1000.0;
println!("\nfinished in {finished_in} s");
// let deployment = DeploymentBuilder::default()
// .name("monitor_core_1".into())
// .server_id(server.server.id.clone())
// .build_id(build.id.clone().into())
// .docker_run_args(
// DockerRunArgsBuilder::default()
// .volumes(vec![Conversion {
// local: "/home/max/.monitor/core.config.toml".into(),
// container: "/config/config.toml".into(),
// }])
// .build()?,
// )
// .build()?;
// let deployment = monitor.create_full_deployment(&deployment).await?;
// println!("{deployment:#?}");
// let deploy_update = monitor.deploy_container(&deployment.id).await?;
// println!("{deploy_update:#?}");
// let update = test_aws_build(&monitor).await?;
// let end_ts = unix_timestamp_ms();
// let finished_in = (end_ts - start_ts) as f64 / 1000.0;
// println!("\nfinished in {finished_in} s");
Ok(())
}