Compare commits

..

40 Commits

Author SHA1 Message Date
mbecker20
575aa62625 update versions to 0.1.16 2023-02-21 04:32:41 +00:00
mbecker20
ac88a2c4ed testing and fixes for aws build 2023-02-21 04:27:30 +00:00
mbecker20
f1dcb71a8a poll periphery on build instance to ensure connectivity before moving on 2023-02-20 22:56:22 +00:00
mbecker20
30d04bc201 support building on epheral ec2 2023-02-20 09:41:15 +00:00
mbecker20
33a00bb1a2 poll when instance running 2023-02-20 04:55:44 +00:00
mbecker20
ccca44ea89 start working on build instance spawn on aws 2023-02-20 01:19:07 +00:00
mbecker20
ae5f36fe51 0.1.15 use passkey for addition core - periphery auth layer 2023-02-17 18:29:13 +00:00
beckerinj
69ce1e4f36 start adding passkey auth to core periphery communication 2023-02-17 12:53:09 -05:00
mbecker20
6e444b9032 temp remove deployment state polling 2023-02-14 18:33:32 +00:00
beckerinj
73eff72da4 work on docs 2023-02-13 11:16:27 -05:00
beckerinj
698e3c214b opinionated 2023-02-12 14:48:28 -05:00
beckerinj
9da77667dc add next page 2023-02-12 04:14:06 -05:00
beckerinj
c30793fb8f to 2023-02-12 04:12:49 -05:00
beckerinj
84fdaab24d close intro better 2023-02-12 04:10:10 -05:00
beckerinj
cbd67bb609 back to table 2023-02-12 04:05:35 -05:00
beckerinj
00f58e9008 architecture 2023-02-12 03:59:26 -05:00
beckerinj
7738fab351 intro 2023-02-12 03:57:55 -05:00
beckerinj
06e8f6589b links 2023-02-12 00:55:14 -05:00
beckerinj
57d9287724 start docs 2023-02-12 00:51:05 -05:00
mbecker20
2cc65595ee log only polls when container is running 2023-02-11 20:29:25 +00:00
mbecker20
3dd2b97873 add polling for deployment state and logs 2023-02-11 20:17:13 +00:00
mbecker20
3c805ebbf7 fix deployment action build A tag styling 2023-02-11 19:36:36 +00:00
beckerinj
a854160018 Update periphery.config.example.toml 2023-02-08 13:40:00 -05:00
mbecker20
a99d9e5969 make update menu wider 2023-02-08 05:56:09 +00:00
mbecker20
813b6c1182 confirm menu 2023-02-08 05:37:27 +00:00
mbecker20
2958f9589b deployment / build delete wont fail if server disabled / unreachable. 2023-02-08 05:37:12 +00:00
beckerinj
69b4e26176 ConfirmMenuButton 2023-02-07 23:49:27 -05:00
beckerinj
78b00f139d delete server side effects 2023-02-07 23:23:46 -05:00
beckerinj
dc1e8de851 widen env 2023-02-07 23:23:18 -05:00
mbecker20
3187b335a3 cli 0.1.20 2023-02-07 20:12:20 +00:00
beckerinj
54b5a2b420 add logrotate to mongo startup 2023-02-07 15:10:36 -05:00
beckerinj
14c6bd00a8 use the scroller class for LogContainer 2023-02-02 16:14:11 -05:00
mbecker20
e9c3646450 0.1.14 fix deployment builder defaults 2023-01-30 17:45:34 +00:00
mbecker20
4f20257479 0.1.13 improve Builder structs with defaults 2023-01-30 17:24:03 +00:00
beckerinj
65749991de 0.1.12 MonitorClient::new_from_env 2023-01-30 11:50:48 -05:00
mbecker20
237a1d802d handle stuff when server disabled / unreachable 2023-01-28 09:03:00 +00:00
mbecker20
e4336f19f3 default copy server to curr server 2023-01-25 23:08:31 +00:00
mbecker20
c895e5e67f fix selector search behavior when using itemMap 2023-01-25 23:01:11 +00:00
mbecker20
4e4e210736 implement build / deployment copy 2023-01-25 22:53:27 +00:00
beckerinj
09dfc8faa3 cli 0.1.19 add Restart=on-failure to service unit file 2023-01-25 14:58:54 -05:00
79 changed files with 2208 additions and 615 deletions

622
Cargo.lock generated
View File

@@ -19,6 +19,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -75,6 +84,322 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3d1e2a1f1ab3ac6c4b884e37413eaa03eb9d901e4fc68ee8f5c1d49721680e"
dependencies = [
"aws-credential-types",
"aws-http",
"aws-sdk-sso",
"aws-sdk-sts",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-types",
"aws-types",
"bytes",
"hex",
"http",
"hyper",
"ring",
"time 0.3.17",
"tokio",
"tower",
"tracing",
"zeroize",
]
[[package]]
name = "aws-credential-types"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0696a0523a39a19087747e4dafda0362dc867531e3d72a3f195564c84e5e08"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
"tokio",
"tracing",
"zeroize",
]
[[package]]
name = "aws-endpoint"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80a4f935ab6a1919fbfd6102a80c4fccd9ff5f47f94ba154074afe1051903261"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
"aws-types",
"http",
"regex",
"tracing",
]
[[package]]
name = "aws-http"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82976ca4e426ee9ca3ffcf919d9b2c8d14d0cd80d43cc02173737a8f07f28d4d"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
"aws-smithy-types",
"aws-types",
"bytes",
"http",
"http-body",
"lazy_static",
"percent-encoding",
"pin-project-lite",
"tracing",
]
[[package]]
name = "aws-sdk-ec2"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b40ee2d853d8300a49513778beb79b1574ff9e9c94b30b1531bc0171d730ad64"
dependencies = [
"aws-credential-types",
"aws-endpoint",
"aws-http",
"aws-sig-auth",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-query",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
"bytes",
"fastrand",
"http",
"regex",
"tokio-stream",
"tower",
"tracing",
]
[[package]]
name = "aws-sdk-sso"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca0119bacf0c42f587506769390983223ba834e605f049babe514b2bd646dbb2"
dependencies = [
"aws-credential-types",
"aws-endpoint",
"aws-http",
"aws-sig-auth",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-types",
"aws-types",
"bytes",
"http",
"regex",
"tokio-stream",
"tower",
]
[[package]]
name = "aws-sdk-sts"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "270b6a33969ebfcb193512fbd5e8ee5306888ad6c6d5d775cdbfb2d50d94de26"
dependencies = [
"aws-credential-types",
"aws-endpoint",
"aws-http",
"aws-sig-auth",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-query",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
"bytes",
"http",
"regex",
"tower",
"tracing",
]
[[package]]
name = "aws-sig-auth"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "660a02a98ab1af83bd8d714afbab2d502ba9b18c49e7e4cddd6bf8837ff778cb"
dependencies = [
"aws-credential-types",
"aws-sigv4",
"aws-smithy-http",
"aws-types",
"http",
"tracing",
]
[[package]]
name = "aws-sigv4"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdaf11005b7444e6cd66f600d09861a3aeb6eb89a0f003c7c9820dbab2d15297"
dependencies = [
"aws-smithy-http",
"form_urlencoded",
"hex",
"hmac",
"http",
"once_cell",
"percent-encoding",
"regex",
"sha2",
"time 0.3.17",
"tracing",
]
[[package]]
name = "aws-smithy-async"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e8615bf58d144dec3fcdb5110941b84e904c68054cb74ed240b9588fc337a5"
dependencies = [
"futures-util",
"pin-project-lite",
"tokio",
"tokio-stream",
]
[[package]]
name = "aws-smithy-client"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c1df4c1d03e1ce299ae4e24c19d0f4cd8bebceac60828530e579977d70289a"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-types",
"bytes",
"fastrand",
"http",
"http-body",
"hyper",
"hyper-rustls",
"lazy_static",
"pin-project-lite",
"tokio",
"tower",
"tracing",
]
[[package]]
name = "aws-smithy-http"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78abf16f8667b9176737cfffd1dd4ad07d350ef5dba01d01fdec5f31265f7134"
dependencies = [
"aws-smithy-types",
"bytes",
"bytes-utils",
"futures-core",
"http",
"http-body",
"hyper",
"once_cell",
"percent-encoding",
"pin-project-lite",
"pin-utils",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "aws-smithy-http-tower"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d517ac2476efc1820228c2fdfdcb17d3bea8695558bd67584a62a47c12b41918"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
"bytes",
"http",
"http-body",
"pin-project-lite",
"tower",
"tracing",
]
[[package]]
name = "aws-smithy-json"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a23cc091168c5d969b150d7cc11a5a202bfa88dc36494e6659d2499b0cf227b"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-query"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92a1218021362bc1faa56648397b8cc4ac7631a3944e087d314d0187ef88d782"
dependencies = [
"aws-smithy-types",
"urlencoding",
]
[[package]]
name = "aws-smithy-types"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8d2056dc5f10094d5e753ac5c649e8996869f0649b641e470950151596db73"
dependencies = [
"base64-simd",
"itoa",
"num-integer",
"ryu",
"time 0.3.17",
]
[[package]]
name = "aws-smithy-xml"
version = "0.54.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a3029cbb4a49656456b3f2b34daee7f68dd93c61cc5d03fa90788cb1d25d5b4"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f15b34253b68cde08e39b0627cc6101bcca64351229484b4743392c035d057"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
"aws-smithy-types",
"http",
"rustc_version 0.4.0",
"tracing",
]
[[package]]
name = "axum"
version = "0.6.4"
@@ -130,9 +455,9 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d"
checksum = "51227033e4d3acad15c879092ac8a228532707b5db5ff2628f638334f63e1b7a"
dependencies = [
"axum",
"bytes",
@@ -181,14 +506,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bcrypt"
version = "0.13.0"
name = "base64-simd"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641"
checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5"
dependencies = [
"base64 0.13.1",
"simd-abstraction",
]
[[package]]
name = "bcrypt"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a"
dependencies = [
"base64 0.21.0",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
@@ -299,10 +634,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cc"
version = "1.0.78"
name = "bytes-utils"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9"
dependencies = [
"bytes",
"either",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
@@ -398,7 +743,7 @@ dependencies = [
[[package]]
name = "core"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"async_timing_util",
@@ -414,8 +759,8 @@ dependencies = [
"hex",
"hmac",
"jwt",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_helpers",
"monitor_types 0.1.16",
"mungos",
"periphery_client",
"serde",
@@ -520,9 +865,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e"
checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -532,9 +877,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200"
checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8"
dependencies = [
"cc",
"codespan-reporting",
@@ -547,15 +892,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea"
checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971"
[[package]]
name = "cxxbridge-macro"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e"
checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e"
dependencies = [
"proc-macro2",
"quote",
@@ -650,10 +995,10 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "db_client"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.16",
"mungos",
]
@@ -740,9 +1085,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "encoding_rs"
@@ -846,9 +1191,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -861,9 +1206,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -871,15 +1216,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -888,15 +1233,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-macro"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -905,21 +1250,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1103,6 +1448,21 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
dependencies = [
"http",
"hyper",
"log",
"rustls",
"rustls-native-certs",
"tokio",
"tokio-rustls",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
@@ -1453,7 +1813,7 @@ dependencies = [
"rand",
"rustc_version_runtime",
"rustls",
"rustls-pemfile",
"rustls-pemfile 0.3.0",
"serde",
"serde_bytes",
"serde_with",
@@ -1478,7 +1838,7 @@ dependencies = [
[[package]]
name = "monitor_cli"
version = "0.1.18"
version = "0.1.22"
dependencies = [
"async_timing_util",
"clap",
@@ -1494,11 +1854,12 @@ dependencies = [
[[package]]
name = "monitor_client"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"envy",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest",
"serde",
"serde_derive",
@@ -1510,47 +1871,30 @@ dependencies = [
[[package]]
name = "monitor_helpers"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"async_timing_util",
"aws-config",
"aws-sdk-ec2",
"axum",
"bollard",
"futures",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rand",
"run_command",
"serde",
"serde_derive",
"serde_json",
"toml",
]
[[package]]
name = "monitor_helpers"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b1ff7bac3697e1fc06044b669b029964b11182483ce3066e3996c5c0f66b57"
dependencies = [
"anyhow",
"async_timing_util",
"axum",
"bollard",
"futures",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.16",
"periphery_client",
"rand",
"run_command",
"serde",
"serde_derive",
"serde_json",
"tokio",
"toml",
]
[[package]]
name = "monitor_periphery"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"async_timing_util",
@@ -1561,8 +1905,8 @@ dependencies = [
"dotenv",
"envy",
"futures-util",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_helpers",
"monitor_types 0.1.16",
"run_command",
"serde",
"serde_derive",
@@ -1576,7 +1920,7 @@ dependencies = [
[[package]]
name = "monitor_types"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"bollard",
@@ -1593,9 +1937,9 @@ dependencies = [
[[package]]
name = "monitor_types"
version = "0.1.11"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31a6a8d9bf88d5f6f0ede8d35efa7b7018ebf4b62cf7b9d052c18eed185ca805"
checksum = "e4d1444216d1585f466d0591cedbdf84d1c810a4f45586362108219e7c943267"
dependencies = [
"anyhow",
"bollard",
@@ -1814,6 +2158,12 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "outref"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -1854,12 +2204,11 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "periphery_client"
version = "0.1.11"
version = "0.1.16"
dependencies = [
"anyhow",
"futures-util",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.16",
"reqwest",
"serde",
"serde_json",
@@ -2020,6 +2369,23 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@@ -2106,7 +2472,16 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.16",
]
[[package]]
@@ -2115,8 +2490,8 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f"
dependencies = [
"rustc_version",
"semver",
"rustc_version 0.2.3",
"semver 0.9.0",
]
[[package]]
@@ -2145,6 +2520,18 @@ dependencies = [
"webpki",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
dependencies = [
"openssl-probe",
"rustls-pemfile 1.0.2",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "0.3.0"
@@ -2154,6 +2541,15 @@ dependencies = [
"base64 0.13.1",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64 0.21.0",
]
[[package]]
name = "rustversion"
version = "1.0.11"
@@ -2199,9 +2595,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.8.1"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721"
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [
"bitflags",
"core-foundation",
@@ -2229,6 +2625,12 @@ dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "semver-parser"
version = "0.7.0"
@@ -2370,6 +2772,15 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-abstraction"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987"
dependencies = [
"outref",
]
[[package]]
name = "slab"
version = "0.4.7"
@@ -2614,9 +3025,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.24.2"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -2664,6 +3075,17 @@ dependencies = [
"webpki",
]
[[package]]
name = "tokio-stream"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.18.0"
@@ -2694,9 +3116,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85"
dependencies = [
"serde",
"serde_spanned",
@@ -2706,18 +3128,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.18.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa"
dependencies = [
"indexmap",
"nom8",
@@ -2793,9 +3215,21 @@ dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
@@ -3231,6 +3665,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "xmlparser"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
[[package]]
name = "zeroize"
version = "1.5.7"

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_cli"
version = "0.1.18"
version = "0.1.22"
edition = "2021"
authors = ["MoghTech"]
description = "monitor cli | tools to setup monitor system"
@@ -18,7 +18,7 @@ async_timing_util = "0.1.14"
rand = "0.8"
serde = "1.0"
serde_derive = "1.0"
toml = "0.6"
toml = "0.7"
run_command = "0.0.5"
colored = "2"
strum = "0.24"

View File

@@ -74,6 +74,7 @@ pub fn gen_core_config(sub_matches: &ArgMatches) {
local_auth: true,
github_oauth: Default::default(),
google_oauth: Default::default(),
aws: Default::default(),
mongo: MongoConfig {
uri: mongo_uri,
db_name: mongo_db_name,
@@ -81,6 +82,7 @@ pub fn gen_core_config(sub_matches: &ArgMatches) {
},
jwt_secret: generate_secret(40),
github_webhook_secret: generate_secret(30),
passkey: generate_secret(30),
};
write_to_toml(&path, &config);
@@ -176,7 +178,7 @@ pub fn start_mongo(sub_matches: &ArgMatches) {
}
}
let command = format!("docker stop {name} && docker container rm {name} && docker run -d --name {name} -p {port}:27017 --network {network} -v {mount}:/data/db{env} --restart {restart} mongo --quiet");
let command = format!("docker stop {name} && docker container rm {name} && docker run -d --name {name} -p {port}:27017 --network {network} -v {mount}:/data/db{env} --restart {restart} --log-opt max-size=15m --log-opt max-file=3 mongo --quiet");
let output = run_command_pipe_to_terminal(&command);
@@ -318,9 +320,10 @@ pub fn gen_periphery_config(sub_matches: &ArgMatches) {
let config = PeripheryConfig {
port,
repo_dir,
stats_polling_rate,
allowed_ips,
repo_dir,
passkeys: vec![],
secrets: Default::default(),
github_accounts: Default::default(),
docker_accounts: Default::default(),
@@ -588,6 +591,7 @@ Description=agent to connect with monitor core
[Service]
ExecStart={home}/.monitor/bin/periphery --config-path {config_path} --home-dir {home}
Restart=on-failure
TimeoutStartSec=0
[Install]

View File

@@ -21,7 +21,6 @@ pub struct CoreConfig {
#[serde(default)]
pub keep_stats_for_days: u64, // 0 means never prune
// jwt config
pub jwt_secret: String,
#[serde(default = "default_jwt_valid_for")]
pub jwt_valid_for: Timelength,
@@ -32,20 +31,25 @@ pub struct CoreConfig {
// used to verify validity from github webhooks
pub github_webhook_secret: String,
// sent in auth header with req to periphery
pub passkey: String,
// integration with slack app
pub slack_url: Option<String>,
// enable login with local auth
pub local_auth: bool,
// github integration
pub mongo: MongoConfig,
#[serde(default)]
pub github_oauth: OauthCredentials,
// google integration
#[serde(default)]
pub google_oauth: OauthCredentials,
// mongo config
pub mongo: MongoConfig,
#[serde(default)]
pub aws: AwsBuilderConfig,
}
fn default_core_port() -> u16 {
@@ -83,6 +87,37 @@ fn default_core_mongo_db_name() -> String {
"monitor".to_string()
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct AwsBuilderConfig {
pub access_key_id: String,
pub secret_access_key: String,
pub default_ami_id: String,
pub default_subnet_id: String,
pub default_key_pair_name: String,
#[serde(default = "default_aws_region")]
pub default_region: String,
#[serde(default = "default_volume_gb")]
pub default_volume_gb: i32,
#[serde(default = "default_instance_type")]
pub default_instance_type: String,
#[serde(default)]
pub default_security_group_ids: Vec<String>,
#[serde(default)]
pub default_assign_public_ip: bool,
}
fn default_aws_region() -> String {
String::from("us-east-1")
}
fn default_volume_gb() -> i32 {
8
}
fn default_instance_type() -> String {
String::from("m5.2xlarge")
}
pub type GithubUsername = String;
pub type GithubToken = String;
pub type GithubAccounts = HashMap<GithubUsername, GithubToken>;
@@ -104,6 +139,8 @@ pub struct PeripheryConfig {
#[serde(default)]
pub allowed_ips: Vec<IpAddr>,
#[serde(default)]
pub passkeys: Vec<String>,
#[serde(default)]
pub secrets: SecretsMap,
#[serde(default)]
pub github_accounts: GithubAccounts,

View File

@@ -22,17 +22,34 @@ slack_url = "your_slack_app_webhook_url"
# token that has to be given to github during webhook config as the Secret
github_webhook_secret = "your_random_webhook_secret"
# token used to authenticate core requests to periphery
passkey = "your_random_passkey"
# can be 30-sec, 1-min, 2-min, 5-min
monitoring_interval = "1-min"
# allow or deny user login with username / password
local_auth = true
[aws]
access_key_id = "your_aws_key_id"
secret_access_key = "your_aws_secret_key"
default_region = "us-east-1"
default_ami_id = "your_periphery_ami"
default_key_pair_name = "your_default_key_pair_name"
default_instance_type = "m5.2xlarge"
default_volume_gb = 8
default_subnet_id = "your_default_subnet_id"
default_security_group_ids = ["sg_id_1", "sg_id_2"]
default_assign_public_ip = false
[github_oauth]
enabled = true
id = "your_github_client_id"
secret = "your_github_client_secret"
[google_oauth]
enabled = true
id = "your_google_client_id"
secret = "your_google_client_secret"

View File

@@ -1,7 +1,8 @@
port = 9001 # optional. 9001 is default
port = 8000 # optional. 8000 is default
repo_dir = "/repos" # optional. /repos is default. no reason to change if running the docker container, just mount your desired repo dir to /repos in the container
stats_polling_rate = "5-sec" # optional. 5-sec is default. can use 1-sec, 5-sec, 10-sec, 30-sec, 1-min. controls granularity of system stats recorded
allowed_ips = ["127.0.0.1"] # optional. default is empty, which will not block any request by ip.
passkeys = ["abcdefghijk"] # optional. default is empty, which will not require any passkey to be passed by core.
[secrets] # optional. can inject these values into your deployments configuration.
secret_variable = "secret_value"
@@ -12,4 +13,4 @@ github_username2 = "github_token2"
[docker_accounts] # optional
docker_username1 = "docker_token1"
docker_username2 = "docker_token2"
docker_username2 = "docker_token2"

View File

@@ -1,23 +1,21 @@
[package]
name = "core"
version = "0.1.11"
version = "0.1.16"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# helpers = { package = "monitor_helpers", path = "../lib/helpers" }
# types = { package = "monitor_types", path = "../lib/types" }
helpers = { package = "monitor_helpers", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.11" }
helpers = { package = "monitor_helpers", path = "../lib/helpers" }
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.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio-util = "0.7"
axum = { version = "0.6", features = ["ws", "json"] }
axum-extra = { version = "0.4", features = ["spa"] }
axum-extra = { version = "0.5", features = ["spa"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.3", features = ["cors"] }
slack = { package = "slack_client_rs", version = "0.0.8" }
@@ -28,7 +26,7 @@ serde_json = "1.0"
dotenv = "0.15"
envy = "0.4"
anyhow = "1.0"
bcrypt = "0.13"
bcrypt = "0.14"
jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"

View File

@@ -1,18 +1,23 @@
use std::time::Duration;
use anyhow::{anyhow, Context};
use diff::Diff;
use helpers::{all_logs_success, to_monitor_name};
use helpers::{
all_logs_success,
aws::{self, create_ec2_client, create_instance_with_ami, terminate_ec2_instance, Ec2Instance},
to_monitor_name,
};
use mungos::{doc, to_bson};
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget,
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget, Version,
};
use crate::{
auth::RequestUser,
helpers::{any_option_diff_is_some, option_diff_is_some},
state::State,
};
use crate::{auth::RequestUser, state::State};
const BUILDER_POLL_RATE_SECS: u64 = 2;
const BUILDER_POLL_MAX_TRIES: usize = 30;
impl State {
pub async fn get_build_check_permissions(
@@ -39,18 +44,13 @@ impl State {
}
}
pub async fn create_build(
&self,
name: &str,
server_id: String,
user: &RequestUser,
) -> anyhow::Result<Build> {
self.get_server_check_permissions(&server_id, user, PermissionLevel::Update)
.await?;
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"));
}
let start_ts = monitor_timestamp();
let build = Build {
name: to_monitor_name(name),
server_id,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
@@ -84,10 +84,7 @@ impl State {
mut build: Build,
user: &RequestUser,
) -> anyhow::Result<Build> {
build.id = self
.create_build(&build.name, build.server_id.clone(), user)
.await?
.id;
build.id = self.create_build(&build.name, user).await?.id;
let build = self.update_build(build, user).await?;
Ok(build)
}
@@ -96,14 +93,13 @@ impl State {
&self,
target_id: &str,
new_name: String,
new_server_id: String,
user: &RequestUser,
) -> anyhow::Result<Build> {
let mut build = self
.get_build_check_permissions(target_id, user, PermissionLevel::Update)
.await?;
build.name = new_name;
build.server_id = new_server_id;
build.version = Version::default();
let build = self.create_full_build(build, user).await?;
Ok(build)
}
@@ -116,12 +112,6 @@ impl State {
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&build.server_id).await?;
let delete_repo_log = self
.periphery
.delete_repo(&server, &build.name)
.await
.context("failed at deleting repo")?;
self.db.builds.delete_one(build_id).await?;
let update = Update {
target: UpdateTarget::Build(build_id.to_string()),
@@ -129,13 +119,10 @@ impl State {
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
logs: vec![
delete_repo_log,
Log::simple(
"delete build",
format!("deleted build {} on server {}", build.name, server.name),
),
],
logs: vec![Log::simple(
"delete build",
format!("deleted build {}", build.name),
)],
success: true,
..Default::default()
};
@@ -179,7 +166,6 @@ impl State {
// none of these should be changed through this method
new_build.name = current_build.name.clone();
new_build.permissions = current_build.permissions.clone();
new_build.server_id = current_build.server_id.clone();
new_build.last_built_at = String::new();
new_build.created_at = current_build.created_at.clone();
new_build.updated_at = start_ts.clone();
@@ -192,41 +178,42 @@ impl State {
let diff = current_build.diff(&new_build);
let mut update = Update {
let update = Update {
operation: Operation::UpdateBuild,
target: UpdateTarget::Build(new_build.id.clone()),
start_ts,
status: UpdateStatus::InProgress,
status: UpdateStatus::Complete,
logs: vec![Log::simple(
"build update",
serde_json::to_string_pretty(&diff).unwrap(),
)],
operator: user.id.clone(),
end_ts: Some(monitor_timestamp()),
success: true,
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
// update.id = self.add_update(update.clone()).await?;
if any_option_diff_is_some(&[&diff.repo, &diff.branch, &diff.github_account])
|| option_diff_is_some(&diff.on_clone)
{
let server = self.db.get_server(&current_build.server_id).await?;
match self.periphery.clone_repo(&server, &new_build).await {
Ok(clone_logs) => {
update.logs.extend(clone_logs);
}
Err(e) => update
.logs
.push(Log::error("cloning repo", format!("{e:#?}"))),
}
}
// if any_option_diff_is_some(&[&diff.repo, &diff.branch, &diff.github_account])
// || option_diff_is_some(&diff.on_clone)
// {
// let server = self.db.get_server(&current_build.server_id).await?;
// match self.periphery.clone_repo(&server, &new_build).await {
// Ok(clone_logs) => {
// update.logs.extend(clone_logs);
// }
// Err(e) => update
// .logs
// .push(Log::error("cloning repo", format!("{e:#?}"))),
// }
// }
update.end_ts = Some(monitor_timestamp());
update.success = all_logs_success(&update.logs);
update.status = UpdateStatus::Complete;
// update.end_ts = Some(monitor_timestamp());
// update.success = all_logs_success(&update.logs);
// update.status = UpdateStatus::Complete;
self.update_update(update).await?;
self.add_update(update).await?;
Ok(new_build)
}
@@ -253,10 +240,7 @@ impl State {
let mut build = self
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let server = self.db.get_server(&build.server_id).await?;
build.version.increment();
let mut update = Update {
target: UpdateTarget::Build(build_id.to_string()),
operation: Operation::BuildBuild,
@@ -267,12 +251,95 @@ impl State {
version: build.version.clone().into(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let (server, aws_client) = if let Some(server_id) = &build.server_id {
let server = self.db.get_server(server_id).await;
if let Err(e) = server {
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.success = false;
update
.logs
.push(Log::error("get build server", format!("{e:#?}")));
self.update_update(update.clone()).await?;
return Err(e);
}
let server = Ec2Instance {
instance_id: String::new(),
server: server.unwrap(),
};
(server, None)
} else if build.aws_config.is_some() {
let start_ts = monitor_timestamp();
let res = self.create_ec2_instance_for_build(&build).await;
if let Err(e) = res {
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.success = false;
update.logs.push(Log {
stage: "start build server".to_string(),
stderr: format!("{e:#?}"),
success: false,
start_ts,
end_ts: monitor_timestamp(),
..Default::default()
});
self.update_update(update).await?;
return Err(e);
}
let (server, aws_client, log) = res.unwrap();
update.logs.push(log);
self.update_update(update.clone()).await?;
(server, aws_client)
} else {
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.success = false;
update.logs.push(Log::error(
"start build",
"build has neither server_id nor aws_config attached".to_string(),
));
self.update_update(update).await?;
return Err(anyhow!(
"build has neither server_id or aws_config attached"
));
};
let clone_success = match self.periphery.clone_repo(&server.server, &build).await {
Ok(clone_logs) => {
update.logs.extend(clone_logs);
true
}
Err(e) => {
update
.logs
.push(Log::error("clone repo", format!("{e:#?}")));
false
}
};
if !clone_success {
let _ = self
.periphery
.delete_repo(&server.server, &build.name)
.await;
if let Some(aws_client) = aws_client {
self.terminate_ec2_instance(aws_client, &server, &mut update)
.await;
}
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.success = false;
self.update_update(update.clone()).await?;
return Ok(update);
}
self.update_update(update.clone()).await?;
let build_logs = match self
.periphery
.build(&server, &build)
.build(&server.server, &build)
.await
.context("failed at call to periphery to build")
{
@@ -282,9 +349,9 @@ impl State {
match build_logs {
Some(logs) => {
let success = all_logs_success(&logs);
update.logs.extend(logs);
update.success = all_logs_success(&update.logs);
if update.success {
if success {
let _ = self
.db
.builds
@@ -305,68 +372,18 @@ impl State {
.push(Log::error("build", "builder busy".to_string()));
}
}
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
self.update_update(update.clone()).await?;
Ok(update)
}
let _ = self
.periphery
.delete_repo(&server.server, &build.name)
.await;
pub async fn reclone_build(
&self,
build_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.build_busy(build_id).await {
return Err(anyhow!("build busy"));
if let Some(aws_client) = aws_client {
self.terminate_ec2_instance(aws_client, &server, &mut update)
.await;
}
{
let mut lock = self.build_action_states.lock().await;
let entry = lock.entry(build_id.to_string()).or_default();
entry.recloning = true;
}
let res = self.reclone_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.recloning = false;
}
res
}
async fn reclone_build_inner(
&self,
build_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
let build = self
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let server = self.db.get_server(&build.server_id).await?;
let mut update = Update {
target: UpdateTarget::Build(build_id.to_string()),
operation: Operation::RecloneBuild,
start_ts: monitor_timestamp(),
status: UpdateStatus::InProgress,
operator: user.id.clone(),
success: true,
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
update.success = match self.periphery.clone_repo(&server, &build).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());
@@ -374,4 +391,170 @@ impl State {
Ok(update)
}
async fn create_ec2_instance_for_build(
&self,
build: &Build,
) -> anyhow::Result<(Ec2Instance, Option<aws::Client>, Log)> {
if build.aws_config.is_none() {
return Err(anyhow!("build has no aws_config attached"));
}
let start_ts = monitor_timestamp();
let aws_config = build.aws_config.as_ref().unwrap();
let region = aws_config
.region
.as_ref()
.unwrap_or(&self.config.aws.default_region)
.to_string();
let aws_client = create_ec2_client(
region,
&self.config.aws.access_key_id,
self.config.aws.secret_access_key.clone(),
)
.await;
let ami_id = aws_config
.ami_id
.as_ref()
.unwrap_or(&self.config.aws.default_ami_id);
let instance_type = aws_config
.instance_type
.as_ref()
.unwrap_or(&self.config.aws.default_instance_type);
let subnet_id = aws_config
.subnet_id
.as_ref()
.unwrap_or(&self.config.aws.default_subnet_id);
let security_group_ids = aws_config
.security_group_ids
.as_ref()
.unwrap_or(&self.config.aws.default_security_group_ids)
.to_owned();
let readable_sec_group_ids = security_group_ids.join(", ");
let volume_size_gb = *aws_config
.volume_gb
.as_ref()
.unwrap_or(&self.config.aws.default_volume_gb);
let key_pair_name = aws_config
.key_pair_name
.as_ref()
.unwrap_or(&self.config.aws.default_key_pair_name);
let assign_public_ip = *aws_config
.assign_public_ip
.as_ref()
.unwrap_or(&self.config.aws.default_assign_public_ip);
let instance = create_instance_with_ami(
&aws_client,
&format!("BUILDER-{}-v{}", build.name, build.version.to_string()),
ami_id,
instance_type,
subnet_id,
security_group_ids,
volume_size_gb,
key_pair_name,
assign_public_ip,
)
.await?;
let mut res = Ok(String::new());
for _ in 0..BUILDER_POLL_MAX_TRIES {
let status = self.periphery.health_check(&instance.server).await;
if let Ok(_) = status {
let instance_id = &instance.instance_id;
let log = Log {
stage: "start build instance".to_string(),
success: true,
stdout: format!("instance id: {instance_id}\nami id: {ami_id}\ninstance type: {instance_type}\nvolume size: {volume_size_gb} GB\nsubnet id: {subnet_id}\nsecurity groups: {readable_sec_group_ids}"),
start_ts,
end_ts: monitor_timestamp(),
..Default::default()
};
return Ok((instance, Some(aws_client), log));
}
res = status;
tokio::time::sleep(Duration::from_secs(BUILDER_POLL_RATE_SECS)).await;
}
let _ = terminate_ec2_instance(&aws_client, &instance.instance_id).await;
Err(anyhow!("unable to reach periphery agent on build server\n{res:#?}"))
}
async fn terminate_ec2_instance(
&self,
aws_client: aws::Client,
server: &Ec2Instance,
update: &mut Update,
) {
let res = terminate_ec2_instance(&aws_client, &server.instance_id).await;
if let Err(e) = res {
update
.logs
.push(Log::error("terminate instance", format!("{e:#?}")))
} else {
update.logs.push(Log::simple(
"terminate instance",
format!("terminate instance id {}", server.instance_id),
))
}
}
// pub async fn reclone_build(
// &self,
// build_id: &str,
// user: &RequestUser,
// ) -> anyhow::Result<Update> {
// if self.build_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.recloning = true;
// }
// let res = self.reclone_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.recloning = false;
// }
// res
// }
// async fn reclone_build_inner(
// &self,
// build_id: &str,
// user: &RequestUser,
// ) -> anyhow::Result<Update> {
// let build = self
// .get_build_check_permissions(build_id, user, PermissionLevel::Update)
// .await?;
// let server = self.db.get_server(&build.server_id).await?;
// let mut update = Update {
// target: UpdateTarget::Build(build_id.to_string()),
// operation: Operation::RecloneBuild,
// start_ts: monitor_timestamp(),
// status: UpdateStatus::InProgress,
// operator: user.id.clone(),
// success: true,
// ..Default::default()
// };
// update.id = self.add_update(update.clone()).await?;
// update.success = match self.periphery.clone_repo(&server, &build).await {
// Ok(clone_logs) => {
// update.logs.extend(clone_logs);
// true
// }
// Err(e) => {
// update
// .logs
// .push(Log::error("clone repo", format!("{e:#?}")));
// false
// }
// };
// update.status = UpdateStatus::Complete;
// update.end_ts = Some(monitor_timestamp());
// self.update_update(update.clone()).await?;
// Ok(update)
// }
}

View File

@@ -119,10 +119,14 @@ impl State {
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&deployment.server_id).await?;
let log = self
let log = match self
.periphery
.container_remove(&server, &deployment.name)
.await?;
.await
{
Ok(log) => log,
Err(e) => Log::error("destroy container", format!("{e:#?}")),
};
self.db
.deployments
.delete_one(deployment_id)

View File

@@ -125,7 +125,7 @@ impl State {
} in &new_procedure.stages
{
match operation {
BuildBuild | RecloneBuild => {
BuildBuild => {
self.get_build_check_permissions(&target_id, user, PermissionLevel::Execute)
.await?;
}
@@ -253,13 +253,6 @@ impl State {
.context(format!("failed at build (id: {target_id})"))?;
updates.push(update);
}
RecloneBuild => {
let update = self
.reclone_build(&target_id, user)
.await
.context(format!("failed at reclone build (id: {target_id})"))?;
updates.push(update);
}
// server
PruneImagesServer => {
let update = self.prune_images(&target_id, user).await.context(format!(

View File

@@ -1,6 +1,8 @@
use anyhow::{anyhow, Context};
use diff::Diff;
use futures_util::future::join_all;
use helpers::to_monitor_name;
use mungos::doc;
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
@@ -102,21 +104,59 @@ impl State {
.get_server_check_permissions(server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
self.db.servers.delete_one(&server_id).await?;
let update = Update {
let mut update = Update {
target: UpdateTarget::Server(server_id.to_string()),
operation: Operation::DeleteServer,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
logs: vec![Log::simple(
"delete server",
format!("deleted server {}", server.name),
)],
success: true,
status: UpdateStatus::InProgress,
..Default::default()
};
self.add_update(update).await?;
update.id = self.add_update(update.clone()).await?;
let res = {
let delete_deployments = self
.db
.deployments
.get_some(doc! { "server_id": server_id }, None)
.await?
.into_iter()
.map(|d| async move { self.delete_deployment(&d.id, user).await });
let delete_builds = self
.db
.builds
.get_some(doc! { "server_id": server_id }, None)
.await?
.into_iter()
.map(|d| async move { self.delete_deployment(&d.id, user).await });
let update_groups = self
.db
.groups
.update_many(doc! {}, doc! { "$pull": { "servers": server_id } });
let (dep_res, build_res, group_res) = tokio::join!(
join_all(delete_deployments),
join_all(delete_builds),
update_groups
);
dep_res.into_iter().collect::<anyhow::Result<Vec<_>>>()?;
build_res.into_iter().collect::<anyhow::Result<Vec<_>>>()?;
group_res?;
self.db.servers.delete_one(&server_id).await?;
anyhow::Ok(())
};
let log = match res {
Ok(_) => Log::simple("delete server", format!("deleted server {}", server.name)),
Err(e) => Log::error("delete server", format!("failed to delete server\n{e:#?}")),
};
update.end_ts = Some(monitor_timestamp());
update.status = UpdateStatus::Complete;
update.success = log.success;
update.logs.push(log);
self.update_update(update).await?;
Ok(server)
}

View File

@@ -31,14 +31,12 @@ struct BuildId {
#[derive(Serialize, Deserialize)]
struct CreateBuildBody {
name: String,
server_id: String,
}
#[typeshare]
#[derive(Serialize, Deserialize)]
struct CopyBuildBody {
name: String,
server_id: String,
}
#[typeshare]
@@ -88,7 +86,7 @@ pub fn router() -> Router {
Extension(user): RequestUserExtension,
Json(build): Json<CreateBuildBody>| async move {
let build = state
.create_build(&build.name, build.server_id, &user)
.create_build(&build.name, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(build))
@@ -121,7 +119,7 @@ pub fn router() -> Router {
Json(build): Json<CopyBuildBody>| async move {
let build = spawn_request_action(async move {
state
.copy_build(&id, build.name, build.server_id, &user)
.copy_build(&id, build.name, &user)
.await
.map_err(handle_anyhow_error)
})
@@ -181,23 +179,6 @@ pub fn router() -> Router {
},
),
)
.route(
"/:id/reclone",
post(
|Extension(state): StateExtension,
Extension(user): RequestUserExtension,
Path(build_id): Path<BuildId>| async move {
let update = spawn_request_action(async move {
state
.reclone_build(&build_id.id, &user)
.await
.map_err(handle_anyhow_error)
})
.await??;
response!(Json(update))
},
),
)
.route(
"/:id/action_state",
get(

View File

@@ -74,6 +74,7 @@ impl State {
id: String::from(GITHUB_WEBHOOK_USER_ID),
is_admin: true,
create_server_permissions: false,
create_build_permissions: false,
},
)
.await?;
@@ -103,6 +104,7 @@ impl State {
id: String::from(GITHUB_WEBHOOK_USER_ID),
is_admin: true,
create_server_permissions: false,
create_build_permissions: false,
},
)
.await?;
@@ -127,6 +129,7 @@ impl State {
id: String::from(GITHUB_WEBHOOK_USER_ID),
is_admin: true,
create_server_permissions: false,
create_build_permissions: false,
},
)
.await?;

View File

@@ -24,6 +24,7 @@ pub struct RequestUser {
pub id: String,
pub is_admin: bool,
pub create_server_permissions: bool,
pub create_build_permissions: bool,
}
#[derive(Serialize, Deserialize)]
@@ -109,6 +110,7 @@ impl JwtClient {
id: claims.id,
is_admin: user.admin,
create_server_permissions: user.create_server_permissions,
create_build_permissions: user.create_build_permissions,
};
Ok(user)
} else {

View File

@@ -32,9 +32,9 @@ impl State {
let state = State {
db: DbClient::new(config.mongo.clone()).await,
slack: config.slack_url.clone().map(|url| slack::Client::new(&url)),
periphery: PeripheryClient::new(config.passkey.clone()),
config,
update: UpdateWsChannel::new(),
periphery: PeripheryClient::default(),
build_action_states: Default::default(),
deployment_action_states: Default::default(),
server_action_states: Default::default(),

28
docs/builds.md Normal file
View File

@@ -0,0 +1,28 @@
# building images
Monitor builds docker images by cloning the source repository from Github and running ```docker build``` on the configured Dockerfile, which should be present in the source repository.
## repo configuration
Setting related to the github repo are under the *repo* tab on respective build's page.
To specify the github repo to build, just give it the name of the repo and the branch under *github config*. The name is given like ```mbecker20/monitor```, it includes the username / organization that owns the repo.
Many repos are private, in this case a Github access token is required in the periphery.config.toml of the building server. these are specified in the config like ```username = "access_token"```. An account which has access to the repo and is available on the periphery server can be selected to use via the *github account* dropdown menu.
Sometimes a command needs to be run when the repo is cloned, you can configure this in the *on clone* section.
There are two fields to pass for *on clone*. the first is *path*, which changes to working directory. To run the command in the root of the repo, just pass ".". The second field is *command*, this is the shell command to be executed after the repo is cloned.
For example, say your repo had a folder in it called "scripts" with a shell script "on-clone.sh". You would give *path* as "scripts" and command as "sh on-clone.sh". Or you could make *path* just "." and then command would be "sh scripts/on-clone.sh". Either way works fine.
## build configuration
## versioning
Monitor uses a major.minor.patch versioning scheme. Every build will auto increment the patch number, and push the image to docker hub with the version tag as well as the "latest" tag.
[next: deploying](https://github.com/mbecker20/monitor/blob/main/docs/deployments.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

3
docs/deployments.md Normal file
View File

@@ -0,0 +1,3 @@
## deploying applications
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

46
docs/introduction.md Normal file
View File

@@ -0,0 +1,46 @@
# introduction
If you have many servers running many applications, it can be a challenge to keep things organized and easily accessible. Without structure, things can become messy quickly, which means operational issues are more likely to arise and they can take longer to resolve. Ultimately these issues hinder productivity and waste valuable time. Monitor is a web app to provide this structure for how applications are built, deployed, and managed across many servers.
## docker
Monitor is opinionated by design, and [docker](https://docs.docker.com/) is the tool of choice. Docker provides the ability to package applications and their runtime dependencies into a standalone bundle, called an *image*. This makes them easy to "ship" to any server and run without the hassle of setting up the runtime environment. Docker uses the image as a sort of template to create *containers*. Containers are kind of like virtual machines but with different performance characteristics, namely that processes contained still run natively on the system kernel. The file system is seperate though, and like virtual machines, they can be created, started, stopped, and destroyed.
## monitor
Monitor is a solution for handling for the following:
1. Build application source into auto-versioned images.
2. Create, start, stop, and restart Docker containers, and view their status and logs.
3. Keep a record of all the actions that are performed and by whom.
4. View realtime and historical system resource usage.
5. Alerting for server health, like high cpu, memory, disk, etc.
## architecture and components
Monitor is composed of a single core and any amount of connected servers running the periphery application.
### monitor core
The core is a web server that hosts the core API and serves the frontend to be accessed in a web browser. All user interaction with the connected servers flow through the core. It is the stateful part of the system, with the application state stored on an instance of MongoDB.
### monitor periphery
The periphery is a stateless web server that exposes API called by the core. The core calls this API to get system usage and container status / logs, clone git repos, and perform docker actions. It is only intended to be reached from the core, and has an address whitelist to limit the IPs allowed to call this API.
### monitor cli
This is a simple standalone cli that helps perform some actions required to setup monitor core and periphery, like generating config files.
## core API
Monitor exposes powerful functionality over the core's REST API, enabling infrastructure engineers to manage deployments programmatically in addition to with the GUI. There is a [rust crate](https://crates.io/crates/monitor_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests.
## permissioning
Monitor is a system designed to be used by many users, whether they are developers, operations personnel, or administrators. The ability to affect an applications state is very powerful, so monitor has a granular permissioning system to only provide this functionality to the intended users. The permissioning system is explained in detail in the [permissioning](https://github.com/mbecker20/monitor/blob/main/docs/permissions.md) section.
User sign-on is possible using username / password, or with Oauth (Github and Google). Allowed login methods can be configured from the [core config](https://github.com/mbecker20/monitor/blob/main/config_example/core.config.example.toml).
[next: connecting servers](https://github.com/mbecker20/monitor/blob/main/docs/servers.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

1
docs/permissions.md Normal file
View File

@@ -0,0 +1 @@
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

30
docs/servers.md Normal file
View File

@@ -0,0 +1,30 @@
# connecting servers
Integrating a device into the monitor system has 2 steps:
1. Setup and start the periphery agent on the server
2. Adding the server to monitor via the core API
## setup monitor periphery
The easiest way to do this is to follow the [monitor guide](https://github.com/mbecker20/monitor-guide). This is a repo containing directions and scripts enabling command line installation via ssh or remotely.
### manual install steps
1. Download the periphery binary from the latest [release](https://github.com/mbecker20/monitor/releases) or install it using [cargo](https://crates.io/crates/monitor_periphery). If the monitor cli.
2. Create and edit ~/.monitor/periphery.config.toml, following the [config example](https://github.com/mbecker20/monitor/blob/main/config_example/periphery.config.example.toml). The file can be anywhere, it can be passed to periphery via the --config-path flag or with the CONFIG_PATH environment variable. The monitor cli can also be used: ```monitor periphery gen-config```
3. Ensure that inbound connectivity is allowed on the port specified in periphery.config.toml (default 8000).
4. Install docker. Make sure whatever user periphery is run as has access to the docker group without sudo.
5. Start the periphery binary with your preferred process manager, like systemd. The config read from the file is printed on startup, ensure that it is as expected.
## adding the server to monitor
The easiest way to add the server is with the GUI. On the home page, click the + button to the right of the server search bar, configure the name and address of the server. The address is the full http/s url to the periphery server, eg http://12.34.56.78:8000.
Once it is added, you can use access the GUI to modify some config, like the alerting thresholds for cpu, memory and disk usage. A server can also be temporarily disabled, this will prevent alerting if it goes offline.
Since no state is stored on the periphery servers, you can easily redirect all builds / deployments to be hosted on a different server. Just update the address to point to the new server.
[next: building](https://github.com/mbecker20/monitor/blob/main/docs/builds.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

5
docs/setup.md Normal file
View File

@@ -0,0 +1,5 @@
# setting up monitor core
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Rounded_Rectangle_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
<g id="Rounded_Rectangle_2">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#fceade" d="M15,4H1C0.45,4,0,4.45,0,5v14c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1V5
C16,4.45,15.55,4,15,4z M14,18H2V6h12V18z M19,0H5C4.45,0,4,0.45,4,1v2h2V2h12v12h-1v2h2c0.55,0,1-0.45,1-1V1
C20,0.45,19.55,0,19,0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 679 B

View File

@@ -0,0 +1,105 @@
import { useNavigate } from "@solidjs/router";
import { Component, createSignal } from "solid-js";
import { client, pushNotification } from "..";
import { useAppState } from "../state/StateProvider";
import { Build, Deployment } from "../types";
import { getId } from "../util/helpers";
import { useToggle } from "../util/hooks";
import ConfirmButton from "./shared/ConfirmButton";
import Icon from "./shared/Icon";
import Input from "./shared/Input";
import Flex from "./shared/layout/Flex";
import Grid from "./shared/layout/Grid";
import CenterMenu from "./shared/menu/CenterMenu";
import Selector from "./shared/menu/Selector";
const CopyMenu: Component<{
type: "deployment" | "build";
id: string;
}> = (p) => {
const navigate = useNavigate();
const [show, toggleShow] = useToggle();
const [newName, setNewName] = createSignal("");
const { builds, deployments, servers } = useAppState();
const curr_server = () => {
if (p.type === "build") {
return builds.get(p.id)!.server_id;
} else {
return deployments.get(p.id)!.deployment.server_id;
}
}
const [selectedId, setSelected] = createSignal(curr_server());
const name = () => {
if (p.type === "build") {
return builds.get(p.id)?.name;
} else if (p.type === "deployment") {
return deployments.get(p.id)?.deployment.name;
}
};
const copy = () => {
if (newName().length !== 0) {
let promise: Promise<Build | Deployment>;
if (p.type === "build") {
promise = client.copy_build(p.id, {
name: newName(),
server_id: selectedId(),
});
} else {
promise = client.copy_deployment(p.id, {
name: newName(),
server_id: selectedId(),
});
}
toggleShow();
promise.then((val) => {
navigate(`/${p.type}/${getId(val)}`);
});
} else {
pushNotification("bad", "copy name cannot be empty");
}
};
return (
<CenterMenu
show={show}
toggleShow={toggleShow}
title={`copy ${p.type} | ${name()}`}
target={<Icon type="duplicate" />}
targetClass="blue"
content={() => (
<Grid placeItems="center">
<Flex alignItems="center">
<Input
placeholder="copy name"
class="card dark"
style={{ padding: "0.5rem" }}
value={newName()}
onEdit={setNewName}
/>
<Selector
label="target: "
selected={selectedId()}
items={servers.ids()!}
onSelect={setSelected}
itemMap={(id) => servers.get(id)!.server.name}
targetClass="blue"
targetStyle={{ display: "flex", gap: "0.5rem" }}
searchStyle={{ width: "100%" }}
position="bottom right"
useSearch
/>
</Flex>
<ConfirmButton
class="green"
style={{ width: "100%" }}
onConfirm={copy}
>
copy {p.type}
</ConfirmButton>
</Grid>
)}
position="center"
/>
);
};
export default CopyMenu;

View File

@@ -10,20 +10,21 @@ import { useActionStates } from "./ActionStateProvider";
import { client } from "../..";
import { combineClasses, getId } from "../../util/helpers";
import { useParams } from "@solidjs/router";
import { PermissionLevel } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
const Actions: Component<{}> = (p) => {
const { user } = useUser();
const params = useParams() as { id: string };
const { builds } = useAppState();
const { builds, servers } = useAppState();
const build = () => builds.get(params.id)!;
const server = () => build() && servers.get(build()!.server_id);
const actions = useActionStates();
const userCanExecute = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Execute ||
build().permissions![getId(user())] === PermissionLevel.Update;
return (
<Show when={userCanExecute()}>
<Show when={userCanExecute() && server()?.status === ServerStatus.Ok}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Grid style={{ height: "fit-content" }}>

View File

@@ -13,6 +13,8 @@ import { A, useParams } from "@solidjs/router";
import { PermissionLevel } from "../../types";
import { client } from "../..";
import HoverMenu from "../shared/menu/HoverMenu";
import CopyMenu from "../CopyMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { builds, servers } = useAppState();
@@ -43,21 +45,26 @@ const Header: Component<{}> = (p) => {
<Flex alignItems="center" justifyContent="space-between">
<h1>{build().name}</h1>
<Show when={userCanUpdate()}>
<HoverMenu
target={
<ConfirmButton
onConfirm={() => {
client.delete_build(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
}
content="delete build"
position="bottom center"
padding="0.5rem"
/>
<Flex alignItems="center">
<CopyMenu type="build" id={params.id} />
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_build(params.id);
}}
class="red"
title={`delete build | ${build().name}`}
match={build().name}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete build"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">

View File

@@ -10,7 +10,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { Build, Operation, PermissionLevel } from "../../../types";
import { Build, Operation, PermissionLevel, ServerWithStatus } from "../../../types";
import { getId } from "../../../util/helpers";
type ConfigBuild = Build & {
@@ -22,6 +22,7 @@ type ConfigBuild = Build & {
type State = {
build: ConfigBuild;
setBuild: SetStoreFunction<ConfigBuild>;
server: () => ServerWithStatus | undefined
reset: () => void;
save: () => void;
userCanUpdate: () => boolean;
@@ -30,7 +31,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, builds } = useAppState();
const { ws, builds, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [build, set] = createStore({
@@ -44,6 +45,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
set(...args);
set("updated", true);
};
const server = () => servers.get(builds.get(params.id)!.server_id);
const load = () => {
// console.log("load build");
@@ -105,6 +107,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const state = {
build,
setBuild,
server,
reset: load,
save,
userCanUpdate,

View File

@@ -74,7 +74,7 @@ const EditBuildArgs: Component<{}> = (p) => {
value={buildArgs()}
onEdit={setBuildArgs}
style={{
width: "700px",
width: "1000px",
"max-width": "90vw",
height: "80vh",
padding: "1rem",

View File

@@ -1,6 +1,7 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { ServerStatus } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import Input from "../../../shared/Input";
import Flex from "../../../shared/layout/Flex";
@@ -9,10 +10,14 @@ import Selector from "../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Docker: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_docker_accounts(build.server_id).then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(build.server_id)
.then(setDockerAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -1,4 +1,4 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { Component, createEffect, createSignal } from "solid-js";
import Grid from "../../../shared/layout/Grid";
import { useConfig } from "../Provider";
import Flex from "../../../shared/layout/Flex";
@@ -6,12 +6,15 @@ import Input from "../../../shared/Input";
import Selector from "../../../shared/menu/Selector";
import { combineClasses } from "../../../../util/helpers";
import { client } from "../../../..";
import { ServerStatus } from "../../../../types";
const Git: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts)
if (server()?.status === ServerStatus.Ok) {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -1,5 +1,5 @@
import { Component, Match, Show, Switch } from "solid-js";
import { client, pushNotification } from "../..";
import { client } from "../..";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import ConfirmButton from "../shared/ConfirmButton";
@@ -9,24 +9,33 @@ import Grid from "../shared/layout/Grid";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
import { useActionStates } from "./ActionStateProvider";
import { combineClasses, getId } from "../../util/helpers";
import { combineClasses } from "../../util/helpers";
import { A, useParams } from "@solidjs/router";
import { DockerContainerState, PermissionLevel } from "../../types";
import {
DockerContainerState,
PermissionLevel,
ServerStatus,
} from "../../types";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Actions: Component<{}> = (p) => {
const { deployments, builds, getPermissionOnDeployment } = useAppState();
const { deployments, builds, servers, getPermissionOnDeployment } =
useAppState();
const params = useParams();
const { user, user_id } = useUser();
const deployment = () => deployments.get(params.id)!;
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
const show = () => {
const permissions = getPermissionOnDeployment(params.id);
return (
server()?.status === ServerStatus.Ok &&
deployment() &&
(user().admin ||
permissions === PermissionLevel.Execute ||
permissions === PermissionLevel.Update)
);
};
const deployment = () => deployments.get(params.id)!;
const showBuild = () => {
const build = deployment().deployment.build_id
? builds.get(deployment().deployment.build_id!)
@@ -117,13 +126,17 @@ const Actions: Component<{}> = (p) => {
};
const Build: Component = () => {
const { ws, deployments } = useAppState();
const { deployments } = useAppState();
const params = useParams();
const actions = useActionStates();
const buildID = () => deployments.get(params.id)!.deployment.build_id!;
return (
<Flex class={combineClasses("action shadow")}>
<A href={`/build/${buildID()}`} class="pointer">
<A
href={`/build/${buildID()}`}
class="pointer"
style={{ padding: 0, "font-size": "16px" }}
>
build
</A>
<Show
@@ -152,6 +165,8 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
const params = useParams();
// const deployment = () => deployments.get(params.id)!;
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.deploying}
@@ -163,14 +178,30 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
>
<HoverMenu
target={
<ConfirmButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
<Show
when={p.redeploy}
fallback={
<ConfirmButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
>
<Icon type={"play"} />
</ConfirmButton>
}
>
<Icon type={p.redeploy ? "reset" : "play"} />
</ConfirmButton>
<ConfirmMenuButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
title={`redeploy container | ${name()}`}
match={name()!}
>
<Icon type={"reset"} />
</ConfirmMenuButton>
</Show>
}
content={p.redeploy ? "redeploy container" : "deploy container"}
position="bottom center"
@@ -183,6 +214,8 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
const RemoveContainer = () => {
const params = useParams();
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.removing}
@@ -194,14 +227,16 @@ const RemoveContainer = () => {
>
<HoverMenu
target={
<ConfirmButton
<ConfirmMenuButton
class="red"
onConfirm={() => {
client.remove_container(params.id);
}}
title={`destroy container | ${name()}`}
match={name()!}
>
<Icon type="trash" />
</ConfirmButton>
</ConfirmMenuButton>
}
content="delete container"
position="bottom center"
@@ -245,6 +280,8 @@ const Start = () => {
const Stop = () => {
const params = useParams();
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.stopping}
@@ -256,14 +293,16 @@ const Stop = () => {
>
<HoverMenu
target={
<ConfirmButton
<ConfirmMenuButton
class="orange"
onConfirm={() => {
client.stop_container(params.id);
}}
title={`stop container | ${name()}`}
match={name()!}
>
<Icon type="pause" />
</ConfirmButton>
</ConfirmMenuButton>
}
content="stop container"
position="bottom center"

View File

@@ -1,27 +1,35 @@
import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { MAX_PAGE_WIDTH } from "../..";
import { Component, onCleanup, Show } from "solid-js";
import { client } from "../..";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import { PermissionLevel } from "../../types";
import { combineClasses, getId } from "../../util/helpers";
import { ServerStatus } from "../../types";
import NotFound from "../NotFound";
import Grid from "../shared/layout/Grid";
import Actions from "./Actions";
import { ActionStateProvider } from "./ActionStateProvider";
import Header from "./Header";
import { ConfigProvider } from "./tabs/config/Provider";
import DeploymentTabs from "./tabs/Tabs";
import Updates from "./Updates";
const Deployment2: Component<{}> = (p) => {
const POLLING_RATE = 10000;
// let interval = -1;
const Deployment: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const { isSemiMobile } = useAppDimensions();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
// clearInterval(interval);
// interval = setInterval(async () => {
// if (server()?.status === ServerStatus.Ok) {
// const deployment = await client.get_deployment(params.id);
// deployments.update(deployment);
// }
// }, POLLING_RATE);
// onCleanup(() => clearInterval(interval));
return (
<Show
when={deployment() && server()}
@@ -53,46 +61,4 @@ const Deployment2: Component<{}> = (p) => {
);
};
const Deployment: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () => deployment() && servers.get(deployment()!.deployment.server_id);
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const userCanUpdate = () => user().admin || deployment()?.deployment.permissions![getId(user())] === PermissionLevel.Update;
return (
<Show
when={deployment() && server()}
fallback={<NotFound type="deployment" />}
>
<ActionStateProvider>
<Grid class={combineClasses("content")}>
{/* left / actions */}
<Grid class="left-content">
<Header />
<Actions />
<Show when={!isSemiMobile() && userCanUpdate()}>
<Updates />
</Show>
</Grid>
{/* right / tabs */}
<Show
when={userCanUpdate()}
fallback={
<h2 class={combineClasses("card tabs shadow")}>
you do not have permission to view this deployment
</h2>
}
>
<ConfigProvider>
<DeploymentTabs />
</ConfigProvider>
</Show>
</Grid>
</ActionStateProvider>
</Show>
);
};
export default Deployment2;
export default Deployment;

View File

@@ -6,7 +6,6 @@ import {
deploymentHeaderStateClass,
getId,
} from "../../util/helpers";
import ConfirmButton from "../shared/ConfirmButton";
import Icon from "../shared/Icon";
import Flex from "../shared/layout/Flex";
import Grid from "../shared/layout/Grid";
@@ -17,6 +16,8 @@ import Updates from "./Updates";
import { DockerContainerState, PermissionLevel } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import CopyMenu from "../CopyMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { deployments, servers } = useAppState();
@@ -53,21 +54,35 @@ const Header: Component<{}> = (p) => {
<Flex alignItems="center" justifyContent="space-between">
<h1>{deployment()!.deployment.name}</h1>
<Show when={userCanUpdate()}>
<HoverMenu
target={
<ConfirmButton
onConfirm={() => {
client.delete_deployment(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
}
content="delete deployment"
position="bottom center"
padding="0.5rem"
/>
<Flex alignItems="center">
<CopyMenu type="deployment" id={params.id} />
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_deployment(params.id);
}}
class="red"
title={`delete deployment | ${
deployment().deployment.name
}`}
match={deployment().deployment.name}
info={
<Show when={deployment().container}>
<div style={{ opacity: 0.7 }}>
warning! this will destroy this deployments container
</div>
</Show>
}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete deployment"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">

View File

@@ -17,6 +17,7 @@ import {
DockerContainerState,
Log as LogType,
Operation,
ServerStatus,
} from "../../../types";
import { client } from "../../..";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
@@ -26,18 +27,23 @@ import { useUser } from "../../../state/UserProvider";
const DeploymentTabs: Component<{}> = () => {
const { user } = useUser();
const { deployments, ws } = useAppState();
const { deployments, ws, servers } = useAppState();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
const [logTail, setLogTail] = createSignal(50);
const [log, setLog] = createSignal<LogType>();
const status = () =>
deployment()!.state === DockerContainerState.NotDeployed
? "not deployed"
: deployment()!.container?.state;
// const status = () =>
// deployment()!.state === DockerContainerState.NotDeployed
// ? "not deployed"
// : deployment()!.container?.state;
const log_available = () =>
server()?.status === ServerStatus.Ok &&
deployment()?.state !== DockerContainerState.NotDeployed;
const loadLog = async () => {
console.log("load log");
if (deployment()?.state !== DockerContainerState.NotDeployed) {
if (log_available()) {
console.log("load log");
const log = await client.get_deployment_container_log(
params.id,
logTail()
@@ -78,7 +84,7 @@ const DeploymentTabs: Component<{}> = () => {
title: "config",
element: () => <Config />,
},
status() !== "not deployed" && [
log_available() && [
{
title: "log",
element: () => (
@@ -90,7 +96,7 @@ const DeploymentTabs: Component<{}> = () => {
/>
),
},
status() !== "not deployed" && {
{
title: "error log",
titleElement: () => (
<Flex gap="0.5rem" alignItems="center">

View File

@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Deployment, Operation, PermissionLevel } from "../../../../types";
import { Deployment, Operation, PermissionLevel, ServerStatus, ServerWithStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigDeployment = Deployment & {
@@ -25,6 +25,7 @@ type State = {
editing: Accessor<boolean>;
deployment: ConfigDeployment;
setDeployment: SetStoreFunction<ConfigDeployment>;
server: () => ServerWithStatus | undefined;
reset: () => void;
save: () => void;
networks: Accessor<any[]>;
@@ -34,7 +35,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, deployments } = useAppState();
const { ws, deployments, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [editing] = createSignal(false);
@@ -87,11 +88,13 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
createEffect(load);
const [networks, setNetworks] = createSignal<any[]>([]);
const server = () => servers.get(deployments.get(params.id)!.deployment.server_id);
createEffect(() => {
console.log("load networks");
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
if (server()?.status === ServerStatus.Ok) {
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
}
});
const save = () => {
@@ -141,6 +144,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
editing,
deployment,
setDeployment,
server,
reset: load,
save,
networks,

View File

@@ -1,18 +1,20 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../../..";
import { useAppState } from "../../../../../state/StateProvider";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Flex from "../../../../shared/layout/Flex";
import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const DockerAccount: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, setDeployment, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
}
});
return (
<Flex

View File

@@ -74,7 +74,7 @@ const EditDotEnv: Component<{}> = (p) => {
value={dotenv()}
onEdit={setDotEnv}
style={{
width: "700px",
width: "1000px",
"max-width": "90vw",
height: "80vh",
padding: "1rem",

View File

@@ -1,5 +1,6 @@
import { Component, createEffect, createSignal } from "solid-js";
import { client } from "../../../../..";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Input from "../../../../shared/Input";
import Flex from "../../../../shared/layout/Flex";
@@ -8,10 +9,14 @@ import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Git: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, server, setDeployment, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(deployment.server_id).then(setGithubAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_github_accounts(deployment.server_id)
.then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>

View File

@@ -1,10 +1,17 @@
import { useParams } from "@solidjs/router";
import { Component, createEffect, createSignal, Show } from "solid-js";
import {
Component,
createEffect,
createMemo,
createSignal,
onCleanup,
Show,
} from "solid-js";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { DockerContainerState, Log as LogType } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import { useBuffer } from "../../../../util/hooks";
import { useBuffer, useLocalStorageToggle } from "../../../../util/hooks";
import Icon from "../../../shared/Icon";
import Flex from "../../../shared/layout/Flex";
import Grid from "../../../shared/layout/Grid";
@@ -12,6 +19,10 @@ 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 Log: Component<{
log?: LogType;
logTail: number;
@@ -57,9 +68,23 @@ const Log: Component<{
}
};
const buffer = useBuffer(scrolled, 250);
const [poll, togglePoll] = useLocalStorageToggle(
"deployment-log-polling",
true
);
clearInterval(interval);
interval = setInterval(() => {
if (poll() && deployment()?.state === DockerContainerState.Running) {
p.reload();
}
}, POLLING_RATE);
onCleanup(() => clearInterval(interval));
return (
<Show when={p.log}>
<Grid gap="0.5rem" style={{ height: "100%", "grid-template-rows": "auto 1fr" }}>
<Grid
gap="0.5rem"
style={{ height: "100%", "grid-template-rows": "auto 1fr" }}
>
<Flex
alignItems="center"
justifyContent="flex-end"
@@ -100,6 +125,9 @@ const Log: Component<{
>
<Icon type="refresh" />
</button>
<button class={poll() ? "green" : "red"} onClick={togglePoll}>
{poll() ? "" : "don't "}poll
</button>
</Flex>
<div style={{ position: "relative", height: "100%" }}>
<div

View File

@@ -25,25 +25,27 @@ const Actions: Component<{}> = (p) => {
<Show
when={server() && server().status === ServerStatus.Ok && userCanExecute()}
>
<Grid class={combineClasses("card shadow")}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
<Grid style={{ height: "fit-content" }}>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
</Grid>
</Grid>
</Show>
);

View File

@@ -9,10 +9,12 @@ import Grid from "../shared/layout/Grid";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useLocalStorageToggle } from "../../util/hooks";
import Updates from "./Updates";
import { PermissionLevel, Server } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -26,25 +28,73 @@ const Header: Component<{}> = (p) => {
const userCanUpdate = () =>
user().admin ||
server().server.permissions![getId(user())] === PermissionLevel.Update;
const [version] = createResource(async () => {
return await client.get_server_version(params.id).catch();
});
const [version] = createResource(
() => server() && server().status === ServerStatus.Ok,
async (do_it?: boolean) => {
if (!do_it) return;
return await client.get_server_version(params.id).catch();
}
);
return (
<>
<Flex
<Grid
gap="0.5rem"
class={combineClasses("card shadow")}
justifyContent="space-between"
alignItems="center"
style={{
position: "relative",
cursor: isSemiMobile() ? "pointer" : undefined,
height: "fit-content",
}}
onClick={() => {
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Grid gap="0.1rem">
<Flex alignItems="center" justifyContent="space-between">
<h1>{server().server.name}</h1>
<Show when={userCanUpdate()}>
<Flex alignItems="center">
<div class={serverStatusClass(server().status)}>{status()}</div>
<HoverMenu
target={
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
}
content="server stats"
position="bottom center"
padding="0.5rem"
/>
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
title={`delete server | ${server().server.name}`}
match={server().server.name}
info={
<div style={{ opacity: 0.7 }}>
warning! this will also delete all builds and
deployments on this server
</div>
}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete server"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">
<Flex gap="0.2rem" alignItems="center" style={{ opacity: 0.8 }}>
<div>server</div>
<Show when={server().server.region}>
@@ -52,42 +102,13 @@ const Header: Component<{}> = (p) => {
{server().server.region}
</Show>
</Flex>
</Grid>
<Flex alignItems="center">
<Show when={!isMobile()}>
<Show when={version()} fallback={<Loading type="three-dot" />}>
<Show when={version()}>
<div style={{ opacity: 0.7 }}>periphery v{version()}</div>
</Show>
</Show>
<div class={serverStatusClass(server().status)}>{status()}</div>
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
<Show when={userCanUpdate()}>
<ConfirmButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
</Show>
</Flex>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
type={showUpdates() ? "chevron-up" : "chevron-down"}
width="0.9rem"
/>
</Flex>
</Show>
</Flex>
</Grid>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>

View File

@@ -32,7 +32,7 @@ const Server: Component<{}> = (p) => {
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid>
<Grid style={{ "flex-grow": 1, "grid-auto-rows": "auto 1fr" }}>
<Header />
<Actions />
</Grid>

View File

@@ -2,6 +2,7 @@ import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { ServerStatus } from "../../../types";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { Tab } from "../../shared/tabs/Tabs";
import Config from "./config/Config";
@@ -26,7 +27,7 @@ const ServerTabs: Component<{}> = (p) => {
title: "config",
element: () => <Config />,
},
{
server()?.status === ServerStatus.Ok && {
title: "info",
element: () => <Info />
},

View File

@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Server, Operation, PermissionLevel } from "../../../../types";
import { Server, Operation, PermissionLevel, ServerStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigServer = Server & { loaded: boolean; updated: boolean };
@@ -59,7 +59,9 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const [networks, setNetworks] = createSignal<any[]>([]);
const loadNetworks = () => {
// console.log("load networks");
client.get_docker_networks(params.id).then(setNetworks);
if (servers.get(params.id)?.status === ServerStatus.Ok) {
client.get_docker_networks(params.id).then(setNetworks);
}
};
createEffect(loadNetworks);

View File

@@ -0,0 +1,83 @@
import { Component, createSignal, JSX } from "solid-js";
import { pushNotification } from "../..";
import { useToggle } from "../../util/hooks";
import ConfirmButton from "./ConfirmButton";
import Input from "./Input";
import Grid from "./layout/Grid";
import CenterMenu from "./menu/CenterMenu";
const ConfirmMenuButton: Component<{
onConfirm?: () => void;
class?: string;
style?: JSX.CSSProperties;
title: string;
match: string;
info?: JSX.Element;
children: JSX.Element;
}> = (p) => {
const [show, toggleShow] = useToggle();
return (
<CenterMenu
show={show}
toggleShow={toggleShow}
title={p.title}
targetClass={p.class}
target={p.children}
content={() => (
<ConfirmMenuContent
class={p.class}
title={p.title}
match={p.match}
info={p.info}
onConfirm={p.onConfirm}
/>
)}
position="center"
/>
);
};
const ConfirmMenuContent: Component<{
class?: string;
title: string;
match: string;
onConfirm?: () => void;
info?: JSX.Element;
}> = (p) => {
const [input, setInput] = createSignal("");
return (
<Grid placeItems="center">
{p.info}
<Input
class="darkgrey"
style={{
padding: "0.5rem",
width: "100%",
"border-style": "solid",
"border-width": "1px",
"border-color": input() === p.match ? "#41764c" : "#952E23",
}}
placeholder={`enter '${p.match}'`}
onEdit={setInput}
value={input()}
autofocus
/>
<ConfirmButton
class={p.class}
style={{ width: "100%" }}
onConfirm={() => {
if (input() === p.match) {
p.onConfirm && p.onConfirm();
} else {
pushNotification("bad", "must enter value to confirm");
}
}}
>
{p.title}
</ConfirmButton>
</Grid>
);
};
export default ConfirmMenuButton;

View File

@@ -45,7 +45,8 @@ export type IconType =
| "cog"
| "home"
| "timeline-line-chart"
| "arrow-right";
| "arrow-right"
| "duplicate";
const ICON_DIR = import.meta.env.VITE_ICON_DIR || "/assets/icons"

View File

@@ -33,10 +33,12 @@ const Selector: Component<{
itemClass?: string;
itemStyle?: JSX.CSSProperties;
label?: JSXElement;
itemMap?: (item: string) => string;
}> = (p) => {
const [show, toggle] = useToggle();
const [search, setSearch] = createSignal("");
let ref: HTMLInputElement | undefined;
const current = () => (p.itemMap ? p.itemMap(p.selected) : p.selected);
createEffect(() => {
if (show()) setTimeout(() => ref?.focus(), 200);
});
@@ -46,7 +48,7 @@ const Selector: Component<{
fallback={
<div class={p.disabledClass} style={p.disabledStyle}>
<Show when={p.label}>{p.label}</Show>
{p.selected}
{current()}
</div>
}
>
@@ -60,7 +62,7 @@ const Selector: Component<{
target={
<button class={p.targetClass} onClick={toggle} style={p.targetStyle}>
<Show when={p.label}>{p.label}</Show>
{p.selected}
{current()}
<Icon type="chevron-down" />
</button>
}
@@ -84,7 +86,11 @@ const Selector: Component<{
<For
each={
p.useSearch
? p.items.filter((item) => item.includes(search()))
? p.items.filter((item) =>
p.itemMap
? p.itemMap(item).includes(search())
: item.includes(search())
)
: p.items
}
>
@@ -101,7 +107,7 @@ const Selector: Component<{
}}
class={combineClasses(p.itemClass, s.SelectorItem)}
>
{item}
{p.itemMap ? p.itemMap(item) : item}
</button>
)}
</For>

View File

@@ -52,7 +52,7 @@ export default UpdateMenu;
const UpdateMenuContent: Component<{ update: UpdateType }> = (p) => {
return (
<Grid class={s.LogContainer}>
<Grid class={combineClasses(s.LogContainer, "scroller")}>
<UpdateSummary update={p.update} />
<UpdateLogs update={p.update} />
</Grid>
@@ -66,7 +66,7 @@ const UpdateSummary: Component<{ update: UpdateType }> = (p) => {
gap="0.5rem"
class="card light shadow"
gridTemplateColumns="1fr 1fr"
style={{ width: "40rem", "max-width": "90vw" }}
style={{ width: "100%", "box-sizing": "border-box" }}
>
<Flex gap="0.5rem" alignItems="center">
status: <h2>{p.update.status.replaceAll("_", " ")}</h2>

View File

@@ -41,7 +41,6 @@
.LogContainer {
max-height: 80vh;
overflow: scroll;
}
.Log {
@@ -49,8 +48,8 @@
overflow-wrap: anywhere;
/* word-wrap: break-word; */
tab-size: 2;
width: 40rem;
max-width: 90vw;
width: 1000px;
max-width: 80vw;
box-sizing: border-box;
background-color: rgba(c.$darkgrey, 0.6);
padding: 1rem;

View File

@@ -102,8 +102,8 @@ export const AppStateProvider: ParentComponent = (p) => {
return PermissionLevel.None;
}
},
serverStats: useServerStats(),
serverInfo: useServerInfo(),
serverStats: useServerStats(servers),
serverInfo: useServerInfo(servers),
groups,
getPermissionOnGroup: (id: string) => {
const group = groups.get(id)!;

View File

@@ -47,13 +47,19 @@ export function useServers() {
);
}
export function useServerStats() {
export function useServerStats(servers: ReturnType<typeof useServers>) {
const [stats, set] = createSignal<Record<string, SystemStats | undefined>>(
{}
);
const load = async (serverID: string) => {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
} catch (error) {
console.log("error getting server stats");
}
}
};
const loading: Record<string, boolean> = {};
setTimeout(() => Object.keys(stats()).forEach(load), 30000);
@@ -74,13 +80,19 @@ export function useServerStats() {
};
}
export function useServerInfo() {
export function useServerInfo(servers: ReturnType<typeof useServers>) {
const [info, set] = createSignal<
Record<string, SystemInformation | undefined>
>({});
const load = async (serverID: string) => {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
} catch (error) {
console.log("error getting server info", error);
}
}
};
const loading: Record<string, boolean> = {};
return {

View File

@@ -11,9 +11,12 @@ body {
background-color: c.$darkgrey;
color: c.$app-color;
scrollbar-color: rgba(252, 234, 222, 0.3) transparent;
scrollbar-width: thin;
display: grid;
grid-template-columns: 1fr;
place-items: center;
place-items: center;
}
code {

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_helpers"
version = "0.1.11"
version = "0.1.16"
edition = "2021"
authors = ["MoghTech"]
description = "helpers used as dependency for mogh tech monitor"
@@ -9,8 +9,9 @@ license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# types = { package = "monitor_types", path = "../types" }
types = { package = "monitor_types", version = "0.1.11" }
tokio = "1.25"
types = { package = "monitor_types", path = "../types" }
periphery_client = { path = "../periphery_client" }
async_timing_util = "0.1.14"
bollard = "0.13"
anyhow = "1.0"
@@ -18,8 +19,10 @@ axum = { version = "0.6", features = ["ws", "json"] }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
toml = "0.6"
toml = "0.7"
run_command = { version = "0.0.5", features = ["async_tokio"] }
rand = "0.8"
futures = "0.3"
futures-util = "0.3.25"
aws-config = "0.54"
aws-sdk-ec2 = "0.24"

199
lib/helpers/src/aws/mod.rs Normal file
View File

@@ -0,0 +1,199 @@
use std::time::Duration;
use anyhow::{anyhow, Context};
use aws_sdk_ec2::model::{
BlockDeviceMapping, EbsBlockDevice, InstanceNetworkInterfaceSpecification, InstanceStateChange,
InstanceStateName, InstanceStatus, ResourceType, Tag, TagSpecification,
};
pub use aws_sdk_ec2::{
model::InstanceType,
output::{DescribeInstanceStatusOutput, TerminateInstancesOutput},
Client, Region,
};
use types::Server;
pub async fn create_ec2_client(
region: String,
access_key_id: &str,
secret_access_key: String,
) -> Client {
// There may be a better way to pass these keys to client
std::env::set_var("AWS_ACCESS_KEY_ID", access_key_id);
std::env::set_var("AWS_SECRET_ACCESS_KEY", secret_access_key);
let region = Region::new(region);
let config = aws_config::from_env().region(region).load().await;
let client = Client::new(&config);
client
}
pub struct Ec2Instance {
pub instance_id: String,
pub server: Server,
}
const POLL_RATE_SECS: u64 = 2;
const MAX_POLL_TRIES: usize = 30;
/// this will only resolve after the instance is running
/// should still poll the periphery agent after creation
pub async fn create_instance_with_ami(
client: &Client,
instance_name: &str,
ami_id: &str,
instance_type: &str,
subnet_id: &str,
security_group_ids: Vec<String>,
volume_size_gb: i32,
key_pair_name: &str,
assign_public_ip: bool,
) -> anyhow::Result<Ec2Instance> {
let instance_type = InstanceType::from(instance_type);
if let InstanceType::Unknown(t) = instance_type {
return Err(anyhow!("unknown instance type {t:?}"));
}
let res = client
.run_instances()
.image_id(ami_id)
.instance_type(instance_type)
.block_device_mappings(
BlockDeviceMapping::builder()
.set_device_name(String::from("/dev/sda1").into())
.set_ebs(
EbsBlockDevice::builder()
.volume_size(volume_size_gb)
.build()
.into(),
)
.build(),
)
.network_interfaces(
InstanceNetworkInterfaceSpecification::builder()
.subnet_id(subnet_id)
.associate_public_ip_address(assign_public_ip)
.set_groups(security_group_ids.into())
.device_index(0)
.build(),
)
.key_name(key_pair_name)
.tag_specifications(
TagSpecification::builder()
.tags(Tag::builder().key("Name").value(instance_name).build())
.resource_type(ResourceType::Instance)
.build(),
)
.min_count(1)
.max_count(1)
.send()
.await
.context("failed to start builder ec2 instance")?;
let instance = res
.instances()
.ok_or(anyhow!("got None for created instances"))?
.get(0)
.ok_or(anyhow!("instances array is empty"))?;
let instance_id = instance
.instance_id()
.ok_or(anyhow!("instance does not have instance_id"))?
.to_string();
for _ in 0..MAX_POLL_TRIES {
let state_name = get_ec2_instance_state_name(&client, &instance_id).await?;
if state_name == Some(InstanceStateName::Running) {
let ip = if assign_public_ip {
get_ec2_instance_public_ip(client, &instance_id).await?
} else {
instance
.private_ip_address()
.ok_or(anyhow!("instance does not have private ip"))?
.to_string()
};
let server = Server {
address: format!("http://{ip}:8000"),
..Default::default()
};
return Ok(Ec2Instance {
instance_id,
server,
});
}
tokio::time::sleep(Duration::from_secs(POLL_RATE_SECS)).await;
}
Err(anyhow!("instance not running after polling"))
}
pub async fn get_ec2_instance_status(
client: &Client,
instance_id: &str,
) -> anyhow::Result<Option<InstanceStatus>> {
let status = client
.describe_instance_status()
.instance_ids(instance_id)
.send()
.await
.context("failed to get instance status from aws")?
.instance_statuses()
.ok_or(anyhow!("instance statuses is None"))?
.get(0)
.map(|s| s.to_owned());
Ok(status)
}
pub async fn get_ec2_instance_state_name(
client: &Client,
instance_id: &str,
) -> anyhow::Result<Option<InstanceStateName>> {
let status = get_ec2_instance_status(client, instance_id).await?;
if status.is_none() {
return Ok(None);
}
let state = status
.unwrap()
.instance_state()
.ok_or(anyhow!("instance state is None"))?
.name()
.ok_or(anyhow!("instance state name is None"))?
.to_owned();
Ok(Some(state))
}
pub async fn get_ec2_instance_public_ip(
client: &Client,
instance_id: &str,
) -> anyhow::Result<String> {
let ip = client
.describe_instances()
.instance_ids(instance_id)
.send()
.await
.context("failed to get instance status from aws")?
.reservations()
.ok_or(anyhow!("instance reservations is None"))?
.get(0)
.ok_or(anyhow!("instance reservations is empty"))?
.instances()
.ok_or(anyhow!("instances is None"))?
.get(0)
.ok_or(anyhow!("instances is empty"))?
.public_ip_address()
.ok_or(anyhow!("instance has no public ip"))?
.to_string();
Ok(ip)
}
pub async fn terminate_ec2_instance(
client: &Client,
instance_id: &str,
) -> anyhow::Result<InstanceStateChange> {
let res = client
.terminate_instances()
.instance_ids(instance_id)
.send()
.await
.context("failed to terminate instance from aws")?
.terminating_instances()
.ok_or(anyhow!("terminating instances is None"))?
.get(0)
.ok_or(anyhow!("terminating instances is empty"))?
.to_owned();
Ok(res)
}

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{anyhow, Context};
use types::{Build, DockerBuildArgs, EnvironmentVar, Log, Version};
use crate::{all_logs_success, git, run_monitor_command, to_monitor_name};
use crate::{run_monitor_command, to_monitor_name};
use super::docker_login;
@@ -17,9 +17,7 @@ pub async fn build(
name,
version,
docker_build_args,
branch,
docker_account,
pre_build,
..
}: &Build,
mut repo_dir: PathBuf,
@@ -38,25 +36,25 @@ pub async fn build(
.await
.context("failed to login to docker")?;
repo_dir.push(&name);
let pull_logs = git::pull(repo_dir.clone(), branch, &None).await;
if !all_logs_success(&pull_logs) {
logs.extend(pull_logs);
return Ok(logs);
}
logs.extend(pull_logs);
if let Some(command) = pre_build {
let dir = repo_dir.join(&command.path);
let pre_build_log = run_monitor_command(
"pre build",
format!("cd {} && {}", dir.display(), command.command),
)
.await;
if !pre_build_log.success {
logs.push(pre_build_log);
return Ok(logs);
}
logs.push(pre_build_log);
}
// let pull_logs = git::pull(repo_dir.clone(), branch, &None).await;
// if !all_logs_success(&pull_logs) {
// logs.extend(pull_logs);
// return Ok(logs);
// }
// logs.extend(pull_logs);
// if let Some(command) = pre_build {
// let dir = repo_dir.join(&command.path);
// let pre_build_log = run_monitor_command(
// "pre build",
// format!("cd {} && {}", dir.display(), command.command),
// )
// .await;
// if !pre_build_log.success {
// logs.push(pre_build_log);
// return Ok(logs);
// }
// logs.push(pre_build_log);
// }
let build_dir = repo_dir.join(build_path);
let dockerfile_path = match dockerfile_path {
Some(dockerfile_path) => dockerfile_path.to_owned(),

View File

@@ -2,47 +2,10 @@ use std::path::PathBuf;
use ::run_command::async_run_command;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use types::{monitor_timestamp, Build, Command, Deployment, GithubToken, GithubUsername, Log};
use types::{monitor_timestamp, CloneArgs, Command, GithubToken, Log};
use crate::{run_monitor_command, to_monitor_name};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CloneArgs {
name: String,
repo: Option<String>,
branch: Option<String>,
on_clone: Option<Command>,
on_pull: Option<Command>,
pub github_account: Option<GithubUsername>,
}
impl From<&Deployment> for CloneArgs {
fn from(d: &Deployment) -> Self {
CloneArgs {
name: d.name.clone(),
repo: d.repo.clone(),
branch: d.branch.clone(),
on_clone: d.on_clone.clone(),
on_pull: d.on_pull.clone(),
github_account: d.github_account.clone(),
}
}
}
impl From<&Build> for CloneArgs {
fn from(b: &Build) -> Self {
CloneArgs {
name: b.name.clone(),
repo: b.repo.clone(),
branch: b.branch.clone(),
on_clone: b.on_clone.clone(),
on_pull: None,
github_account: b.github_account.clone(),
}
}
}
pub async fn pull(
mut path: PathBuf,
branch: &Option<String>,

View File

@@ -7,6 +7,7 @@ use run_command::{async_run_command, CommandOutput};
use serde::de::DeserializeOwned;
use types::{monitor_timestamp, Log};
pub mod aws;
pub mod docker;
pub mod git;

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_client"
version = "0.1.11"
version = "0.1.16"
edition = "2021"
authors = ["MoghTech"]
description = "a client to interact with the monitor system"
@@ -9,14 +9,15 @@ 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.1.11"
monitor_types = "0.1.16"
# monitor_types = { path = "../types" }
reqwest = { version = "0.11", features = ["json"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio = { version = "1.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
tokio-util = "0.7"
anyhow = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
futures-util = "0.3"
futures-util = "0.3"
envy = "0.4"

View File

@@ -88,9 +88,9 @@ impl MonitorClient {
.context(format!("failed at building build {build_id}"))
}
pub async fn reclone_build(&self, id: &str) -> anyhow::Result<Update> {
self.post::<(), _>(&format!("/api/build/{id}/reclone"), None)
.await
.context(format!("failed at recloning build {id}"))
}
// pub async fn reclone_build(&self, id: &str) -> anyhow::Result<Update> {
// self.post::<(), _>(&format!("/api/build/{id}/reclone"), None)
// .await
// .context(format!("failed at recloning build {id}"))
// }
}

View File

@@ -3,7 +3,7 @@
use anyhow::{anyhow, Context};
use monitor_types::User;
use reqwest::StatusCode;
use serde::{de::DeserializeOwned, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::json;
pub use futures_util;
@@ -20,6 +20,15 @@ mod secret;
mod server;
mod update;
#[derive(Deserialize)]
struct MonitorEnv {
monitor_url: String,
monitor_token: Option<String>,
monitor_username: Option<String>,
monitor_password: Option<String>,
monitor_secret: Option<String>,
}
#[derive(Clone)]
pub struct MonitorClient {
http_client: reqwest::Client,
@@ -80,6 +89,26 @@ impl MonitorClient {
Ok(client)
}
pub async fn new_from_env() -> anyhow::Result<MonitorClient> {
let env = envy::from_env::<MonitorEnv>()
.context("failed to parse environment for monitor client")?;
if let Some(token) = env.monitor_token {
Ok(MonitorClient::new_with_token(&env.monitor_url, token))
} else if let Some(password) = env.monitor_password {
let username = env.monitor_username.ok_or(anyhow!(
"must provide MONITOR_USERNAME to authenticate with MONITOR_PASSWORD"
))?;
MonitorClient::new_with_password(&env.monitor_url, username, password).await
} else if let Some(secret) = env.monitor_secret {
let username = env.monitor_username.ok_or(anyhow!(
"must provide MONITOR_USERNAME to authenticate with MONITOR_SECRET"
))?;
MonitorClient::new_with_secret(&env.monitor_url, username, secret).await
} else {
Err(anyhow!("failed to initialize monitor client from env | must provide one of: (MONITOR_TOKEN), (MONITOR_USERNAME and MONITOR_PASSWORD), (MONITOR_USERNAME and MONITOR_SECRET)"))
}
}
pub async fn create_user(
&self,
username: impl Into<String>,

View File

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

View File

@@ -1,7 +1,6 @@
use anyhow::Context;
use helpers::git::CloneArgs;
use serde_json::json;
use types::{Command, Log, Server};
use types::{CloneArgs, Command, Log, Server};
use crate::PeripheryClient;

View File

@@ -14,12 +14,19 @@ mod git;
mod image;
mod network;
#[derive(Default)]
pub struct PeripheryClient {
http_client: reqwest::Client,
passkey: String,
}
impl PeripheryClient {
pub fn new(passkey: String) -> PeripheryClient {
PeripheryClient {
http_client: Default::default(),
passkey,
}
}
pub async fn health_check(&self, server: &Server) -> anyhow::Result<String> {
self.get_text(server, "/health", 1000)
.await
@@ -95,7 +102,8 @@ impl PeripheryClient {
) -> anyhow::Result<String> {
let mut req = self
.http_client
.get(format!("{}{endpoint}", server.address));
.get(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey);
if let Some(timeout) = timeout_ms.into() {
req = req.timeout(Duration::from_millis(timeout))
@@ -130,6 +138,7 @@ impl PeripheryClient {
let res = self
.http_client
.get(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey)
.send()
.await
.context(format!(
@@ -165,6 +174,7 @@ impl PeripheryClient {
let res = self
.http_client
.post(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey)
.json(body)
.send()
.await

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_types"
version = "0.1.11"
version = "0.1.16"
edition = "2021"
authors = ["MoghTech"]
description = "types for the mogh tech monitor"

View File

@@ -32,31 +32,40 @@ pub struct Build {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub server_id: String, // server which this image should be built on
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub server_id: Option<String>, // server which this image should be built on
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub aws_config: Option<AwsBuilderConfig>,
#[builder(default)]
pub version: Version,
// git related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub branch: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub github_account: Option<String>,
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_clone: Option<Command>,
// build related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub pre_build: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_build_args: Option<DockerBuildArgs>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_account: Option<String>,
@@ -128,8 +137,10 @@ impl Version {
#[diff(attr(#[derive(Debug, Serialize, PartialEq)]))]
pub struct DockerBuildArgs {
pub build_path: String,
#[builder(default)]
pub dockerfile_path: Option<String>,
#[serde(default)]
#[builder(default)]
pub build_args: Vec<EnvironmentVar>,
}
@@ -139,3 +150,40 @@ pub struct BuildVersionsReponse {
pub version: Version,
pub ts: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Diff, Builder)]
#[diff(attr(#[derive(Debug, Serialize, PartialEq)]))]
pub struct AwsBuilderConfig {
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub region: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub instance_type: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub ami_id: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub volume_gb: Option<i32>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub subnet_id: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub security_group_ids: Option<Vec<String>>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub key_pair_name: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub assign_public_ip: Option<bool>,
}

View File

@@ -23,8 +23,8 @@ pub struct CoreConfig {
#[serde(default = "default_core_port")]
pub port: u16,
// jwt config
pub jwt_secret: String,
#[serde(default = "default_jwt_valid_for")]
pub jwt_valid_for: Timelength,
@@ -42,20 +42,25 @@ pub struct CoreConfig {
// used to verify validity from github webhooks
pub github_webhook_secret: String,
// sent in auth header with req to periphery
pub passkey: String,
// integration with slack app
pub slack_url: Option<String>,
// enable login with local auth
pub local_auth: bool,
// github integration
pub mongo: MongoConfig,
#[serde(default)]
pub github_oauth: OauthCredentials,
// google integration
#[serde(default)]
pub google_oauth: OauthCredentials,
// mongo config
pub mongo: MongoConfig,
#[serde(default)]
pub aws: AwsBuilderConfig,
}
fn default_core_port() -> u16 {
@@ -93,6 +98,37 @@ fn default_core_mongo_db_name() -> String {
"monitor".to_string()
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct AwsBuilderConfig {
pub access_key_id: String,
pub secret_access_key: String,
pub default_ami_id: String,
pub default_subnet_id: String,
pub default_key_pair_name: String,
#[serde(default = "default_aws_region")]
pub default_region: String,
#[serde(default = "default_volume_gb")]
pub default_volume_gb: i32,
#[serde(default = "default_instance_type")]
pub default_instance_type: String,
#[serde(default)]
pub default_security_group_ids: Vec<String>,
#[serde(default)]
pub default_assign_public_ip: bool,
}
fn default_aws_region() -> String {
String::from("us-east-1")
}
fn default_volume_gb() -> i32 {
8
}
fn default_instance_type() -> String {
String::from("m5.2xlarge")
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PeripheryConfig {
#[serde(default = "default_periphery_port")]
@@ -104,6 +140,8 @@ pub struct PeripheryConfig {
#[serde(default)]
pub allowed_ips: Vec<IpAddr>,
#[serde(default)]
pub passkeys: Vec<String>,
#[serde(default)]
pub secrets: SecretsMap,
#[serde(default)]
pub github_accounts: GithubAccounts,

View File

@@ -32,31 +32,40 @@ pub struct Deployment {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "docker_run_args_diff_no_change")]))]
pub docker_run_args: DockerRunArgs,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub build_id: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub build_version: Option<Version>,
// deployment repo related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub branch: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub github_account: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_clone: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_pull: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo_mount: Option<Conversion>,
@@ -94,39 +103,49 @@ pub struct DeploymentActionState {
#[derive(Serialize, Deserialize, Debug, Clone, Diff, Builder)]
#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))]
pub struct DockerRunArgs {
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub image: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub ports: Vec<Conversion>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub volumes: Vec<Conversion>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub environment: Vec<EnvironmentVar>,
#[serde(default = "default_network")]
#[builder(default = "default_network()")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub network: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "restart_mode_diff_no_change")]))]
pub restart: RestartMode,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub post_image: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub container_user: Option<String>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub extra_args: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_account: Option<String>, // the username of the dockerhub account
}

View File

@@ -28,18 +28,23 @@ pub struct Group {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub builds: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub deployments: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub servers: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub procedures: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub groups: Vec<String>,

View File

@@ -38,6 +38,16 @@ pub const GITHUB_WEBHOOK_USER_ID: &str = "github";
#[typeshare]
pub type PermissionsMap = HashMap<String, PermissionLevel>;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CloneArgs {
pub name: String,
pub repo: Option<String>,
pub branch: Option<String>,
pub on_clone: Option<Command>,
pub on_pull: Option<Command>,
pub github_account: Option<GithubUsername>,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Diff)]
#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))]
@@ -96,7 +106,6 @@ pub enum Operation {
UpdateBuild,
DeleteBuild,
BuildBuild,
RecloneBuild,
// deployment
CreateDeployment,

View File

@@ -24,10 +24,12 @@ pub struct Procedure {
pub name: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub stages: Vec<ProcedureStage>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub webhook_branches: Vec<String>,
@@ -72,7 +74,6 @@ pub enum ProcedureOperation {
// build
BuildBuild,
RecloneBuild,
// deployment
DeployContainer,

View File

@@ -34,36 +34,45 @@ pub struct Server {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default = "true")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub enabled: bool,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub to_notify: Vec<String>, // slack users to notify
#[serde(default)]
#[builder(default = "true")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub auto_prune: bool,
#[serde(default = "default_cpu_alert")]
#[builder(default = "default_cpu_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f32_diff_no_change")]))]
pub cpu_alert: f32,
#[serde(default = "default_mem_alert")]
#[builder(default = "default_mem_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f64_diff_no_change")]))]
pub mem_alert: f64,
#[serde(default = "default_disk_alert")]
#[builder(default = "default_disk_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f64_diff_no_change")]))]
pub disk_alert: f64,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "timelength_diff_no_change")]))]
pub stats_interval: Timelength,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub region: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub instance_id: Option<String>,

View File

@@ -1,5 +1,5 @@
use crate::{
Build, BuildActionState, Deployment, DeploymentActionState, Group, PermissionLevel,
Build, BuildActionState, CloneArgs, Deployment, DeploymentActionState, Group, PermissionLevel,
PermissionsMap, Procedure, Server, ServerActionState,
};
@@ -68,3 +68,29 @@ impl Busy for BuildActionState {
self.building || self.recloning || self.updating
}
}
impl From<&Deployment> for CloneArgs {
fn from(d: &Deployment) -> Self {
CloneArgs {
name: d.name.clone(),
repo: d.repo.clone(),
branch: d.branch.clone(),
on_clone: d.on_clone.clone(),
on_pull: d.on_pull.clone(),
github_account: d.github_account.clone(),
}
}
}
impl From<&Build> for CloneArgs {
fn from(b: &Build) -> Self {
CloneArgs {
name: b.name.clone(),
repo: b.repo.clone(),
branch: b.branch.clone(),
on_clone: b.pre_build.clone(),
on_pull: None,
github_account: b.github_account.clone(),
}
}
}

View File

@@ -21,15 +21,22 @@ pub struct User {
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub username: String,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub enabled: bool,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub admin: bool,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub create_server_permissions: bool,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub create_build_permissions: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub avatar: Option<String>,

View File

@@ -1,6 +1,6 @@
[package]
name = "monitor_periphery"
version = "0.1.11"
version = "0.1.16"
edition = "2021"
authors = ["MoghTech"]
description = "monitor periphery binary | run monitor periphery as system daemon"
@@ -13,13 +13,11 @@ path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# helpers = { package = "monitor_helpers", path = "../lib/helpers" }
# types = { package = "monitor_types", path = "../lib/types" }
helpers = { package = "monitor_helpers", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.11" }
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.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
axum = { version = "0.6", features = ["ws"] }
tower = { version = "0.4", features = ["full"] }
dotenv = "0.15"
@@ -30,7 +28,7 @@ bollard = "0.13"
anyhow = "1.0"
envy = "0.4"
sysinfo = "0.27.7"
toml = "0.6"
toml = "0.7"
daemonize = "0.4"
clap = { version = "4.0", features = ["derive"] }
futures-util = "0.3"

View File

@@ -1,10 +1,7 @@
use axum::{routing::post, Extension, Json, Router};
use helpers::{
git::{self, CloneArgs},
handle_anyhow_error, to_monitor_name,
};
use helpers::{git, handle_anyhow_error, to_monitor_name};
use serde::Deserialize;
use types::{Command, Log};
use types::{CloneArgs, Command, Log};
use crate::{helpers::get_github_token, PeripheryConfigExtension};

View File

@@ -43,7 +43,8 @@ pub fn router(config: PeripheryConfigExtension, home_dir: HomeDirExtension) -> R
.nest("/build", build::router())
.nest("/image", image::router())
.layer(DockerClient::extension())
.layer(middleware::from_fn(guard_request))
.layer(middleware::from_fn(guard_request_by_ip))
.layer(middleware::from_fn(guard_request_by_passkey))
.layer(StatsClient::extension(
config.stats_polling_rate.to_string().parse().unwrap(),
))
@@ -51,7 +52,59 @@ pub fn router(config: PeripheryConfigExtension, home_dir: HomeDirExtension) -> R
.layer(home_dir)
}
async fn guard_request(
async fn guard_request_by_passkey(
req: Request<Body>,
next: Next<Body>,
) -> Result<Response, (StatusCode, String)> {
let config = req.extensions().get::<Arc<PeripheryConfig>>().ok_or((
StatusCode::INTERNAL_SERVER_ERROR,
"could not get periphery config".to_string(),
))?;
if config.passkeys.is_empty() {
return Ok(next.run(req).await);
}
let req_passkey = req.headers().get("authorization");
if req_passkey.is_none() {
return Err((
StatusCode::UNAUTHORIZED,
format!("request was not sent with passkey"),
));
}
let req_passkey = req_passkey
.unwrap()
.to_str()
.map_err(|e| {
(
StatusCode::UNAUTHORIZED,
format!("failed to get passkey from authorization header as str: {e:?}"),
)
})?
.to_string();
if config.passkeys.contains(&req_passkey) {
Ok(next.run(req).await)
} else {
let ConnectInfo(socket_addr) =
req.extensions().get::<ConnectInfo<SocketAddr>>().ok_or((
StatusCode::UNAUTHORIZED,
"could not get socket addr of request".to_string(),
))?;
let ip = socket_addr.ip();
let method = req.method().to_owned();
let uri = req.uri().to_owned();
let body = req
.extract::<Json<Value>, _>()
.await
.ok()
.map(|Json(body)| body);
eprintln!(
"{} | unauthorized request from {ip} (bad passkey) | method: {method} | uri: {uri} | body: {body:?}",
monitor_timestamp(),
);
Err((StatusCode::UNAUTHORIZED, format!("request passkey invalid")))
}
}
async fn guard_request_by_ip(
req: Request<Body>,
next: Next<Body>,
) -> Result<Response, (StatusCode, String)> {
@@ -78,9 +131,8 @@ async fn guard_request(
.ok()
.map(|Json(body)| body);
eprintln!(
"{} | unauthorized request from {ip} | method: {method} | uri: {uri} | body: {:?}",
monitor_timestamp(),
body
"{} | unauthorized request from {ip} | method: {method} | uri: {uri} | body: {body:?}",
monitor_timestamp()
);
Err((
StatusCode::UNAUTHORIZED,

11
readme.md Normal file
View File

@@ -0,0 +1,11 @@
# monitor 🦎
a tool to build and deploy software across many servers
## table of contents
1. [introduction](https://github.com/mbecker20/monitor/blob/main/docs/introduction.md)
2. [connecting servers](https://github.com/mbecker20/monitor/blob/main/docs/servers.md)
3. [building](https://github.com/mbecker20/monitor/blob/main/docs/builds.md)
4. [deploying](https://github.com/mbecker20/monitor/blob/main/docs/deployments.md)
5. [permissioning](https://github.com/mbecker20/monitor/blob/main/docs/permissions.md)

View File

@@ -30,11 +30,13 @@ async fn main() -> anyhow::Result<()> {
// container.container
// );
let update = test_build(&monitor).await?;
println!("build update:\n{update:#?}");
// let update = test_build(&monitor).await?;
// println!("build update:\n{update:#?}");
// test_updates(&monitor).await.unwrap();
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");

View File

@@ -3,8 +3,9 @@ use monitor_client::{
futures_util::StreamExt,
tokio_tungstenite::tungstenite::Message,
types::{
Build, Command, Conversion, Deployment, DeploymentWithContainerState, DockerBuildArgs,
Server, SystemStats, Update,
AwsBuilderConfigBuilder, Build, BuildBuilder, Command, Conversion, Deployment,
DeploymentWithContainerState, DockerBuildArgs, DockerBuildArgsBuilder, Server, SystemStats,
Update,
},
MonitorClient,
};
@@ -101,13 +102,9 @@ pub async fn test_build(monitor: &MonitorClient) -> anyhow::Result<Update> {
println!("created build. updating...");
build.repo = Some("mbecker20/monitor".to_string());
// build.branch = Some("");
build.on_clone = Some(Command {
path: ".".to_string(),
command: "yarn".to_string(),
});
build.pre_build = Some(Command {
path: "periphery".to_string(),
command: "yarn build".to_string(),
path: ".".to_string(),
command: "yarn && cd periphery && yarn build".to_string(),
});
build.docker_build_args = Some(DockerBuildArgs {
build_path: "periphery".to_string(),
@@ -129,3 +126,31 @@ pub async fn test_updates(monitor: &MonitorClient) -> anyhow::Result<()> {
println!("{build_updates:#?}");
Ok(())
}
pub async fn test_aws_build(monitor: &MonitorClient) -> anyhow::Result<()> {
let build = BuildBuilder::default()
.name("test_tram".to_string())
.repo(Some(String::from("SocialSocialTrading/master")))
.branch(Some(String::from("master")))
.docker_build_args(
DockerBuildArgsBuilder::default()
.build_path("backend".to_string())
.dockerfile_path("Dockerfile".to_string().into())
.build()
.context("failed to construct DockerBuildArgs struct")?
.into(),
)
.aws_config(
AwsBuilderConfigBuilder::default()
.build()
.context("failed to construct AwsBuilderConfig struct")?
.into(),
)
.build()
.context("failed to construct Build struct")?;
let build = monitor.create_full_build(&build).await?;
println!("{build:#?}\n");
let update = monitor.build(&build.id).await?;
println!("{update:#?}");
Ok(())
}