diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index c4899da37..000000000 --- a/.dockerignore +++ /dev/null @@ -1,14 +0,0 @@ -/target -readme.md -typeshare.toml -LICENSE -*.code-workspace - -*/node_modules -*/dist - -creds.toml -.core-repos -.repos -.stacks -.ssl \ No newline at end of file diff --git a/.gitignore b/.gitignore index 80333d821..2b3a40717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ target -/frontend/build -/lib/ts_client/build node_modules dist .env .env.development .DS_Store +/frontend/build +/lib/ts_client/build + creds.toml -.komodo \ No newline at end of file +.dev \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ff5ef93ac..28f056033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arc-swap" @@ -197,7 +197,7 @@ dependencies = [ "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -279,15 +279,15 @@ dependencies = [ [[package]] name = "aws-sdk-ec2" -version = "1.93.0" +version = "1.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9987ec31fe6dd2d81f2a534de8a752ac9dfc4b87305502807424296e6371af" +checksum = "facb3c68b989b2a8f4b26c4c0d8c5558b3a5d5335839bf2963b3932997a166d0" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -311,7 +311,7 @@ dependencies = [ "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -333,7 +333,7 @@ dependencies = [ "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -355,7 +355,7 @@ dependencies = [ "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -431,6 +431,15 @@ dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -443,9 +452,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -643,7 +652,7 @@ dependencies = [ "hyper 1.5.1", "hyper-util", "pin-project-lite", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", @@ -811,7 +820,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tokio-util", "tower-service", @@ -884,7 +893,7 @@ dependencies = [ [[package]] name = "cache" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "tokio", @@ -960,9 +969,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -970,9 +979,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -994,9 +1003,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" @@ -1025,7 +1034,7 @@ dependencies = [ [[package]] name = "command" -version = "1.16.12" +version = "1.16.13" dependencies = [ "komodo_client", "run_command", @@ -1464,9 +1473,9 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.16.12" +version = "1.16.13" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -1533,7 +1542,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.16.12" +version = "1.16.13" dependencies = [ "serror", ] @@ -1671,7 +1680,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "cache", @@ -2051,7 +2060,7 @@ dependencies = [ "http 1.1.0", "hyper 1.5.1", "hyper-util", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -2434,7 +2443,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "clap", @@ -2450,7 +2459,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "async_timing_util", @@ -2470,7 +2479,7 @@ dependencies = [ "serde_json", "serror", "strum", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tokio-tungstenite", "tokio-util", @@ -2481,7 +2490,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "async_timing_util", @@ -2520,7 +2529,8 @@ dependencies = [ "regex", "reqwest 0.12.9", "resolver_api", - "rustls 0.23.18", + "response", + "rustls 0.23.19", "serde", "serde_json", "serde_yaml", @@ -2542,12 +2552,11 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "async_timing_util", "axum", - "axum-extra", "axum-server", "bollard", "cache", @@ -2565,8 +2574,9 @@ dependencies = [ "merge_config_files", "periphery_client", "resolver_api", + "response", "run_command", - "rustls 0.23.18", + "rustls 0.23.19", "serde", "serde_json", "serror", @@ -2653,7 +2663,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "komodo_client", @@ -3090,16 +3100,16 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3cebff57f7dbd1255b44d8bddc2cebeb0ea677dbaa2e25a3070a91b318f660" +checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7" dependencies = [ "futures-core", "futures-sink", "js-sys", - "once_cell", "pin-project-lite", "thiserror 1.0.69", + "tracing", ] [[package]] @@ -3156,16 +3166,15 @@ checksum = "bc1b6902ff63b32ef6c489e8048c5e253e2e4a803ea3ea7e783914536eb15c52" [[package]] name = "opentelemetry_sdk" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b742c1cae4693792cc564e58d75a2a0ba29421a34a85b50da92efa89ecb2bc" +checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8" dependencies = [ "async-trait", "futures-channel", "futures-executor", "futures-util", "glob", - "once_cell", "opentelemetry", "percent-encoding", "rand", @@ -3351,7 +3360,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.16.12" +version = "1.16.13" dependencies = [ "anyhow", "komodo_client", @@ -3499,9 +3508,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustls 0.23.19", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -3517,10 +3526,10 @@ dependencies = [ "rand", "ring 0.17.8", "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -3727,7 +3736,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -3825,28 +3834,35 @@ dependencies = [ [[package]] name = "resolver_api" -version = "1.1.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86d2e3a6e01f92f8f964bc16a5e579028c02fa625612e5e1f159a6474e47969" +checksum = "3faf1143e798058d8287e5a019fc8a46542c1ef152a29bc3b3d9bf886363016b" dependencies = [ - "anyhow", "resolver_api_derive", - "serde", - "serde_json", - "thiserror 1.0.69", ] [[package]] name = "resolver_api_derive" -version = "1.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35ef0d79735ffcdf944895605c107b7f04ea097ce5a39d3191f9b2d82f73f7b" +checksum = "e45ff368040e7f3787e97e6f3a97718ca08e0553587747bd4d0bf3ca2b899963" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] +[[package]] +name = "response" +version = "1.16.13" +dependencies = [ + "anyhow", + "axum", + "serde", + "serde_json", + "serror", +] + [[package]] name = "retry-policies" version = "0.1.2" @@ -3991,9 +4007,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "aws-lc-rs", "log", @@ -4358,9 +4374,9 @@ dependencies = [ [[package]] name = "serror" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715a997753611604c722411afbe11f83a89e00e39323dc9016db96a86cc04fc8" +checksum = "47348b77230bc82d8c6f6452df44988f73e42a3bd68098c6236fcd656605b5e6" dependencies = [ "anyhow", "axum", @@ -4619,9 +4635,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +checksum = "948512566b1895f93b1592c7574baeb2de842f224f2aab158799ecadb8ebbb46" dependencies = [ "core-foundation-sys", "libc", @@ -4705,11 +4721,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.6", ] [[package]] @@ -4725,9 +4741,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -4802,9 +4818,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -4845,7 +4861,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -4875,9 +4891,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -5041,9 +5057,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5103,9 +5119,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -5113,9 +5129,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "serde", @@ -5475,7 +5491,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 91e888963..43e9bc168 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,23 +8,20 @@ members = [ ] [workspace.package] -version = "1.16.12" +version = "1.16.13" edition = "2021" authors = ["mbecker20 "] license = "GPL-3.0-or-later" repository = "https://github.com/mbecker20/komodo" homepage = "https://komo.do" -[patch.crates-io] -# komodo_client = { path = "client/core/rs" } - [workspace.dependencies] # LOCAL -# komodo_client = "1.15.6" komodo_client = { path = "client/core/rs" } periphery_client = { path = "client/periphery/rs" } environment_file = { path = "lib/environment_file" } formatting = { path = "lib/formatting" } +response = { path = "lib/response" } command = { path = "lib/command" } logger = { path = "lib/logger" } cache = { path = "lib/cache" } @@ -32,7 +29,7 @@ git = { path = "lib/git" } # MOGH run_command = { version = "0.0.6", features = ["async_tokio"] } -serror = { version = "0.4.7", default-features = false } +serror = { version = "0.4.8", default-features = false } slack = { version = "0.3.0", package = "slack_client_rs", default-features = false, features = ["rustls"] } derive_default_builder = "0.1.8" derive_empty_traits = "0.1.0" @@ -41,15 +38,15 @@ async_timing_util = "1.0.0" partial_derive2 = "0.4.3" derive_variants = "1.0.0" mongo_indexed = "2.0.1" -resolver_api = "1.1.1" +resolver_api = "3.0.0" toml_pretty = "1.1.2" mungos = "1.1.0" svi = "1.0.1" # ASYNC reqwest = { version = "0.12.9", default-features = false, features = ["json", "rustls-tls"] } -tokio = { version = "1.41.1", features = ["full"] } -tokio-util = "0.7.12" +tokio = { version = "1.42.0", features = ["full"] } +tokio-util = "0.7.13" futures = "0.3.31" futures-util = "0.3.31" @@ -69,20 +66,20 @@ serde_yaml = "0.9.34" toml = "0.8.19" # ERROR -anyhow = "1.0.93" -thiserror = "2.0.3" +anyhow = "1.0.94" +thiserror = "2.0.6" # LOGGING opentelemetry-otlp = { version = "0.27.0", features = ["tls-roots", "reqwest-rustls"] } -opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] } -tracing-subscriber = { version = "0.3.18", features = ["json"] } +opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] } +tracing-subscriber = { version = "0.3.19", features = ["json"] } opentelemetry-semantic-conventions = "0.27.0" tracing-opentelemetry = "0.28.0" -opentelemetry = "0.27.0" -tracing = "0.1.40" +opentelemetry = "0.27.1" +tracing = "0.1.41" # CONFIG -clap = { version = "4.5.21", features = ["derive"] } +clap = { version = "4.5.23", features = ["derive"] } dotenvy = "0.15.7" envy = "0.4.2" @@ -93,7 +90,7 @@ urlencoding = "2.1.3" nom_pem = "4.0.0" bcrypt = "0.16.0" base64 = "0.22.1" -rustls = "0.23.18" +rustls = "0.23.19" hmac = "0.12.1" sha2 = "0.10.8" rand = "0.8.5" @@ -102,11 +99,11 @@ hex = "0.4.3" # SYSTEM bollard = "0.18.1" -sysinfo = "0.32.0" +sysinfo = "0.33.0" # CLOUD aws-config = "1.5.10" -aws-sdk-ec2 = "1.91.0" +aws-sdk-ec2 = "1.95.0" # MISC derive_builder = "0.20.2" diff --git a/bin/cli/Cargo.toml b/bin/cli/Cargo.toml index f3f3dd67e..7ab8f3c06 100644 --- a/bin/cli/Cargo.toml +++ b/bin/cli/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [dependencies] # local +# komodo_client = "1.16.12" komodo_client.workspace = true # external tracing-subscriber.workspace = true diff --git a/bin/core/Cargo.toml b/bin/core/Cargo.toml index cec4c2bfa..180bd91bc 100644 --- a/bin/core/Cargo.toml +++ b/bin/core/Cargo.toml @@ -19,6 +19,7 @@ komodo_client = { workspace = true, features = ["mongo"] } periphery_client.workspace = true environment_file.workspace = true formatting.workspace = true +response.workspace = true command.workspace = true logger.workspace = true cache.workspace = true diff --git a/bin/core/multi-arch.Dockerfile b/bin/core/multi-arch.Dockerfile index 411cf1538..326ab35c5 100644 --- a/bin/core/multi-arch.Dockerfile +++ b/bin/core/multi-arch.Dockerfile @@ -47,4 +47,4 @@ LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo LABEL org.opencontainers.image.description="Komodo Core" LABEL org.opencontainers.image.licenses=GPL-3.0 -ENTRYPOINT [ "core" ] \ No newline at end of file +CMD [ "core" ] \ No newline at end of file diff --git a/bin/core/single-arch.Dockerfile b/bin/core/single-arch.Dockerfile index 8a3a22ba2..4db635437 100644 --- a/bin/core/single-arch.Dockerfile +++ b/bin/core/single-arch.Dockerfile @@ -41,4 +41,4 @@ LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo LABEL org.opencontainers.image.description="Komodo Core" LABEL org.opencontainers.image.licenses=GPL-3.0 -ENTRYPOINT [ "core" ] \ No newline at end of file +CMD [ "core" ] \ No newline at end of file diff --git a/bin/core/src/api/auth.rs b/bin/core/src/api/auth.rs index 91ca0ef4e..97a03654e 100644 --- a/bin/core/src/api/auth.rs +++ b/bin/core/src/api/auth.rs @@ -1,13 +1,12 @@ use std::{sync::OnceLock, time::Instant}; -use anyhow::anyhow; use axum::{http::HeaderMap, routing::post, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use derive_variants::{EnumVariants, ExtractVariant}; use komodo_client::{api::auth::*, entities::user::User}; -use reqwest::StatusCode; -use resolver_api::{derive::Resolver, Resolve, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; -use serror::{AddStatusCode, Json}; +use serror::Json; use typeshare::typeshare; use uuid::Uuid; @@ -20,13 +19,21 @@ use crate::{ }, config::core_config, helpers::query::get_user, - state::{jwt_client, State}, + state::jwt_client, }; +pub struct AuthArgs { + pub headers: HeaderMap, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(HeaderMap)] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, +)] +#[args(AuthArgs)] +#[response(Response)] +#[error(serror::Error)] +#[variant_derive(Debug)] #[serde(tag = "type", content = "params")] #[allow(clippy::enum_variant_names, clippy::large_enum_variant)] pub enum AuthRequest { @@ -66,27 +73,20 @@ pub fn router() -> Router { async fn handler( headers: HeaderMap, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); - debug!("/auth request {req_id} | METHOD: {}", request.req_type()); - let res = State.resolve_request(request, headers).await.map_err( - |e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }, + debug!( + "/auth request {req_id} | METHOD: {:?}", + request.extract_variant() ); + let res = request.resolve(&AuthArgs { headers }).await; if let Err(e) = &res { - debug!("/auth request {req_id} | error: {e:#}"); + debug!("/auth request {req_id} | error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/auth request {req_id} | resolve time: {elapsed:?}"); - Ok(( - TypedHeader(ContentType::json()), - res.status_code(StatusCode::UNAUTHORIZED)?, - )) + res.map(|res| res.0) } fn login_options_reponse() -> &'static GetLoginOptionsResponse { @@ -112,38 +112,34 @@ fn login_options_reponse() -> &'static GetLoginOptionsResponse { }) } -impl Resolve for State { +impl Resolve for GetLoginOptions { #[instrument(name = "GetLoginOptions", level = "debug", skip(self))] async fn resolve( - &self, - _: GetLoginOptions, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { Ok(*login_options_reponse()) } } -impl Resolve for State { +impl Resolve for ExchangeForJwt { #[instrument(name = "ExchangeForJwt", level = "debug", skip(self))] async fn resolve( - &self, - ExchangeForJwt { token }: ExchangeForJwt, - _: HeaderMap, - ) -> anyhow::Result { - let jwt = jwt_client().redeem_exchange_token(&token).await?; - let res = ExchangeForJwtResponse { jwt }; - Ok(res) + self, + _: &AuthArgs, + ) -> serror::Result { + let jwt = jwt_client().redeem_exchange_token(&self.token).await?; + Ok(ExchangeForJwtResponse { jwt }) } } -impl Resolve for State { +impl Resolve for GetUser { #[instrument(name = "GetUser", level = "debug", skip(self))] async fn resolve( - &self, - GetUser {}: GetUser, - headers: HeaderMap, - ) -> anyhow::Result { - let user_id = get_user_id_from_headers(&headers).await?; - get_user(&user_id).await + self, + AuthArgs { headers }: &AuthArgs, + ) -> serror::Result { + let user_id = get_user_id_from_headers(headers).await?; + Ok(get_user(&user_id).await?) } } diff --git a/bin/core/src/api/execute/action.rs b/bin/core/src/api/execute/action.rs index 05af563cc..1a9cab3b9 100644 --- a/bin/core/src/api/execute/action.rs +++ b/bin/core/src/api/execute/action.rs @@ -13,11 +13,8 @@ use komodo_client::{ user::{CreateApiKey, CreateApiKeyResponse, DeleteApiKey}, }, entities::{ - action::Action, - config::core::CoreConfig, - permission::PermissionLevel, - update::Update, - user::{action_user, User}, + action::Action, config::core::CoreConfig, + permission::PermissionLevel, update::Update, user::action_user, }, }; use mungos::{by_id::update_one_by_id, mongodb::bson::to_document}; @@ -25,7 +22,7 @@ use resolver_api::Resolve; use tokio::fs; use crate::{ - api::execute::ExecuteRequest, + api::{execute::ExecuteRequest, user::UserArgs}, config::core_config, helpers::{ interpolate::{ @@ -37,9 +34,11 @@ use crate::{ update::update_update, }, resource::{self, refresh_action_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; +use super::ExecuteArgs; + impl super::BatchExecute for BatchRunAction { type Resource = Action; fn single_request(action: String) -> ExecuteRequest { @@ -47,26 +46,27 @@ impl super::BatchExecute for BatchRunAction { } } -impl Resolve for State { +impl Resolve for BatchRunAction { #[instrument(name = "BatchRunAction", skip(self, user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunAction { pattern }: BatchRunAction, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunAction", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunAction { + #[instrument(name = "RunAction", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunAction { action }: RunAction, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut action = resource::get_check_permissions::( - &action, + &self.action, &user, PermissionLevel::Execute, ) @@ -83,17 +83,18 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.running = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; - let CreateApiKeyResponse { key, secret } = State - .resolve( - CreateApiKey { - name: update.id.clone(), - expires: 0, - }, - action_user().to_owned(), - ) - .await?; + let CreateApiKeyResponse { key, secret } = CreateApiKey { + name: update.id.clone(), + expires: 0, + } + .resolve(&UserArgs { + user: action_user().to_owned(), + }) + .await?; let contents = &mut action.config.file_contents; @@ -133,12 +134,15 @@ impl Resolve for State { cleanup_run(file + ".js", &path).await; - if let Err(e) = State - .resolve(DeleteApiKey { key }, action_user().to_owned()) + if let Err(e) = (DeleteApiKey { key }) + .resolve(&UserArgs { + user: action_user().to_owned(), + }) .await { warn!( - "Failed to delete API key after action execution | {e:#}" + "Failed to delete API key after action execution | {:#}", + e.error ); }; @@ -171,7 +175,7 @@ async fn interpolate( update: &mut Update, key: String, secret: String, -) -> anyhow::Result> { +) -> serror::Result> { let mut vars_and_secrets = get_variables_and_secrets().await?; vars_and_secrets diff --git a/bin/core/src/api/execute/build.rs b/bin/core/src/api/execute/build.rs index 6f63ce243..6cc97ee86 100644 --- a/bin/core/src/api/execute/build.rs +++ b/bin/core/src/api/execute/build.rs @@ -17,7 +17,7 @@ use komodo_client::{ komodo_timestamp, permission::PermissionLevel, update::{Log, Update}, - user::{auto_redeploy_user, User}, + user::auto_redeploy_user, }, }; use mungos::{ @@ -49,10 +49,10 @@ use crate::{ update::{init_execution_update, update_update}, }, resource::{self, refresh_build_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchRunBuild { type Resource = Build; @@ -61,26 +61,27 @@ impl super::BatchExecute for BatchRunBuild { } } -impl Resolve for State { - #[instrument(name = "BatchRunBuild", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchRunBuild { + #[instrument(name = "BatchRunBuild", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunBuild { pattern }: BatchRunBuild, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunBuild { + #[instrument(name = "RunBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunBuild { build }: RunBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut build = resource::get_check_permissions::( - &build, + &self.build, &user, PermissionLevel::Execute, ) @@ -88,7 +89,7 @@ impl Resolve for State { let mut vars_and_secrets = get_variables_and_secrets().await?; if build.config.builder_id.is_empty() { - return Err(anyhow!("Must attach builder to RunBuild")); + return Err(anyhow!("Must attach builder to RunBuild").into()); } // get the action state for the build (or insert default). @@ -103,6 +104,9 @@ impl Resolve for State { if build.config.auto_increment_version { build.config.version.increment(); } + + let mut update = update.clone(); + update.version = build.config.version; update_update(update.clone()).await?; @@ -408,7 +412,7 @@ impl Resolve for State { }); } - Ok(update) + Ok(update.clone()) } } @@ -418,7 +422,7 @@ async fn handle_early_return( build_id: String, build_name: String, is_cancel: bool, -) -> anyhow::Result { +) -> serror::Result { update.finalize(); // Need to manually update the update before cache refresh, // and before broadcast with add_update. @@ -456,7 +460,7 @@ async fn handle_early_return( send_alerts(&[alert]).await }); } - Ok(update) + Ok(update.clone()) } pub async fn validate_cancel_build( @@ -495,25 +499,28 @@ pub async fn validate_cancel_build( match (latest_build, latest_cancel) { (Some(build), Some(cancel)) => { if cancel.start_ts > build.start_ts { - return Err(anyhow!("Build has already been cancelled")); + return Err( + anyhow!("Build has already been cancelled"), + ); } } - (None, _) => return Err(anyhow!("No build in progress")), + (None, _) => { + return Err(anyhow!("No build in progress")) + } _ => {} }; } Ok(()) } -impl Resolve for State { - #[instrument(name = "CancelBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CancelBuild { + #[instrument(name = "CancelBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CancelBuild { build }: CancelBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let build = resource::get_check_permissions::( - &build, + &self.build, &user, PermissionLevel::Execute, ) @@ -527,9 +534,11 @@ impl Resolve for State { .and_then(|s| s.get().ok().map(|s| s.building)) .unwrap_or_default() { - return Err(anyhow!("Build is not building.")); + return Err(anyhow!("Build is not building.").into()); } + let mut update = update.clone(); + update.push_simple_log( "cancel triggered", "the build cancel has been triggered", @@ -593,16 +602,13 @@ async fn handle_post_build_redeploy(build_id: &str) { let user = auto_redeploy_user().to_owned(); let res = async { let update = init_execution_update(&req, &user).await?; - State - .resolve( - Deploy { - deployment: deployment.id.clone(), - stop_signal: None, - stop_time: None, - }, - (user, update), - ) - .await + Deploy { + deployment: deployment.id.clone(), + stop_signal: None, + stop_time: None, + } + .resolve(&ExecuteArgs { user, update }) + .await } .await; Some((deployment.id.clone(), res)) @@ -616,7 +622,10 @@ async fn handle_post_build_redeploy(build_id: &str) { continue; }; if let Err(e) = res { - warn!("failed post build redeploy for deployment {id}: {e:#}"); + warn!( + "failed post build redeploy for deployment {id}: {:#}", + e.error + ); } } } @@ -636,14 +645,17 @@ async fn validate_account_extract_registry_token( }, .. }: &Build, -) -> anyhow::Result> { +) -> serror::Result> { if domain.is_empty() { return Ok(None); } if account.is_empty() { - return Err(anyhow!( - "Must attach account to use registry provider {domain}" - )); + return Err( + anyhow!( + "Must attach account to use registry provider {domain}" + ) + .into(), + ); } let registry_token = registry_token(domain, account).await.with_context( diff --git a/bin/core/src/api/execute/deployment.rs b/bin/core/src/api/execute/deployment.rs index cbf8d9a89..d320aafd3 100644 --- a/bin/core/src/api/execute/deployment.rs +++ b/bin/core/src/api/execute/deployment.rs @@ -35,10 +35,10 @@ use crate::{ }, monitor::update_cache_for_server, resource, - state::{action_states, State}, + state::action_states, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchDeploy { type Resource = Deployment; @@ -51,14 +51,16 @@ impl super::BatchExecute for BatchDeploy { } } -impl Resolve for State { - #[instrument(name = "BatchDeploy", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeploy { + #[instrument(name = "BatchDeploy", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeploy { pattern }: BatchDeploy, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } @@ -87,19 +89,14 @@ async fn setup_deployment_execution( Ok((deployment, server)) } -impl Resolve for State { - #[instrument(name = "Deploy", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for Deploy { + #[instrument(name = "Deploy", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - Deploy { - deployment, - stop_signal, - stop_time, - }: Deploy, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (mut deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -112,6 +109,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.deploying = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -237,8 +236,8 @@ impl Resolve for State { match periphery_client(&server)? .request(api::container::Deploy { deployment, - stop_signal, - stop_time, + stop_signal: self.stop_signal, + stop_time: self.stop_time, registry_token, replacers: secret_replacers.into_iter().collect(), }) @@ -378,14 +377,14 @@ pub async fn pull_deployment_inner( res } -impl Resolve for State { +impl Resolve for PullDeployment { + #[instrument(name = "PullDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PullDeployment { deployment }: PullDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -398,6 +397,7 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -411,15 +411,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartDeployment { + #[instrument(name = "StartDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartDeployment { deployment }: StartDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -432,6 +431,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.starting = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -457,15 +458,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RestartDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartDeployment { + #[instrument(name = "RestartDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartDeployment { deployment }: RestartDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -478,6 +478,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.restarting = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -505,15 +507,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseDeployment { + #[instrument(name = "PauseDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseDeployment { deployment }: PauseDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -526,6 +527,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -551,15 +554,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseDeployment { + #[instrument(name = "UnpauseDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseDeployment { deployment }: UnpauseDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, &user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -572,6 +574,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.unpausing = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -599,19 +603,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopDeployment { + #[instrument(name = "StopDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopDeployment { - deployment, - signal, - time, - }: StopDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, &user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -624,16 +623,20 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.stopping = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let log = match periphery_client(&server)? .request(api::container::StopContainer { name: deployment.name, - signal: signal + signal: self + .signal .unwrap_or(deployment.config.termination_signal) .into(), - time: time + time: self + .time .unwrap_or(deployment.config.termination_timeout) .into(), }) @@ -666,31 +669,30 @@ impl super::BatchExecute for BatchDestroyDeployment { } } -impl Resolve for State { - #[instrument(name = "BatchDestroyDeployment", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDestroyDeployment { + #[instrument(name = "BatchDestroyDeployment", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDestroyDeployment { pattern }: BatchDestroyDeployment, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user) - .await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::( + &self.pattern, + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DestroyDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyDeployment { + #[instrument(name = "DestroyDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyDeployment { - deployment, - signal, - time, - }: DestroyDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -703,16 +705,20 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.destroying = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let log = match periphery_client(&server)? .request(api::container::RemoveContainer { name: deployment.name, - signal: signal + signal: self + .signal .unwrap_or(deployment.config.termination_signal) .into(), - time: time + time: self + .time .unwrap_or(deployment.config.termination_timeout) .into(), }) diff --git a/bin/core/src/api/execute/mod.rs b/bin/core/src/api/execute/mod.rs index 714dd11f2..eb31882bd 100644 --- a/bin/core/src/api/execute/mod.rs +++ b/bin/core/src/api/execute/mod.rs @@ -1,6 +1,6 @@ -use std::time::Instant; +use std::{pin::Pin, time::Instant}; -use anyhow::{anyhow, Context}; +use anyhow::Context; use axum::{middleware, routing::post, Extension, Router}; use axum_extra::{headers::ContentType, TypedHeader}; use derive_variants::{EnumVariants, ExtractVariant}; @@ -15,7 +15,8 @@ use komodo_client::{ }, }; use mungos::by_id::find_one_by_id; -use resolver_api::{derive::Resolver, Resolver}; +use resolver_api::Resolve; +use response::JsonString; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; @@ -25,7 +26,7 @@ use crate::{ auth::auth_request, helpers::update::{init_execution_update, update_update}, resource::{list_full_for_user_using_pattern, KomodoResource}, - state::{db_client, State}, + state::db_client, }; mod action; @@ -42,13 +43,19 @@ pub use { deployment::pull_deployment_inner, stack::pull_stack_inner, }; +pub struct ExecuteArgs { + pub user: User, + pub update: Update, +} + #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] #[variant_derive(Debug)] -#[resolver_target(State)] -#[resolver_args((User, Update))] +#[args(ExecuteArgs)] +#[response(JsonString)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] pub enum ExecuteRequest { // ==== SERVER ==== @@ -153,64 +160,73 @@ pub enum ExecutionResult { Batch(String), } -pub async fn inner_handler( +pub fn inner_handler( request: ExecuteRequest, user: User, -) -> anyhow::Result { - let req_id = Uuid::new_v4(); +) -> Pin< + Box< + dyn std::future::Future> + + Send, + >, +> { + Box::pin(async move { + let req_id = Uuid::new_v4(); - // need to validate no cancel is active before any update is created. - build::validate_cancel_build(&request).await?; + // need to validate no cancel is active before any update is created. + build::validate_cancel_build(&request).await?; - let update = init_execution_update(&request, &user).await?; + let update = init_execution_update(&request, &user).await?; - // This will be the case for the Batch exections, - // they don't have their own updates. - // The batch calls also call "inner_handler" themselves, - // and in their case will spawn tasks, so that isn't necessary - // here either. - if update.operation == Operation::None { - return Ok(ExecutionResult::Batch( - task(req_id, request, user, update).await?, - )); - } - - let handle = - tokio::spawn(task(req_id, request, user, update.clone())); - - tokio::spawn({ - let update_id = update.id.clone(); - async move { - let log = match handle.await { - Ok(Err(e)) => { - warn!("/execute request {req_id} task error: {e:#}",); - Log::error("task error", format_serror(&e.into())) - } - Err(e) => { - warn!("/execute request {req_id} spawn error: {e:?}",); - Log::error("spawn error", format!("{e:#?}")) - } - _ => return, - }; - let res = async { - let mut update = - find_one_by_id(&db_client().updates, &update_id) - .await - .context("failed to query to db")? - .context("no update exists with given id")?; - update.logs.push(log); - update.finalize(); - update_update(update).await - } - .await; - - if let Err(e) = res { - warn!("failed to update update with task error log | {e:#}"); - } + // This will be the case for the Batch exections, + // they don't have their own updates. + // The batch calls also call "inner_handler" themselves, + // and in their case will spawn tasks, so that isn't necessary + // here either. + if update.operation == Operation::None { + return Ok(ExecutionResult::Batch( + task(req_id, request, user, update).await?, + )); } - }); - Ok(ExecutionResult::Single(update)) + let handle = + tokio::spawn(task(req_id, request, user, update.clone())); + + tokio::spawn({ + let update_id = update.id.clone(); + async move { + let log = match handle.await { + Ok(Err(e)) => { + warn!("/execute request {req_id} task error: {e:#}",); + Log::error("task error", format_serror(&e.into())) + } + Err(e) => { + warn!("/execute request {req_id} spawn error: {e:?}",); + Log::error("spawn error", format!("{e:#?}")) + } + _ => return, + }; + let res = async { + let mut update = + find_one_by_id(&db_client().updates, &update_id) + .await + .context("failed to query to db")? + .context("no update exists with given id")?; + update.logs.push(log); + update.finalize(); + update_update(update).await + } + .await; + + if let Err(e) = res { + warn!( + "failed to update update with task error log | {e:#}" + ); + } + } + }); + + Ok(ExecutionResult::Single(update)) + }) } #[instrument( @@ -231,15 +247,14 @@ async fn task( info!("/execute request {req_id} | user: {}", user.username); let timer = Instant::now(); - let res = State - .resolve_request(request, (user, update)) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = match request.resolve(&ExecuteArgs { user, update }).await + { + Err(e) => Err(e.error), + Ok(JsonString::Err(e)) => Err( + anyhow::Error::from(e).context("failed to serialize response"), + ), + Ok(JsonString::Ok(res)) => Ok(res), + }; if let Err(e) = &res { warn!("/execute request {req_id} error: {e:#}"); diff --git a/bin/core/src/api/execute/procedure.rs b/bin/core/src/api/execute/procedure.rs index 28a7d5db5..a0f5d3478 100644 --- a/bin/core/src/api/execute/procedure.rs +++ b/bin/core/src/api/execute/procedure.rs @@ -17,10 +17,10 @@ use tokio::sync::Mutex; use crate::{ helpers::{procedure::execute_procedure, update::update_update}, resource::{self, refresh_procedure_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchRunProcedure { type Resource = Procedure; @@ -29,25 +29,29 @@ impl super::BatchExecute for BatchRunProcedure { } } -impl Resolve for State { - #[instrument(name = "BatchRunProcedure", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchRunProcedure { + #[instrument(name = "BatchRunProcedure", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunProcedure { pattern }: BatchRunProcedure, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunProcedure", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunProcedure { + #[instrument(name = "RunProcedure", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunProcedure { procedure }: RunProcedure, - (user, update): (User, Update), - ) -> anyhow::Result { - resolve_inner(procedure, user, update).await + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + Ok( + resolve_inner(self.procedure, user.clone(), update.clone()) + .await?, + ) } } diff --git a/bin/core/src/api/execute/repo.rs b/bin/core/src/api/execute/repo.rs index fd924adce..1eeb4781e 100644 --- a/bin/core/src/api/execute/repo.rs +++ b/bin/core/src/api/execute/repo.rs @@ -12,7 +12,6 @@ use komodo_client::{ repo::Repo, server::Server, update::{Log, Update}, - user::User, }, }; use mungos::{ @@ -28,6 +27,7 @@ use tokio_util::sync::CancellationToken; use crate::{ alert::send_alerts, + api::write::WriteArgs, helpers::{ builder::{cleanup_builder_instance, get_builder_periphery}, channel::repo_cancel_channel, @@ -42,10 +42,10 @@ use crate::{ update::update_update, }, resource::{self, refresh_repo_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchCloneRepo { type Resource = Repo; @@ -54,27 +54,28 @@ impl super::BatchExecute for BatchCloneRepo { } } -impl Resolve for State { - #[instrument(name = "BatchCloneRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchCloneRepo { + #[instrument(name = "BatchCloneRepo", skip( user), fields(user_id = user.id))] async fn resolve( - &self, - BatchCloneRepo { pattern }: BatchCloneRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CloneRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CloneRepo { + #[instrument(name = "CloneRepo", skip( user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CloneRepo { repo }: CloneRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -88,10 +89,11 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.cloning = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; if repo.config.server_id.is_empty() { - return Err(anyhow!("repo has no server attached")); + return Err(anyhow!("repo has no server attached").into()); } let git_token = git_token( @@ -141,9 +143,10 @@ impl Resolve for State { update_last_pulled_time(&repo.name).await; } - if let Err(e) = State - .resolve(RefreshRepoCache { repo: repo.id }, user) + if let Err(e) = (RefreshRepoCache { repo: repo.id }) + .resolve(&WriteArgs { user: user.clone() }) .await + .map_err(|e| e.error) .context("Failed to refresh repo cache") { update.push_error_log( @@ -163,26 +166,27 @@ impl super::BatchExecute for BatchPullRepo { } } -impl Resolve for State { - #[instrument(name = "BatchPullRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchPullRepo { + #[instrument(name = "BatchPullRepo", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchPullRepo { pattern }: BatchPullRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "PullRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PullRepo { + #[instrument(name = "PullRepo", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PullRepo { repo }: PullRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, + &self.repo, &user, PermissionLevel::Execute, ) @@ -197,10 +201,12 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; if repo.config.server_id.is_empty() { - return Err(anyhow!("repo has no server attached")); + return Err(anyhow!("repo has no server attached").into()); } let git_token = git_token( @@ -254,9 +260,10 @@ impl Resolve for State { update_last_pulled_time(&repo.name).await; } - if let Err(e) = State - .resolve(RefreshRepoCache { repo: repo.id }, user) + if let Err(e) = (RefreshRepoCache { repo: repo.id }) + .resolve(&WriteArgs { user: user.clone() }) .await + .map_err(|e| e.error) .context("Failed to refresh repo cache") { update.push_error_log( @@ -272,7 +279,7 @@ impl Resolve for State { #[instrument(skip_all, fields(update_id = update.id))] async fn handle_server_update_return( update: Update, -) -> anyhow::Result { +) -> serror::Result { // Need to manually update the update before cache refresh, // and before broadcast with add_update. // The Err case of to_document should be unreachable, @@ -314,33 +321,34 @@ impl super::BatchExecute for BatchBuildRepo { } } -impl Resolve for State { - #[instrument(name = "BatchBuildRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchBuildRepo { + #[instrument(name = "BatchBuildRepo", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchBuildRepo { pattern }: BatchBuildRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "BuildRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for BuildRepo { + #[instrument(name = "BuildRepo", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - BuildRepo { repo }: BuildRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; if repo.config.builder_id.is_empty() { - return Err(anyhow!("Must attach builder to BuildRepo")); + return Err(anyhow!("Must attach builder to BuildRepo").into()); } // get the action state for the repo (or insert default). @@ -352,6 +360,7 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.building = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; let git_token = git_token( @@ -429,7 +438,8 @@ impl Resolve for State { return handle_builder_early_return( update, repo.id, repo.name, false, ) - .await; + .await + .map_err(Into::into); } }; @@ -547,7 +557,7 @@ async fn handle_builder_early_return( repo_id: String, repo_name: String, is_cancel: bool, -) -> anyhow::Result { +) -> serror::Result { update.finalize(); // Need to manually update the update before cache refresh, // and before broadcast with add_update. @@ -635,16 +645,15 @@ pub async fn validate_cancel_repo_build( Ok(()) } -impl Resolve for State { - #[instrument(name = "CancelRepoBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CancelRepoBuild { + #[instrument(name = "CancelRepoBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CancelRepoBuild { repo }: CancelRepoBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -657,9 +666,11 @@ impl Resolve for State { .and_then(|s| s.get().ok().map(|s| s.building)) .unwrap_or_default() { - return Err(anyhow!("Repo is not building.")); + return Err(anyhow!("Repo is not building.").into()); } + let mut update = update.clone(); + update.push_simple_log( "cancel triggered", "the repo build cancel has been triggered", diff --git a/bin/core/src/api/execute/server.rs b/bin/core/src/api/execute/server.rs index c5642400d..2e29c71d1 100644 --- a/bin/core/src/api/execute/server.rs +++ b/bin/core/src/api/execute/server.rs @@ -7,7 +7,6 @@ use komodo_client::{ permission::PermissionLevel, server::Server, update::{Log, Update}, - user::User, }, }; use periphery_client::api; @@ -17,19 +16,20 @@ use crate::{ helpers::{periphery_client, update::update_update}, monitor::update_cache_for_server, resource, - state::{action_states, State}, + state::action_states, }; -impl Resolve for State { +use super::ExecuteArgs; + +impl Resolve for StartContainer { #[instrument(name = "StartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartContainer { server, container }: StartContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -45,13 +45,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.starting_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::StartContainer { name: container }) + .request(api::container::StartContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -71,16 +75,15 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RestartContainer { #[instrument(name = "RestartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartContainer { server, container }: RestartContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -96,13 +99,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.restarting_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::RestartContainer { name: container }) + .request(api::container::RestartContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -124,16 +131,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseContainer { + #[instrument(name = "PauseContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseContainer { server, container }: PauseContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -149,13 +155,17 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::PauseContainer { name: container }) + .request(api::container::PauseContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -175,16 +185,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseContainer { + #[instrument(name = "UnpauseContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseContainer { server, container }: UnpauseContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -200,13 +209,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.unpausing_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::UnpauseContainer { name: container }) + .request(api::container::UnpauseContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -228,21 +241,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopContainer { + #[instrument(name = "StopContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopContainer { - server, - container, - signal, - time, - }: StopContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -258,6 +265,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.stopping_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -265,9 +274,9 @@ impl Resolve for State { let log = match periphery .request(api::container::StopContainer { - name: container, - signal, - time, + name: self.container, + signal: self.signal, + time: self.time, }) .await { @@ -288,21 +297,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DestroyContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyContainer { + #[instrument(name = "DestroyContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyContainer { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + let DestroyContainer { server, container, signal, time, - }: DestroyContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Execute, ) .await?; @@ -318,6 +327,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -348,16 +359,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartAllContainers { + #[instrument(name = "StartAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartAllContainers { server }: StartAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -373,6 +383,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.starting_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -397,16 +409,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RestartAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartAllContainers { + #[instrument(name = "RestartAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartAllContainers { server }: RestartAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -422,6 +433,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.restarting_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -448,16 +461,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseAllContainers { + #[instrument(name = "PauseAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseAllContainers { server }: PauseAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -473,6 +485,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -497,16 +511,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseAllContainers { + #[instrument(name = "UnpauseAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseAllContainers { server }: UnpauseAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -522,6 +535,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.unpausing_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -548,16 +563,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopAllContainers { + #[instrument(name = "StopAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopAllContainers { server }: StopAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -573,6 +587,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.stopping_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -597,16 +613,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneContainers { + #[instrument(name = "PruneContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneContainers { server }: PruneContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -622,6 +637,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -652,37 +669,43 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteNetwork", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteNetwork { + #[instrument(name = "DeleteNetwork", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteNetwork { server, name }: DeleteNetwork, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::network::DeleteNetwork { name: name.clone() }) + .request(api::network::DeleteNetwork { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete network {name} on server {}", - server.name + "failed to delete network {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete network", format_serror( - &e.context(format!("failed to delete network {name}")) - .into(), + &e.context(format!( + "failed to delete network {}", + self.name + )) + .into(), ), ), }; @@ -697,16 +720,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneNetworks", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneNetworks { + #[instrument(name = "PruneNetworks", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneNetworks { server }: PruneNetworks, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -722,6 +744,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_networks = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -750,36 +774,40 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteImage", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteImage { + #[instrument(name = "DeleteImage", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteImage { server, name }: DeleteImage, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::image::DeleteImage { name: name.clone() }) + .request(api::image::DeleteImage { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete image {name} on server {}", - server.name + "failed to delete image {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete image", format_serror( - &e.context(format!("failed to delete image {name}")).into(), + &e.context(format!("failed to delete image {}", self.name)) + .into(), ), ), }; @@ -794,16 +822,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneImages", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneImages { + #[instrument(name = "PruneImages", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneImages { server }: PruneImages, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -819,6 +846,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_images = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -845,37 +874,43 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteVolume", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteVolume { + #[instrument(name = "DeleteVolume", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteVolume { server, name }: DeleteVolume, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::volume::DeleteVolume { name: name.clone() }) + .request(api::volume::DeleteVolume { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete volume {name} on server {}", - server.name + "failed to delete volume {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete volume", format_serror( - &e.context(format!("failed to delete volume {name}")) - .into(), + &e.context(format!( + "failed to delete volume {}", + self.name + )) + .into(), ), ), }; @@ -890,16 +925,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneVolumes", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneVolumes { + #[instrument(name = "PruneVolumes", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneVolumes { server }: PruneVolumes, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -915,6 +949,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_volumes = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -941,16 +977,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneDockerBuilders", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneDockerBuilders { + #[instrument(name = "PruneDockerBuilders", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneDockerBuilders { server }: PruneDockerBuilders, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -966,6 +1001,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_builders = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -992,16 +1029,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneBuildx", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneBuildx { + #[instrument(name = "PruneBuildx", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneBuildx { server }: PruneBuildx, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -1017,6 +1053,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_buildx = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -1043,16 +1081,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneSystem", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneSystem { + #[instrument(name = "PruneSystem", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneSystem { server }: PruneSystem, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -1068,6 +1105,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_system = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; diff --git a/bin/core/src/api/execute/server_template.rs b/bin/core/src/api/execute/server_template.rs index 5ef9fadbe..b82d047e5 100644 --- a/bin/core/src/api/execute/server_template.rs +++ b/bin/core/src/api/execute/server_template.rs @@ -7,51 +7,51 @@ use komodo_client::{ server::PartialServerConfig, server_template::{ServerTemplate, ServerTemplateConfig}, update::Update, - user::User, }, }; use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, cloud::{ aws::ec2::launch_ec2_instance, hetzner::launch_hetzner_server, }, helpers::update::update_update, resource, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "LaunchServer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +use super::ExecuteArgs; + +impl Resolve for LaunchServer { + #[instrument(name = "LaunchServer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - LaunchServer { - name, - server_template, - }: LaunchServer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { // validate name isn't already taken by another server if db_client() .servers .find_one(doc! { - "name": &name + "name": &self.name }) .await .context("failed to query db for servers")? .is_some() { - return Err(anyhow!("name is already taken")); + return Err(anyhow!("name is already taken").into()); } let template = resource::get_check_permissions::( - &server_template, - &user, + &self.server_template, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update.push_simple_log( "launching server", format!("{:#?}", template.config), @@ -63,24 +63,24 @@ impl Resolve for State { let region = config.region.clone(); let use_https = config.use_https; let port = config.port; - let instance = match launch_ec2_instance(&name, config).await - { - Ok(instance) => instance, - Err(e) => { - update.push_error_log( - "launch server", - format!("failed to launch aws instance\n\n{e:#?}"), - ); - update.finalize(); - update_update(update.clone()).await?; - return Ok(update); - } - }; + let instance = + match launch_ec2_instance(&self.name, config).await { + Ok(instance) => instance, + Err(e) => { + update.push_error_log( + "launch server", + format!("failed to launch aws instance\n\n{e:#?}"), + ); + update.finalize(); + update_update(update.clone()).await?; + return Ok(update); + } + }; update.push_simple_log( "launch server", format!( - "successfully launched server {name} on ip {}", - instance.ip + "successfully launched server {} on ip {}", + self.name, instance.ip ), ); let protocol = if use_https { "https" } else { "http" }; @@ -95,24 +95,24 @@ impl Resolve for State { let datacenter = config.datacenter; let use_https = config.use_https; let port = config.port; - let server = match launch_hetzner_server(&name, config).await - { - Ok(server) => server, - Err(e) => { - update.push_error_log( - "launch server", - format!("failed to launch hetzner server\n\n{e:#?}"), - ); - update.finalize(); - update_update(update.clone()).await?; - return Ok(update); - } - }; + let server = + match launch_hetzner_server(&self.name, config).await { + Ok(server) => server, + Err(e) => { + update.push_error_log( + "launch server", + format!("failed to launch hetzner server\n\n{e:#?}"), + ); + update.finalize(); + update_update(update.clone()).await?; + return Ok(update); + } + }; update.push_simple_log( "launch server", format!( - "successfully launched server {name} on ip {}", - server.ip + "successfully launched server {} on ip {}", + self.name, server.ip ), ); let protocol = if use_https { "https" } else { "http" }; @@ -125,7 +125,13 @@ impl Resolve for State { } }; - match self.resolve(CreateServer { name, config }, user).await { + match (CreateServer { + name: self.name, + config, + }) + .resolve(&WriteArgs { user: user.clone() }) + .await + { Ok(server) => { update.push_simple_log( "create server", @@ -136,7 +142,9 @@ impl Resolve for State { Err(e) => { update.push_error_log( "create server", - format_serror(&e.context("failed to create server").into()), + format_serror( + &e.error.context("failed to create server").into(), + ), ); } }; diff --git a/bin/core/src/api/execute/stack.rs b/bin/core/src/api/execute/stack.rs index 01c1a118b..51f93c5e0 100644 --- a/bin/core/src/api/execute/stack.rs +++ b/bin/core/src/api/execute/stack.rs @@ -9,7 +9,6 @@ use komodo_client::{ server::Server, stack::{Stack, StackInfo}, update::{Log, Update}, - user::User, }, }; use mungos::mongodb::bson::{doc, to_document}; @@ -17,6 +16,7 @@ use periphery_client::api::compose::*; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, helpers::{ interpolate::{ add_interp_update_log, @@ -31,10 +31,10 @@ use crate::{ monitor::update_cache_for_server, resource, stack::{execute::execute_compose, get_stack_and_server}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchDeployStack { type Resource = Stack; @@ -47,31 +47,28 @@ impl super::BatchExecute for BatchDeployStack { } } -impl Resolve for State { - #[instrument(name = "BatchDeployStack", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeployStack { + #[instrument(name = "BatchDeployStack", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeployStack { pattern }: BatchDeployStack, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeployStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeployStack { + #[instrument(name = "DeployStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeployStack { - stack, - service, - stop_time, - }: DeployStack, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (mut stack, server) = get_stack_and_server( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, true, ) @@ -86,9 +83,11 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.deploying = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; - if let Some(service) = &service { + if let Some(service) = &self.service { update.logs.push(Log::simple( &format!("Service: {service}"), format!("Execution requested for Stack service {service}"), @@ -176,7 +175,7 @@ impl Resolve for State { } = periphery_client(&server)? .request(ComposeUp { stack: stack.clone(), - service, + service: self.service, git_token, registry_token, replacers: secret_replacers.into_iter().collect(), @@ -284,39 +283,39 @@ impl super::BatchExecute for BatchDeployStackIfChanged { } } -impl Resolve for State { - #[instrument(name = "BatchDeployStackIfChanged", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeployStackIfChanged { + #[instrument(name = "BatchDeployStackIfChanged", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeployStackIfChanged { pattern }: BatchDeployStackIfChanged, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user) - .await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::( + &self.pattern, + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeployStackIfChanged", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeployStackIfChanged { + #[instrument(name = "DeployStackIfChanged", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - DeployStackIfChanged { stack, stop_time }: DeployStackIfChanged, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, ) .await?; - State - .resolve( - RefreshStackCache { - stack: stack.id.clone(), - }, - user.clone(), - ) - .await?; + RefreshStackCache { + stack: stack.id.clone(), + } + .resolve(&WriteArgs { user: user.clone() }) + .await?; let stack = resource::get::(&stack.id).await?; let changed = match ( &stack.info.deployed_contents, @@ -343,6 +342,8 @@ impl Resolve for State { _ => false, }; + let mut update = update.clone(); + if !changed { update.push_simple_log( "Diff compose files", @@ -356,16 +357,16 @@ impl Resolve for State { // This is usually done in crate::helpers::update::init_execution_update. update.id = add_update_without_send(&update).await?; - State - .resolve( - DeployStack { - stack: stack.name, - service: None, - stop_time, - }, - (user, update), - ) - .await + DeployStack { + stack: stack.name, + service: None, + stop_time: self.stop_time, + } + .resolve(&ExecuteArgs { + user: user.clone(), + update, + }) + .await } } @@ -412,16 +413,15 @@ pub async fn pull_stack_inner( Ok(res) } -impl Resolve for State { - #[instrument(name = "PullStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PullStack { + #[instrument(name = "PullStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - PullStack { stack, service }: PullStack, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (stack, server) = get_stack_and_server( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, true, ) @@ -436,11 +436,16 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; - let res = - pull_stack_inner(stack, service, &server, Some(&mut update)) - .await?; + let res = pull_stack_inner( + stack, + self.service, + &server, + Some(&mut update), + ) + .await?; update.logs.extend(res.logs); update.finalize(); @@ -450,104 +455,100 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartStack { + #[instrument(name = "StartStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - StartStack { stack, service }: StartStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| state.starting = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "RestartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartStack { + #[instrument(name = "RestartStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - RestartStack { stack, service }: RestartStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| { state.restarting = true; }, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "PauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseStack { + #[instrument(name = "PauseStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseStack { stack, service }: PauseStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| state.pausing = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "UnpauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseStack { + #[instrument(name = "UnpauseStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseStack { stack, service }: UnpauseStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| state.unpausing = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "StopStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopStack { + #[instrument(name = "StopStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopStack { - stack, - stop_time, - service, - }: StopStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| state.stopping = true, - update, - stop_time, + update.clone(), + self.stop_time, ) .await + .map_err(Into::into) } } @@ -563,37 +564,33 @@ impl super::BatchExecute for BatchDestroyStack { } } -impl Resolve for State { - #[instrument(name = "BatchDestroyStack", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDestroyStack { + #[instrument(name = "BatchDestroyStack", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDestroyStack { pattern }: BatchDestroyStack, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + super::batch_execute::(&self.pattern, user) + .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "DestroyStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyStack { + #[instrument(name = "DestroyStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyStack { - stack, - service, - remove_orphans, - stop_time, - }: DestroyStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.service, + user, |state| state.destroying = true, - update, - (stop_time, remove_orphans), + update.clone(), + (self.stop_time, self.remove_orphans), ) .await + .map_err(Into::into) } } diff --git a/bin/core/src/api/execute/sync.rs b/bin/core/src/api/execute/sync.rs index 60f6176f2..6c353902a 100644 --- a/bin/core/src/api/execute/sync.rs +++ b/bin/core/src/api/execute/sync.rs @@ -20,7 +20,7 @@ use komodo_client::{ stack::Stack, sync::ResourceSync, update::{Log, Update}, - user::{sync_user, User}, + user::sync_user, ResourceTargetVariant, }, }; @@ -32,9 +32,10 @@ use mungos::{ use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, helpers::{query::get_id_to_tags, update::update_update}, resource::{self, refresh_resource_sync_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, sync::{ deploy::{ build_deploy_cache, deploy_from_cache, SyncDeployParams, @@ -45,17 +46,19 @@ use crate::{ }, }; -impl Resolve for State { - #[instrument(name = "RunSync", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +use super::ExecuteArgs; + +impl Resolve for RunSync { + #[instrument(name = "RunSync", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunSync { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + let RunSync { sync, resource_type: match_resource_type, resources: match_resources, - }: RunSync, - (user, mut update): (User, Update), - ) -> anyhow::Result { + } = self; let sync = resource::get_check_permissions::< entities::sync::ResourceSync, >(&sync, &user, PermissionLevel::Execute) @@ -72,6 +75,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.syncing = true)?; + let mut update = update.clone(); + // Send update here for FE to recheck action state update_update(update.clone()).await?; @@ -90,7 +95,9 @@ impl Resolve for State { update_update(update.clone()).await?; if !file_errors.is_empty() { - return Err(anyhow!("Found file errors. Cannot execute sync.")); + return Err( + anyhow!("Found file errors. Cannot execute sync.").into(), + ); } let resources = resources?; @@ -581,18 +588,21 @@ impl Resolve for State { ) } - if let Err(e) = State - .resolve( - RefreshResourceSyncPending { sync: sync.id }, - sync_user().to_owned(), - ) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.id }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) .await { - warn!("failed to refresh sync {} after run | {e:#}", sync.name); + warn!( + "failed to refresh sync {} after run | {:#}", + sync.name, e.error + ); update.push_error_log( "refresh sync", format_serror( - &e.context("failed to refresh sync pending after run") + &e.error + .context("failed to refresh sync pending after run") .into(), ), ); diff --git a/bin/core/src/api/read/action.rs b/bin/core/src/api/read/action.rs index 3501333a3..7c049eea9 100644 --- a/bin/core/src/api/read/action.rs +++ b/bin/core/src/api/read/action.rs @@ -6,7 +6,6 @@ use komodo_client::{ Action, ActionActionState, ActionListItem, ActionState, }, permission::PermissionLevel, - user::User, }, }; use resolver_api::Resolve; @@ -14,63 +13,70 @@ use resolver_api::Resolve; use crate::{ helpers::query::get_all_tags, resource, - state::{action_state_cache, action_states, State}, + state::{action_state_cache, action_states}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetAction { async fn resolve( - &self, - GetAction { action }: GetAction, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &action, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.action, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListActions { async fn resolve( - &self, - ListActions { query }: ListActions, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, &user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullActions { async fn resolve( - &self, - ListFullActions { query }: ListFullActions, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetActionActionState { async fn resolve( - &self, - GetActionActionState { action }: GetActionActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let action = resource::get_check_permissions::( - &action, + &self.action, &user, PermissionLevel::Read, ) @@ -85,12 +91,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetActionsSummary { async fn resolve( - &self, - GetActionsSummary {}: GetActionsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let actions = resource::list_full_for_user::( Default::default(), &user, diff --git a/bin/core/src/api/read/alert.rs b/bin/core/src/api/read/alert.rs index 2d789545e..574869430 100644 --- a/bin/core/src/api/read/alert.rs +++ b/bin/core/src/api/read/alert.rs @@ -5,7 +5,7 @@ use komodo_client::{ }, entities::{ deployment::Deployment, server::Server, stack::Stack, - sync::ResourceSync, user::User, + sync::ResourceSync, }, }; use mungos::{ @@ -16,29 +16,29 @@ use mungos::{ use resolver_api::Resolve; use crate::{ - config::core_config, - resource::get_resource_ids_for_user, - state::{db_client, State}, + config::core_config, resource::get_resource_ids_for_user, + state::db_client, }; +use super::ReadArgs; + const NUM_ALERTS_PER_PAGE: u64 = 100; -impl Resolve for State { +impl Resolve for ListAlerts { async fn resolve( - &self, - ListAlerts { query, page }: ListAlerts, - user: User, - ) -> anyhow::Result { - let mut query = query.unwrap_or_default(); + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut query = self.query.unwrap_or_default(); if !user.admin && !core_config().transparent_mode { let server_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let stack_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let deployment_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let sync_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; query.extend(doc! { "$or": [ { "target.type": "Server", "target.id": { "$in": &server_ids } }, @@ -55,7 +55,7 @@ impl Resolve for State { FindOptions::builder() .sort(doc! { "ts": -1 }) .limit(NUM_ALERTS_PER_PAGE as i64) - .skip(page * NUM_ALERTS_PER_PAGE) + .skip(self.page * NUM_ALERTS_PER_PAGE) .build(), ) .await @@ -64,7 +64,7 @@ impl Resolve for State { let next_page = if alerts.len() < NUM_ALERTS_PER_PAGE as usize { None } else { - Some((page + 1) as i64) + Some((self.page + 1) as i64) }; let res = ListAlertsResponse { next_page, alerts }; @@ -73,15 +73,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetAlert { async fn resolve( - &self, - GetAlert { id }: GetAlert, - _: User, - ) -> anyhow::Result { - find_one_by_id(&db_client().alerts, &id) - .await - .context("failed to query db for alert")? - .context("no alert found with given id") + self, + _: &ReadArgs, + ) -> serror::Result { + Ok( + find_one_by_id(&db_client().alerts, &self.id) + .await + .context("failed to query db for alert")? + .context("no alert found with given id")?, + ) } } diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 654d37bc3..8b158a280 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ alerter::{Alerter, AlerterListItem}, permission::PermissionLevel, - user::User, }, }; use mongo_indexed::Document; @@ -12,63 +11,68 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetAlerter { async fn resolve( - &self, - GetAlerter { alerter }: GetAlerter, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &alerter, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.alerter, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListAlerters { async fn resolve( - &self, - ListAlerters { query }: ListAlerters, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullAlerters { async fn resolve( - &self, - ListFullAlerters { query }: ListFullAlerters, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetAlertersSummary { async fn resolve( - &self, - GetAlertersSummary {}: GetAlertersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< Alerter, >(&user) diff --git a/bin/core/src/api/read/build.rs b/bin/core/src/api/read/build.rs index 06a64aa02..331e5a55c 100644 --- a/bin/core/src/api/read/build.rs +++ b/bin/core/src/api/read/build.rs @@ -10,7 +10,6 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, update::UpdateStatus, - user::User, Operation, }, }; @@ -25,65 +24,72 @@ use crate::{ helpers::query::get_all_tags, resource, state::{ - action_states, build_state_cache, db_client, github_client, State, + action_states, build_state_cache, db_client, github_client, }, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetBuild { async fn resolve( - &self, - GetBuild { build }: GetBuild, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &build, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.build, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListBuilds { async fn resolve( - &self, - ListBuilds { query }: ListBuilds, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullBuilds { async fn resolve( - &self, - ListFullBuilds { query }: ListFullBuilds, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetBuildActionState { async fn resolve( - &self, - GetBuildActionState { build }: GetBuildActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Read, ) .await?; @@ -97,15 +103,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetBuildsSummary { async fn resolve( - &self, - GetBuildsSummary {}: GetBuildsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let builds = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -145,16 +150,15 @@ impl Resolve for State { const ONE_DAY_MS: i64 = 86400000; -impl Resolve for State { +impl Resolve for GetBuildMonthlyStats { async fn resolve( - &self, - GetBuildMonthlyStats { page }: GetBuildMonthlyStats, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let curr_ts = unix_timestamp_ms() as i64; let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS; - let close_ts = next_day - page as i64 * 30 * ONE_DAY_MS; + let close_ts = next_day - self.page as i64 * 30 * ONE_DAY_MS; let open_ts = close_ts - 30 * ONE_DAY_MS; let mut build_updates = db_client() @@ -202,21 +206,21 @@ fn ms_to_hour(duration: i64) -> f64 { duration as f64 / MS_TO_HOUR_DIVISOR } -impl Resolve for State { +impl Resolve for ListBuildVersions { async fn resolve( - &self, - ListBuildVersions { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let ListBuildVersions { build, major, minor, patch, limit, - }: ListBuildVersions, - user: User, - ) -> anyhow::Result> { + } = self; let build = resource::get_check_permissions::( &build, - &user, + user, PermissionLevel::Read, ) .await?; @@ -259,21 +263,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonBuildExtraArgs { async fn resolve( - &self, - ListCommonBuildExtraArgs { query }: ListCommonBuildExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let builds = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let builds = resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -290,12 +294,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetBuildWebhookEnabled { async fn resolve( - &self, - GetBuildWebhookEnabled { build }: GetBuildWebhookEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetBuildWebhookEnabledResponse { managed: false, @@ -304,8 +307,8 @@ impl Resolve for State { }; let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/builder.rs b/bin/core/src/api/read/builder.rs index 469866d37..eecca91a6 100644 --- a/bin/core/src/api/read/builder.rs +++ b/bin/core/src/api/read/builder.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ builder::{Builder, BuilderListItem}, permission::PermissionLevel, - user::User, }, }; use mongo_indexed::Document; @@ -12,63 +11,68 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetBuilder { async fn resolve( - &self, - GetBuilder { builder }: GetBuilder, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &builder, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.builder, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListBuilders { async fn resolve( - &self, - ListBuilders { query }: ListBuilders, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullBuilders { async fn resolve( - &self, - ListFullBuilders { query }: ListFullBuilders, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetBuildersSummary { async fn resolve( - &self, - GetBuildersSummary {}: GetBuildersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< Builder, >(&user) diff --git a/bin/core/src/api/read/deployment.rs b/bin/core/src/api/read/deployment.rs index 697da171f..b147e0ff6 100644 --- a/bin/core/src/api/read/deployment.rs +++ b/bin/core/src/api/read/deployment.rs @@ -12,7 +12,6 @@ use komodo_client::{ permission::PermissionLevel, server::Server, update::Log, - user::User, }, }; use periphery_client::api; @@ -21,67 +20,73 @@ use resolver_api::Resolve; use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, - state::{action_states, deployment_status_cache, State}, + state::{action_states, deployment_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetDeployment { async fn resolve( - &self, - GetDeployment { deployment }: GetDeployment, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &deployment, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.deployment, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListDeployments { async fn resolve( - &self, - ListDeployments { query }: ListDeployments, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullDeployments { async fn resolve( - &self, - ListFullDeployments { query }: ListFullDeployments, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetDeploymentContainer { async fn resolve( - &self, - GetDeploymentContainer { deployment }: GetDeploymentContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; @@ -99,23 +104,23 @@ impl Resolve for State { const MAX_LOG_LENGTH: u64 = 5000; -impl Resolve for State { +impl Resolve for GetDeploymentLog { async fn resolve( - &self, - GetDeploymentLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetDeploymentLog { deployment, tail, timestamps, - }: GetDeploymentLog, - user: User, - ) -> anyhow::Result { + } = self; let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( &deployment, - &user, + user, PermissionLevel::Read, ) .await?; @@ -123,36 +128,37 @@ impl Resolve for State { return Ok(Log::default()); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerLog { name, tail: cmp::min(tail, MAX_LOG_LENGTH), timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchDeploymentLog { async fn resolve( - &self, - SearchDeploymentLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchDeploymentLog { deployment, terms, combinator, invert, timestamps, - }: SearchDeploymentLog, - user: User, - ) -> anyhow::Result { + } = self; let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( &deployment, - &user, + user, PermissionLevel::Read, ) .await?; @@ -160,7 +166,7 @@ impl Resolve for State { return Ok(Log::default()); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerLogSearch { name, terms, @@ -169,46 +175,48 @@ impl Resolve for State { timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDeploymentStats { async fn resolve( - &self, - GetDeploymentStats { deployment }: GetDeploymentStats, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; if server_id.is_empty() { - return Err(anyhow!("deployment has no server attached")); + return Err( + anyhow!("deployment has no server attached").into(), + ); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerStats { name }) .await - .context("failed to get stats from periphery") + .context("failed to get stats from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDeploymentActionState { async fn resolve( - &self, - GetDeploymentActionState { deployment }: GetDeploymentActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; @@ -222,15 +230,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetDeploymentsSummary { async fn resolve( - &self, - GetDeploymentsSummary {}: GetDeploymentsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployments = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -263,19 +270,18 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonDeploymentExtraArgs { async fn resolve( - &self, - ListCommonDeploymentExtraArgs { query }: ListCommonDeploymentExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; let deployments = resource::list_full_for_user::( - query, &user, &all_tags, + self.query, &user, &all_tags, ) .await .context("failed to get resources matching query")?; diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 324c3153c..54b85936b 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -2,7 +2,6 @@ use std::{collections::HashSet, sync::OnceLock, time::Instant}; use anyhow::{anyhow, Context}; use axum::{middleware, routing::post, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; use komodo_client::{ api::read::*, entities::{ @@ -16,9 +15,8 @@ use komodo_client::{ ResourceTarget, }, }; -use resolver_api::{ - derive::Resolver, Resolve, ResolveToString, Resolver, -}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; @@ -26,7 +24,7 @@ use uuid::Uuid; use crate::{ auth::auth_request, config::core_config, helpers::periphery_client, - resource, state::State, + resource, }; mod action; @@ -39,7 +37,6 @@ mod permission; mod procedure; mod provider; mod repo; -mod search; mod server; mod server_template; mod stack; @@ -51,15 +48,18 @@ mod user; mod user_group; mod variable; +pub struct ReadArgs { + pub user: User, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(User)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] +#[args(ReadArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] enum ReadRequest { - #[to_string_resolver] GetVersion(GetVersion), - #[to_string_resolver] GetCoreInfo(GetCoreInfo), ListSecrets(ListSecrets), ListGitProvidersFromConfig(ListGitProvidersFromConfig), @@ -79,9 +79,6 @@ enum ReadRequest { GetUserGroup(GetUserGroup), ListUserGroups(ListUserGroups), - // ==== SEARCH ==== - FindResources(FindResources), - // ==== PROCEDURE ==== GetProceduresSummary(GetProceduresSummary), GetProcedure(GetProcedure), @@ -120,15 +117,10 @@ enum ReadRequest { ListDockerImageHistory(ListDockerImageHistory), InspectDockerVolume(InspectDockerVolume), ListAllDockerContainers(ListAllDockerContainers), - #[to_string_resolver] ListDockerContainers(ListDockerContainers), - #[to_string_resolver] ListDockerNetworks(ListDockerNetworks), - #[to_string_resolver] ListDockerImages(ListDockerImages), - #[to_string_resolver] ListDockerVolumes(ListDockerVolumes), - #[to_string_resolver] ListComposeProjects(ListComposeProjects), // ==== DEPLOYMENT ==== @@ -212,11 +204,8 @@ enum ReadRequest { GetAlert(GetAlert), // ==== SERVER STATS ==== - #[to_string_resolver] GetSystemInformation(GetSystemInformation), - #[to_string_resolver] GetSystemStats(GetSystemStats), - #[to_string_resolver] ListSystemProcesses(ListSystemProcesses), // ==== VARIABLE ==== @@ -240,54 +229,35 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); debug!("/read request | user: {}", user.username); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&ReadArgs { user }).await; if let Err(e) = &res { - debug!("/read request {req_id} error: {e:#}"); + debug!("/read request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/read request {req_id} | resolve time: {elapsed:?}"); - Ok((TypedHeader(ContentType::json()), res?)) + res.map(|res| res.0) } -fn version() -> &'static String { - static VERSION: OnceLock = OnceLock::new(); - VERSION.get_or_init(|| { - serde_json::to_string(&GetVersionResponse { +impl Resolve for GetVersion { + async fn resolve( + self, + _: &ReadArgs, + ) -> serror::Result { + Ok(GetVersionResponse { version: env!("CARGO_PKG_VERSION").to_string(), }) - .context("failed to serialize GetVersionResponse") - .unwrap() - }) -} - -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetVersion {}: GetVersion, - _: User, - ) -> anyhow::Result { - Ok(version().to_string()) } } -fn core_info() -> &'static String { - static CORE_INFO: OnceLock = OnceLock::new(); +fn core_info() -> &'static GetCoreInfoResponse { + static CORE_INFO: OnceLock = OnceLock::new(); CORE_INFO.get_or_init(|| { let config = core_config(); - let info = GetCoreInfoResponse { + GetCoreInfoResponse { title: config.title.clone(), monitoring_interval: config.monitoring_interval, webhook_base_url: if config.webhook_base_url.is_empty() { @@ -305,36 +275,31 @@ fn core_info() -> &'static String { .iter() .map(|i| i.namespace.to_string()) .collect(), - }; - serde_json::to_string(&info) - .context("failed to serialize GetCoreInfoResponse") - .unwrap() + } }) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetCoreInfo {}: GetCoreInfo, - _: User, - ) -> anyhow::Result { - Ok(core_info().to_string()) +impl Resolve for GetCoreInfo { + async fn resolve( + self, + _: &ReadArgs, + ) -> serror::Result { + Ok(core_info().clone()) } } -impl Resolve for State { +impl Resolve for ListSecrets { async fn resolve( - &self, - ListSecrets { target }: ListSecrets, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let mut secrets = core_config() .secrets .keys() .cloned() .collect::>(); - if let Some(target) = target { + if let Some(target) = self.target { let server_id = match target { ResourceTarget::Server(id) => Some(id), ResourceTarget::Builder(id) => { @@ -348,7 +313,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ) } }; if let Some(id) = server_id { @@ -373,15 +340,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListGitProvidersFromConfig { async fn resolve( - &self, - ListGitProvidersFromConfig { target }: ListGitProvidersFromConfig, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let mut providers = core_config().git_providers.clone(); - if let Some(target) = target { + if let Some(target) = self.target { match target { ResourceTarget::Server(id) => { merge_git_providers_for_server(&mut providers, &id).await?; @@ -405,7 +371,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ) } } } @@ -471,15 +439,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListDockerRegistriesFromConfig { async fn resolve( - &self, - ListDockerRegistriesFromConfig { target }: ListDockerRegistriesFromConfig, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let mut registries = core_config().docker_registries.clone(); - if let Some(target) = target { + if let Some(target) = self.target { match target { ResourceTarget::Server(id) => { merge_docker_registries_for_server(&mut registries, &id) @@ -504,7 +471,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ) } } } @@ -518,7 +487,7 @@ impl Resolve for State { async fn merge_git_providers_for_server( providers: &mut Vec, server_id: &str, -) -> anyhow::Result<()> { +) -> serror::Result<()> { let server = resource::get::(server_id).await?; let more = periphery_client(&server)? .request(periphery_client::api::ListGitProviders {}) @@ -556,7 +525,7 @@ fn merge_git_providers( async fn merge_docker_registries_for_server( registries: &mut Vec, server_id: &str, -) -> anyhow::Result<()> { +) -> serror::Result<()> { let server = resource::get::(server_id).await?; let more = periphery_client(&server)? .request(periphery_client::api::ListDockerRegistries {}) diff --git a/bin/core/src/api/read/permission.rs b/bin/core/src/api/read/permission.rs index e3e493e28..3a6cfc7c0 100644 --- a/bin/core/src/api/read/permission.rs +++ b/bin/core/src/api/read/permission.rs @@ -5,23 +5,23 @@ use komodo_client::{ ListPermissionsResponse, ListUserTargetPermissions, ListUserTargetPermissionsResponse, }, - entities::{permission::PermissionLevel, user::User}, + entities::permission::PermissionLevel, }; use mungos::{find::find_collect, mongodb::bson::doc}; use resolver_api::Resolve; use crate::{ - helpers::query::get_user_permission_on_target, - state::{db_client, State}, + helpers::query::get_user_permission_on_target, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for ListPermissions { async fn resolve( - &self, - ListPermissions {}: ListPermissions, - user: User, - ) -> anyhow::Result { - find_collect( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let res = find_collect( &db_client().permissions, doc! { "user_target.type": "User", @@ -30,34 +30,33 @@ impl Resolve for State { None, ) .await - .context("failed to query db for permissions") + .context("failed to query db for permissions")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetPermissionLevel { async fn resolve( - &self, - GetPermissionLevel { target }: GetPermissionLevel, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if user.admin { return Ok(PermissionLevel::Write); } - get_user_permission_on_target(&user, &target).await + Ok(get_user_permission_on_target(user, &self.target).await?) } } -impl Resolve for State { +impl Resolve for ListUserTargetPermissions { async fn resolve( - &self, - ListUserTargetPermissions { user_target }: ListUserTargetPermissions, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } - let (variant, id) = user_target.extract_variant_id(); - find_collect( + let (variant, id) = self.user_target.extract_variant_id(); + let res = find_collect( &db_client().permissions, doc! { "user_target.type": variant.as_ref(), @@ -66,6 +65,7 @@ impl Resolve for State { None, ) .await - .context("failed to query db for permissions") + .context("failed to query db for permissions")?; + Ok(res) } } diff --git a/bin/core/src/api/read/procedure.rs b/bin/core/src/api/read/procedure.rs index ec1e4961d..d3507a8f5 100644 --- a/bin/core/src/api/read/procedure.rs +++ b/bin/core/src/api/read/procedure.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ permission::PermissionLevel, procedure::{Procedure, ProcedureState}, - user::User, }, }; use resolver_api::Resolve; @@ -12,65 +11,73 @@ use resolver_api::Resolve; use crate::{ helpers::query::get_all_tags, resource, - state::{action_states, procedure_state_cache, State}, + state::{action_states, procedure_state_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetProcedure { async fn resolve( - &self, - GetProcedure { procedure }: GetProcedure, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &procedure, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.procedure, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListProcedures { async fn resolve( - &self, - ListProcedures { query }: ListProcedures, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullProcedures { async fn resolve( - &self, - ListFullProcedures { query }: ListFullProcedures, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetProceduresSummary { async fn resolve( - &self, - GetProceduresSummary {}: GetProceduresSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let procedures = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -108,15 +115,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetProcedureActionState { async fn resolve( - &self, - GetProcedureActionState { procedure }: GetProcedureActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let procedure = resource::get_check_permissions::( - &procedure, - &user, + &self.procedure, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/provider.rs b/bin/core/src/api/read/provider.rs index 28d3735df..f787e5d29 100644 --- a/bin/core/src/api/read/provider.rs +++ b/bin/core/src/api/read/provider.rs @@ -1,13 +1,5 @@ use anyhow::{anyhow, Context}; -use komodo_client::{ - api::read::{ - GetDockerRegistryAccount, GetDockerRegistryAccountResponse, - GetGitProviderAccount, GetGitProviderAccountResponse, - ListDockerRegistryAccounts, ListDockerRegistryAccountsResponse, - ListGitProviderAccounts, ListGitProviderAccountsResponse, - }, - entities::user::User, -}; +use komodo_client::api::read::*; use mongo_indexed::{doc, Document}; use mungos::{ by_id::find_one_by_id, find::find_collect, @@ -15,45 +7,48 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetGitProviderAccount { async fn resolve( - &self, - GetGitProviderAccount { id }: GetGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read git provider accounts" - )); + return Err( + anyhow!("Only admins can read git provider accounts").into(), + ); } - find_one_by_id(&db_client().git_accounts, &id) + let res = find_one_by_id(&db_client().git_accounts, &self.id) .await .context("failed to query db for git provider accounts")? - .context("did not find git provider account with the given id") + .context( + "did not find git provider account with the given id", + )?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListGitProviderAccounts { async fn resolve( - &self, - ListGitProviderAccounts { domain, username }: ListGitProviderAccounts, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read git provider accounts" - )); + return Err( + anyhow!("Only admins can read git provider accounts").into(), + ); } let mut filter = Document::new(); - if let Some(domain) = domain { + if let Some(domain) = self.domain { filter.insert("domain", domain); } - if let Some(username) = username { + if let Some(username) = self.username { filter.insert("username", username); } - find_collect( + let res = find_collect( &db_client().git_accounts, filter, FindOptions::builder() @@ -61,49 +56,52 @@ impl Resolve for State { .build(), ) .await - .context("failed to query db for git provider accounts") + .context("failed to query db for git provider accounts")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDockerRegistryAccount { async fn resolve( - &self, - GetDockerRegistryAccount { id }: GetDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read docker registry accounts" - )); + return Err( + anyhow!("Only admins can read docker registry accounts") + .into(), + ); } - find_one_by_id(&db_client().registry_accounts, &id) - .await - .context("failed to query db for docker registry accounts")? - .context( - "did not find docker registry account with the given id", - ) + let res = + find_one_by_id(&db_client().registry_accounts, &self.id) + .await + .context("failed to query db for docker registry accounts")? + .context( + "did not find docker registry account with the given id", + )?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListDockerRegistryAccounts { async fn resolve( - &self, - ListDockerRegistryAccounts { domain, username }: ListDockerRegistryAccounts, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read docker registry accounts" - )); + return Err( + anyhow!("Only admins can read docker registry accounts") + .into(), + ); } let mut filter = Document::new(); - if let Some(domain) = domain { + if let Some(domain) = self.domain { filter.insert("domain", domain); } - if let Some(username) = username { + if let Some(username) = self.username { filter.insert("username", username); } - find_collect( + let res = find_collect( &db_client().registry_accounts, filter, FindOptions::builder() @@ -111,6 +109,7 @@ impl Resolve for State { .build(), ) .await - .context("failed to query db for docker registry accounts") + .context("failed to query db for docker registry accounts")?; + Ok(res) } } diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index 5b8f8c976..4da0c052a 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -5,7 +5,6 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, repo::{Repo, RepoActionState, RepoListItem, RepoState}, - user::User, }, }; use resolver_api::Resolve; @@ -14,64 +13,71 @@ use crate::{ config::core_config, helpers::query::get_all_tags, resource, - state::{action_states, github_client, repo_state_cache, State}, + state::{action_states, github_client, repo_state_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetRepo { async fn resolve( - &self, - GetRepo { repo }: GetRepo, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &repo, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.repo, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListRepos { async fn resolve( - &self, - ListRepos { query }: ListRepos, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, &user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullRepos { async fn resolve( - &self, - ListFullRepos { query }: ListFullRepos, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetRepoActionState { async fn resolve( - &self, - GetRepoActionState { repo }: GetRepoActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Read, ) .await?; @@ -85,15 +91,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetReposSummary { async fn resolve( - &self, - GetReposSummary {}: GetReposSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let repos = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -141,12 +146,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetRepoWebhooksEnabled { async fn resolve( - &self, - GetRepoWebhooksEnabled { repo }: GetRepoWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetRepoWebhooksEnabledResponse { managed: false, @@ -157,8 +161,8 @@ impl Resolve for State { }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/search.rs b/bin/core/src/api/read/search.rs deleted file mode 100644 index 3568ad8ab..000000000 --- a/bin/core/src/api/read/search.rs +++ /dev/null @@ -1,82 +0,0 @@ -use komodo_client::{ - api::read::{FindResources, FindResourcesResponse}, - entities::{ - build::Build, deployment::Deployment, procedure::Procedure, - repo::Repo, server::Server, user::User, ResourceTargetVariant, - }, -}; -use resolver_api::Resolve; - -use crate::{resource, state::State}; - -const FIND_RESOURCE_TYPES: [ResourceTargetVariant; 5] = [ - ResourceTargetVariant::Server, - ResourceTargetVariant::Build, - ResourceTargetVariant::Deployment, - ResourceTargetVariant::Repo, - ResourceTargetVariant::Procedure, -]; - -impl Resolve for State { - async fn resolve( - &self, - FindResources { query, resources }: FindResources, - user: User, - ) -> anyhow::Result { - let mut res = FindResourcesResponse::default(); - let resource_types = if resources.is_empty() { - FIND_RESOURCE_TYPES.to_vec() - } else { - resources - .into_iter() - .filter(|r| { - !matches!( - r, - ResourceTargetVariant::System - | ResourceTargetVariant::Builder - | ResourceTargetVariant::Alerter - ) - }) - .collect() - }; - for resource_type in resource_types { - match resource_type { - ResourceTargetVariant::Server => { - res.servers = resource::list_for_user_using_document::< - Server, - >(query.clone(), &user) - .await?; - } - ResourceTargetVariant::Deployment => { - res.deployments = resource::list_for_user_using_document::< - Deployment, - >(query.clone(), &user) - .await?; - } - ResourceTargetVariant::Build => { - res.builds = - resource::list_for_user_using_document::( - query.clone(), - &user, - ) - .await?; - } - ResourceTargetVariant::Repo => { - res.repos = resource::list_for_user_using_document::( - query.clone(), - &user, - ) - .await?; - } - ResourceTargetVariant::Procedure => { - res.procedures = resource::list_for_user_using_document::< - Procedure, - >(query.clone(), &user) - .await?; - } - _ => {} - } - } - Ok(res) - } -} diff --git a/bin/core/src/api/read/server.rs b/bin/core/src/api/read/server.rs index 1761aca15..3e532ac64 100644 --- a/bin/core/src/api/read/server.rs +++ b/bin/core/src/api/read/server.rs @@ -23,8 +23,8 @@ use komodo_client::{ Server, ServerActionState, ServerListItem, ServerState, }, stack::{Stack, StackServiceNames}, + stats::{SystemInformation, SystemProcess}, update::Log, - user::User, ResourceTarget, }, }; @@ -39,25 +39,26 @@ use periphery_client::api::{ network::InspectNetwork, volume::InspectVolume, }; -use resolver_api::{Resolve, ResolveToString}; +use resolver_api::Resolve; use tokio::sync::Mutex; use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, stack::compose_container_match_regex, - state::{action_states, db_client, server_status_cache, State}, + state::{action_states, db_client, server_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetServersSummary { async fn resolve( - &self, - GetServersSummary {}: GetServersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let servers = resource::list_for_user::( Default::default(), - &user, + user, &[], ) .await?; @@ -80,15 +81,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetPeripheryVersion { async fn resolve( - &self, - req: GetPeripheryVersion, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &req.server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -101,61 +101,66 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetServer { async fn resolve( - &self, - req: GetServer, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &req.server, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.server, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListServers { async fn resolve( - &self, - ListServers { query }: ListServers, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, &user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullServers { async fn resolve( - &self, - ListFullServers { query }: ListFullServers, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetServerState { async fn resolve( - &self, - GetServerState { server }: GetServerState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -170,15 +175,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetServerActionState { async fn resolve( - &self, - GetServerActionState { server }: GetServerActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -194,22 +198,22 @@ impl Resolve for State { // This protects the peripheries from spam requests const SYSTEM_INFO_EXPIRY: u128 = FIFTEEN_SECONDS_MS; -type SystemInfoCache = Mutex>>; +type SystemInfoCache = + Mutex>>; fn system_info_cache() -> &'static SystemInfoCache { static SYSTEM_INFO_CACHE: OnceLock = OnceLock::new(); SYSTEM_INFO_CACHE.get_or_init(Default::default) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetSystemInformation { server }: GetSystemInformation, - user: User, - ) -> anyhow::Result { +impl Resolve for GetSystemInformation { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -223,28 +227,26 @@ impl ResolveToString for State { let stats = periphery_client(&server)? .request(periphery::stats::GetSystemInformation {}) .await?; - let res = serde_json::to_string(&stats)?; lock.insert( server.id, - (res.clone(), unix_timestamp_ms() + SYSTEM_INFO_EXPIRY) + (stats.clone(), unix_timestamp_ms() + SYSTEM_INFO_EXPIRY) .into(), ); - res + stats } }; Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetSystemStats { server }: GetSystemStats, - user: User, - ) -> anyhow::Result { +impl Resolve for GetSystemStats { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -256,28 +258,27 @@ impl ResolveToString for State { .stats .as_ref() .context("server stats not available")?; - let stats = serde_json::to_string(&stats)?; - Ok(stats) + Ok(stats.clone()) } } // This protects the peripheries from spam requests const PROCESSES_EXPIRY: u128 = FIFTEEN_SECONDS_MS; -type ProcessesCache = Mutex>>; +type ProcessesCache = + Mutex, u128)>>>; fn processes_cache() -> &'static ProcessesCache { static PROCESSES_CACHE: OnceLock = OnceLock::new(); PROCESSES_CACHE.get_or_init(Default::default) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListSystemProcesses { server }: ListSystemProcesses, - user: User, - ) -> anyhow::Result { +impl Resolve for ListSystemProcesses { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -290,13 +291,12 @@ impl ResolveToString for State { let stats = periphery_client(&server)? .request(periphery::stats::GetSystemProcesses {}) .await?; - let res = serde_json::to_string(&stats)?; lock.insert( server.id, - (res.clone(), unix_timestamp_ms() + PROCESSES_EXPIRY) + (stats.clone(), unix_timestamp_ms() + PROCESSES_EXPIRY) .into(), ); - res + stats } }; Ok(res) @@ -305,19 +305,19 @@ impl ResolveToString for State { const STATS_PER_PAGE: i64 = 200; -impl Resolve for State { +impl Resolve for GetHistoricalServerStats { async fn resolve( - &self, - GetHistoricalServerStats { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetHistoricalServerStats { server, granularity, page, - }: GetHistoricalServerStats, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; @@ -358,15 +358,14 @@ impl Resolve for State { } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerContainers { server }: ListDockerContainers, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerContainers { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -374,20 +373,18 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(containers) = &cache.containers { - serde_json::to_string(containers) - .context("failed to serialize response") + Ok(containers.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for ListAllDockerContainers { async fn resolve( - &self, - ListAllDockerContainers { servers }: ListAllDockerContainers, - user: User, - ) -> anyhow::Result> { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let servers = resource::list_for_user::( Default::default(), &user, @@ -396,9 +393,9 @@ impl Resolve for State { .await? .into_iter() .filter(|server| { - servers.is_empty() - || servers.contains(&server.id) - || servers.contains(&server.name) + self.servers.is_empty() + || self.servers.contains(&server.id) + || self.servers.contains(&server.name) }); let mut containers = Vec::::new(); @@ -416,15 +413,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for InspectDockerContainer { async fn resolve( - &self, - InspectDockerContainer { server, container }: InspectDockerContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -432,67 +428,74 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect container: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect container: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(InspectContainer { name: container }) - .await + let res = periphery_client(&server)? + .request(InspectContainer { + name: self.container, + }) + .await?; + Ok(res) } } const MAX_LOG_LENGTH: u64 = 5000; -impl Resolve for State { +impl Resolve for GetContainerLog { async fn resolve( - &self, - GetContainerLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetContainerLog { server, container, tail, timestamps, - }: GetContainerLog, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(periphery::container::GetContainerLog { name: container, tail: cmp::min(tail, MAX_LOG_LENGTH), timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchContainerLog { async fn resolve( - &self, - SearchContainerLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchContainerLog { server, container, terms, combinator, invert, timestamps, - }: SearchContainerLog, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(periphery::container::GetContainerLogSearch { name: container, terms, @@ -501,25 +504,25 @@ impl Resolve for State { timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetResourceMatchingContainer { async fn resolve( - &self, - GetResourceMatchingContainer { server, container }: GetResourceMatchingContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; // first check deployments if let Ok(deployment) = - resource::get::(&container).await + resource::get::(&self.container).await { return Ok(GetResourceMatchingContainerResponse { resource: ResourceTarget::Deployment(deployment.id).into(), @@ -553,7 +556,7 @@ impl Resolve for State { warn!("{e:#}"); continue; } - }.is_match(&container); + }.is_match(&self.container); if is_match { return Ok(GetResourceMatchingContainerResponse { @@ -567,15 +570,14 @@ impl Resolve for State { } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerNetworks { server }: ListDockerNetworks, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerNetworks { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -583,23 +585,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(networks) = &cache.networks { - serde_json::to_string(networks) - .context("failed to serialize response") + Ok(networks.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerNetwork { async fn resolve( - &self, - InspectDockerNetwork { server, network }: InspectDockerNetwork, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -607,26 +607,29 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect network: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect network: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(InspectNetwork { name: network }) - .await + let res = periphery_client(&server)? + .request(InspectNetwork { name: self.network }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerImages { server }: ListDockerImages, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerImages { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -634,23 +637,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(images) = &cache.images { - serde_json::to_string(images) - .context("failed to serialize response") + Ok(images.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerImage { async fn resolve( - &self, - InspectDockerImage { server, image }: InspectDockerImage, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -658,26 +659,26 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect image: server is {:?}", - cache.state - )); + return Err( + anyhow!("Cannot inspect image: server is {:?}", cache.state) + .into(), + ); } - periphery_client(&server)? - .request(InspectImage { name: image }) - .await + let res = periphery_client(&server)? + .request(InspectImage { name: self.image }) + .await?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListDockerImageHistory { async fn resolve( - &self, - ListDockerImageHistory { server, image }: ListDockerImageHistory, - user: User, - ) -> anyhow::Result> { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -685,26 +686,29 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot get image history: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot get image history: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(ImageHistory { name: image }) - .await + let res = periphery_client(&server)? + .request(ImageHistory { name: self.image }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerVolumes { server }: ListDockerVolumes, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerVolumes { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -712,23 +716,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(volumes) = &cache.volumes { - serde_json::to_string(volumes) - .context("failed to serialize response") + Ok(volumes.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerVolume { async fn resolve( - &self, - InspectDockerVolume { server, volume }: InspectDockerVolume, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -736,26 +738,26 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect volume: server is {:?}", - cache.state - )); + return Err( + anyhow!("Cannot inspect volume: server is {:?}", cache.state) + .into(), + ); } - periphery_client(&server)? - .request(InspectVolume { name: volume }) - .await + let res = periphery_client(&server)? + .request(InspectVolume { name: self.volume }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListComposeProjects { server }: ListComposeProjects, - user: User, - ) -> anyhow::Result { +impl Resolve for ListComposeProjects { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -763,10 +765,9 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(projects) = &cache.projects { - serde_json::to_string(projects) - .context("failed to serialize response") + Ok(projects.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } diff --git a/bin/core/src/api/read/server_template.rs b/bin/core/src/api/read/server_template.rs index 503f92540..39dc325cc 100644 --- a/bin/core/src/api/read/server_template.rs +++ b/bin/core/src/api/read/server_template.rs @@ -3,7 +3,6 @@ use komodo_client::{ api::read::*, entities::{ permission::PermissionLevel, server_template::ServerTemplate, - user::User, }, }; use mongo_indexed::Document; @@ -11,66 +10,70 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetServerTemplate { async fn resolve( - &self, - GetServerTemplate { server_template }: GetServerTemplate, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &server_template, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.server_template, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListServerTemplates { async fn resolve( - &self, - ListServerTemplates { query }: ListServerTemplates, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullServerTemplates { async fn resolve( - &self, - ListFullServerTemplates { query }: ListFullServerTemplates, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetServerTemplatesSummary { async fn resolve( - &self, - GetServerTemplatesSummary {}: GetServerTemplatesSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< ServerTemplate, >(&user) diff --git a/bin/core/src/api/read/stack.rs b/bin/core/src/api/read/stack.rs index 9122236f4..e1e467b77 100644 --- a/bin/core/src/api/read/stack.rs +++ b/bin/core/src/api/read/stack.rs @@ -7,7 +7,6 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, stack::{Stack, StackActionState, StackListItem, StackState}, - user::User, }, }; use periphery_client::api::compose::{ @@ -20,33 +19,35 @@ use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, stack::get_stack_and_server, - state::{action_states, github_client, stack_status_cache, State}, + state::{action_states, github_client, stack_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetStack { async fn resolve( - &self, - GetStack { stack }: GetStack, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &stack, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.stack, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListStackServices { async fn resolve( - &self, - ListStackServices { stack }: ListStackServices, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; @@ -63,25 +64,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStackServiceLog { async fn resolve( - &self, - GetStackServiceLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetStackServiceLog { stack, service, tail, timestamps, - }: GetStackServiceLog, - user: User, - ) -> anyhow::Result { - let (stack, server) = get_stack_and_server( - &stack, - &user, - PermissionLevel::Read, - true, - ) - .await?; - periphery_client(&server)? + } = self; + let (stack, server) = + get_stack_and_server(&stack, user, PermissionLevel::Read, true) + .await?; + let res = periphery_client(&server)? .request(GetComposeServiceLog { project: stack.project_name(false), service, @@ -89,31 +86,28 @@ impl Resolve for State { timestamps, }) .await - .context("failed to get stack service log from periphery") + .context("failed to get stack service log from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchStackServiceLog { async fn resolve( - &self, - SearchStackServiceLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchStackServiceLog { stack, service, terms, combinator, invert, timestamps, - }: SearchStackServiceLog, - user: User, - ) -> anyhow::Result { - let (stack, server) = get_stack_and_server( - &stack, - &user, - PermissionLevel::Read, - true, - ) - .await?; - periphery_client(&server)? + } = self; + let (stack, server) = + get_stack_and_server(&stack, user, PermissionLevel::Read, true) + .await?; + let res = periphery_client(&server)? .request(GetComposeServiceLogSearch { project: stack.project_name(false), service, @@ -123,25 +117,26 @@ impl Resolve for State { timestamps, }) .await - .context("failed to get stack service log from periphery") + .context("failed to get stack service log from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListCommonStackExtraArgs { async fn resolve( - &self, - ListCommonStackExtraArgs { query }: ListCommonStackExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let stacks = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let stacks = resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -158,21 +153,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonStackBuildExtraArgs { async fn resolve( - &self, - ListCommonStackBuildExtraArgs { query }: ListCommonStackBuildExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let stacks = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let stacks = resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -189,46 +184,50 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListStacks { async fn resolve( - &self, - ListStacks { query }: ListStacks, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullStacks { async fn resolve( - &self, - ListFullStacks { query }: ListFullStacks, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetStackActionState { async fn resolve( - &self, - GetStackActionState { stack }: GetStackActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; @@ -242,15 +241,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStacksSummary { async fn resolve( - &self, - GetStacksSummary {}: GetStacksSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stacks = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -276,12 +274,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStackWebhooksEnabled { async fn resolve( - &self, - GetStackWebhooksEnabled { stack }: GetStackWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetStackWebhooksEnabledResponse { managed: false, @@ -291,8 +288,8 @@ impl Resolve for State { }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/sync.rs b/bin/core/src/api/read/sync.rs index 2606890fa..085f89ba3 100644 --- a/bin/core/src/api/read/sync.rs +++ b/bin/core/src/api/read/sync.rs @@ -8,7 +8,6 @@ use komodo_client::{ ResourceSync, ResourceSyncActionState, ResourceSyncListItem, ResourceSyncState, }, - user::User, }, }; use resolver_api::Resolve; @@ -17,69 +16,73 @@ use crate::{ config::core_config, helpers::query::get_all_tags, resource, - state::{ - action_states, github_client, resource_sync_state_cache, State, - }, + state::{action_states, github_client, resource_sync_state_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetResourceSync { async fn resolve( - &self, - GetResourceSync { sync }: GetResourceSync, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &sync, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.sync, + &user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListResourceSyncs { async fn resolve( - &self, - ListResourceSyncs { query }: ListResourceSyncs, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, &user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullResourceSyncs { async fn resolve( - &self, - ListFullResourceSyncs { query }: ListFullResourceSyncs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, &user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetResourceSyncActionState { async fn resolve( - &self, - GetResourceSyncActionState { sync }: GetResourceSyncActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Read, ) .await?; @@ -93,16 +96,15 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetResourceSyncsSummary { async fn resolve( - &self, - GetResourceSyncsSummary {}: GetResourceSyncsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let resource_syncs = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -159,12 +161,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetSyncWebhooksEnabled { async fn resolve( - &self, - GetSyncWebhooksEnabled { sync }: GetSyncWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetSyncWebhooksEnabledResponse { managed: false, @@ -174,8 +175,8 @@ impl Resolve for State { }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/tag.rs b/bin/core/src/api/read/tag.rs index be1d032da..6bb849181 100644 --- a/bin/core/src/api/read/tag.rs +++ b/bin/core/src/api/read/tag.rs @@ -1,39 +1,31 @@ use anyhow::Context; use komodo_client::{ api::read::{GetTag, ListTags}, - entities::{tag::Tag, user::User}, + entities::tag::Tag, }; use mongo_indexed::doc; use mungos::{find::find_collect, mongodb::options::FindOptions}; use resolver_api::Resolve; -use crate::{ - helpers::query::get_tag, - state::{db_client, State}, -}; +use crate::{helpers::query::get_tag, state::db_client}; -impl Resolve for State { - async fn resolve( - &self, - GetTag { tag }: GetTag, - _: User, - ) -> anyhow::Result { - get_tag(&tag).await +use super::ReadArgs; + +impl Resolve for GetTag { + async fn resolve(self, _: &ReadArgs) -> serror::Result { + Ok(get_tag(&self.tag).await?) } } -impl Resolve for State { - async fn resolve( - &self, - ListTags { query }: ListTags, - _: User, - ) -> anyhow::Result> { - find_collect( +impl Resolve for ListTags { + async fn resolve(self, _: &ReadArgs) -> serror::Result> { + let res = find_collect( &db_client().tags, - query, + self.query, FindOptions::builder().sort(doc! { "name": 1 }).build(), ) .await - .context("failed to get tags from db") + .context("failed to get tags from db")?; + Ok(res) } } diff --git a/bin/core/src/api/read/toml.rs b/bin/core/src/api/read/toml.rs index 5186e6196..376a41e89 100644 --- a/bin/core/src/api/read/toml.rs +++ b/bin/core/src/api/read/toml.rs @@ -10,8 +10,7 @@ use komodo_client::{ deployment::Deployment, permission::PermissionLevel, procedure::Procedure, repo::Repo, resource::ResourceQuery, server::Server, server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, toml::ResourcesToml, user::User, - ResourceTarget, + sync::ResourceSync, toml::ResourcesToml, ResourceTarget, }, }; use mungos::find::find_collect; @@ -22,7 +21,7 @@ use crate::{ get_all_tags, get_id_to_tags, get_user_user_group_ids, }, resource, - state::{db_client, State}, + state::db_client, sync::{ toml::{convert_resource, ToToml, TOML_PRETTY_OPTIONS}, user_groups::convert_user_groups, @@ -30,23 +29,26 @@ use crate::{ }, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for ExportAllResourcesToToml { async fn resolve( - &self, - ExportAllResourcesToToml { tags }: ExportAllResourcesToToml, - user: User, - ) -> anyhow::Result { + self, + args: &ReadArgs, + ) -> serror::Result { let mut targets = Vec::::new(); - let all_tags = if tags.is_empty() { + let all_tags = if self.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; + let ReadArgs { user } = args; + targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -56,7 +58,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -66,7 +68,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -74,19 +76,9 @@ impl Resolve for State { .into_iter() .map(|resource| ResourceTarget::Server(resource.id)), ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Deployment(resource.id)), - ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -94,9 +86,19 @@ impl Resolve for State { .into_iter() .map(|resource| ResourceTarget::Stack(resource.id)), ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(self.tags.clone()).build(), + &user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Deployment(resource.id)), + ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -106,7 +108,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -116,7 +118,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -126,7 +128,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -136,7 +138,7 @@ impl Resolve for State { ); targets.extend( resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -146,7 +148,7 @@ impl Resolve for State { ); targets.extend( resource::list_full_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), + ResourceQuery::builder().tags(self.tags.clone()).build(), &user, &all_tags, ) @@ -156,7 +158,7 @@ impl Resolve for State { .map(|resource| ResourceTarget::ResourceSync(resource.id)), ); - let user_groups = if user.admin && tags.is_empty() { + let user_groups = if user.admin && self.tags.is_empty() { find_collect(&db_client().user_groups, None, None) .await .context("failed to query db for user groups")? @@ -167,32 +169,30 @@ impl Resolve for State { get_user_user_group_ids(&user.id).await? }; - self - .resolve( - ExportResourcesToToml { - targets, - user_groups, - include_variables: tags.is_empty(), - }, - user, - ) - .await - } -} - -impl Resolve for State { - async fn resolve( - &self, ExportResourcesToToml { + targets, + user_groups, + include_variables: self.tags.is_empty(), + } + .resolve(args) + .await + } +} + +impl Resolve for ExportResourcesToToml { + async fn resolve( + self, + args: &ReadArgs, + ) -> serror::Result { + let ExportResourcesToToml { targets, user_groups, include_variables, - }: ExportResourcesToToml, - user: User, - ) -> anyhow::Result { + } = self; let mut res = ResourcesToml::default(); let all = AllResourcesById::load().await?; let id_to_tags = get_id_to_tags(None).await?; + let ReadArgs { user } = args; for target in targets { match target { ResourceTarget::Alerter(id) => { @@ -368,7 +368,7 @@ impl Resolve for State { }; } - add_user_groups(user_groups, &mut res, &all, &user) + add_user_groups(user_groups, &mut res, &all, args) .await .context("failed to add user groups")?; @@ -398,11 +398,12 @@ async fn add_user_groups( user_groups: Vec, res: &mut ResourcesToml, all: &AllResourcesById, - user: &User, + args: &ReadArgs, ) -> anyhow::Result<()> { - let user_groups = State - .resolve(ListUserGroups {}, user.clone()) - .await? + let user_groups = ListUserGroups {} + .resolve(args) + .await + .map_err(|e| e.error)? .into_iter() .filter(|ug| { user_groups.contains(&ug.name) || user_groups.contains(&ug.id) diff --git a/bin/core/src/api/read/update.rs b/bin/core/src/api/read/update.rs index 89d59440d..f5f0a1d26 100644 --- a/bin/core/src/api/read/update.rs +++ b/bin/core/src/api/read/update.rs @@ -28,22 +28,19 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{ - config::core_config, - resource, - state::{db_client, State}, -}; +use crate::{config::core_config, resource, state::db_client}; + +use super::ReadArgs; const UPDATES_PER_PAGE: i64 = 100; -impl Resolve for State { +impl Resolve for ListUpdates { async fn resolve( - &self, - ListUpdates { query, page }: ListUpdates, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = if user.admin || core_config().transparent_mode { - query + self.query } else { let server_query = resource::get_resource_ids_for_user::(&user) @@ -157,7 +154,7 @@ impl Resolve for State { }) .unwrap_or_else(|| doc! { "target.type": "ResourceSync" }); - let mut query = query.unwrap_or_default(); + let mut query = self.query.unwrap_or_default(); query.extend(doc! { "$or": [ server_query, @@ -188,7 +185,7 @@ impl Resolve for State { query, FindOptions::builder() .sort(doc! { "start_ts": -1 }) - .skip(page as u64 * UPDATES_PER_PAGE as u64) + .skip(self.page as u64 * UPDATES_PER_PAGE as u64) .limit(UPDATES_PER_PAGE) .build(), ) @@ -220,7 +217,7 @@ impl Resolve for State { .collect::>(); let next_page = if updates.len() == UPDATES_PER_PAGE as usize { - Some(page + 1) + Some(self.page + 1) } else { None }; @@ -229,13 +226,12 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetUpdate { async fn resolve( - &self, - GetUpdate { id }: GetUpdate, - user: User, - ) -> anyhow::Result { - let update = find_one_by_id(&db_client().updates, &id) + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let update = find_one_by_id(&db_client().updates, &self.id) .await .context("failed to query to db")? .context("no update exists with given id")?; @@ -244,9 +240,9 @@ impl Resolve for State { } match &update.target { ResourceTarget::System(_) => { - return Err(anyhow!( - "user must be admin to view system updates" - )) + return Err( + anyhow!("user must be admin to view system updates").into(), + ) } ResourceTarget::Server(id) => { resource::get_check_permissions::( diff --git a/bin/core/src/api/read/user.rs b/bin/core/src/api/read/user.rs index d120e5bbb..ad62b6d17 100644 --- a/bin/core/src/api/read/user.rs +++ b/bin/core/src/api/read/user.rs @@ -6,7 +6,7 @@ use komodo_client::{ ListApiKeysForServiceUserResponse, ListApiKeysResponse, ListUsers, ListUsersResponse, }, - entities::user::{admin_service_user, User, UserConfig}, + entities::user::{admin_service_user, UserConfig}, }; use mungos::{ by_id::find_one_by_id, @@ -15,25 +15,23 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{ - helpers::query::get_user, - state::{db_client, State}, -}; +use crate::{helpers::query::get_user, state::db_client}; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetUsername { async fn resolve( - &self, - GetUsername { user_id }: GetUsername, - _: User, - ) -> anyhow::Result { - if let Some(user) = admin_service_user(&user_id) { + self, + _: &ReadArgs, + ) -> serror::Result { + if let Some(user) = admin_service_user(&self.user_id) { return Ok(GetUsernameResponse { username: user.username, avatar: None, }); } - let user = find_one_by_id(&db_client().users, &user_id) + let user = find_one_by_id(&db_client().users, &self.user_id) .await .context("failed at mongo query for user")? .context("no user found with id")?; @@ -51,27 +49,27 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for FindUser { async fn resolve( - &self, - FindUser { user }: FindUser, - admin: User, - ) -> anyhow::Result { + self, + ReadArgs { user: admin }: &ReadArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This method is admin only.")); + return Err(anyhow!("This method is admin only.").into()); } - get_user(&user).await + Ok(get_user(&self.user).await?) } } -impl Resolve for State { +impl Resolve for ListUsers { async fn resolve( - &self, - ListUsers {}: ListUsers, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("this route is only accessable by admins")); + return Err( + anyhow!("this route is only accessable by admins").into(), + ); } let mut users = find_collect( &db_client().users, @@ -85,12 +83,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListApiKeys { async fn resolve( - &self, - ListApiKeys {}: ListApiKeys, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let api_keys = find_collect( &db_client().api_keys, doc! { "user_id": &user.id }, @@ -108,20 +105,19 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListApiKeysForServiceUser { async fn resolve( - &self, - ListApiKeysForServiceUser { user }: ListApiKeysForServiceUser, - admin: User, - ) -> anyhow::Result { + self, + ReadArgs { user: admin }: &ReadArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This method is admin only.")); + return Err(anyhow!("This method is admin only.").into()); } - let user = get_user(&user).await?; + let user = get_user(&self.user).await?; let UserConfig::Service { .. } = user.config else { - return Err(anyhow!("Given user is not service user")); + return Err(anyhow!("Given user is not service user").into()); }; let api_keys = find_collect( &db_client().api_keys, diff --git a/bin/core/src/api/read/user_group.rs b/bin/core/src/api/read/user_group.rs index 363332eaa..2f0eceae0 100644 --- a/bin/core/src/api/read/user_group.rs +++ b/bin/core/src/api/read/user_group.rs @@ -1,13 +1,7 @@ use std::str::FromStr; use anyhow::Context; -use komodo_client::{ - api::read::{ - GetUserGroup, GetUserGroupResponse, ListUserGroups, - ListUserGroupsResponse, - }, - entities::user::User, -}; +use komodo_client::api::read::*; use mungos::{ find::find_collect, mongodb::{ @@ -17,48 +11,50 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetUserGroup { async fn resolve( - &self, - GetUserGroup { user_group }: GetUserGroup, - user: User, - ) -> anyhow::Result { - let mut filter = match ObjectId::from_str(&user_group) { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; // Don't allow non admin users to get UserGroups they aren't a part of. if !user.admin { // Filter for only UserGroups which contain the users id filter.insert("users", &user.id); } - db_client() + let res = db_client() .user_groups .find_one(filter) .await .context("failed to query db for user groups")? - .context("no UserGroup found with given name or id") + .context("no UserGroup found with given name or id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListUserGroups { async fn resolve( - &self, - ListUserGroups {}: ListUserGroups, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let mut filter = Document::new(); if !user.admin { filter.insert("users", &user.id); } - find_collect( + let res = find_collect( &db_client().user_groups, filter, FindOptions::builder().sort(doc! { "name": 1 }).build(), ) .await - .context("failed to query db for UserGroups") + .context("failed to query db for UserGroups")?; + Ok(res) } } diff --git a/bin/core/src/api/read/variable.rs b/bin/core/src/api/read/variable.rs index 9fa42c517..f196c1df2 100644 --- a/bin/core/src/api/read/variable.rs +++ b/bin/core/src/api/read/variable.rs @@ -1,27 +1,19 @@ use anyhow::Context; -use komodo_client::{ - api::read::{ - GetVariable, GetVariableResponse, ListVariables, - ListVariablesResponse, - }, - entities::user::User, -}; +use komodo_client::api::read::*; use mongo_indexed::doc; use mungos::{find::find_collect, mongodb::options::FindOptions}; use resolver_api::Resolve; -use crate::{ - helpers::query::get_variable, - state::{db_client, State}, -}; +use crate::{helpers::query::get_variable, state::db_client}; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetVariable { async fn resolve( - &self, - GetVariable { name }: GetVariable, - user: User, - ) -> anyhow::Result { - let mut variable = get_variable(&name).await?; + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut variable = get_variable(&self.name).await?; if !variable.is_secret || user.admin { return Ok(variable); } @@ -30,12 +22,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListVariables { async fn resolve( - &self, - ListVariables {}: ListVariables, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let variables = find_collect( &db_client().variables, None, diff --git a/bin/core/src/api/user.rs b/bin/core/src/api/user.rs index 8d6bd527c..959da98e3 100644 --- a/bin/core/src/api/user.rs +++ b/bin/core/src/api/user.rs @@ -2,19 +2,15 @@ use std::{collections::VecDeque, time::Instant}; use anyhow::{anyhow, Context}; use axum::{middleware, routing::post, Extension, Json, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use derive_variants::EnumVariants; use komodo_client::{ - api::user::{ - CreateApiKey, CreateApiKeyResponse, DeleteApiKey, - DeleteApiKeyResponse, PushRecentlyViewed, - PushRecentlyViewedResponse, SetLastSeenUpdate, - SetLastSeenUpdateResponse, - }, + api::user::*, entities::{api_key::ApiKey, komodo_timestamp, user::User}, }; use mongo_indexed::doc; use mungos::{by_id::update_one_by_id, mongodb::bson::to_bson}; -use resolver_api::{derive::Resolver, Resolve, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use uuid::Uuid; @@ -22,13 +18,20 @@ use uuid::Uuid; use crate::{ auth::auth_request, helpers::{query::get_user, random_string}, - state::{db_client, State}, + state::db_client, }; +pub struct UserArgs { + pub user: User, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(User)] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, +)] +#[args(UserArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] enum UserRequest { PushRecentlyViewed(PushRecentlyViewed), @@ -47,47 +50,37 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); debug!( "/user request {req_id} | user: {} ({})", user.username, user.id ); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&UserArgs { user }).await; if let Err(e) = &res { - warn!("/user request {req_id} error: {e:#}"); + warn!("/user request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/user request {req_id} | resolve time: {elapsed:?}"); - Ok((TypedHeader(ContentType::json()), res?)) + res.map(|res| res.0) } const RECENTLY_VIEWED_MAX: usize = 10; -impl Resolve for State { +impl Resolve for PushRecentlyViewed { #[instrument( name = "PushRecentlyViewed", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - PushRecentlyViewed { resource }: PushRecentlyViewed, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let user = get_user(&user.id).await?; - let (resource_type, id) = resource.extract_variant_id(); + let (resource_type, id) = self.resource.extract_variant_id(); let update = match user.recents.get(&resource_type) { Some(recents) => { let mut recents = recents @@ -117,17 +110,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for SetLastSeenUpdate { #[instrument( name = "SetLastSeenUpdate", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - SetLastSeenUpdate {}: SetLastSeenUpdate, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { update_one_by_id( &db_client().users, &user.id, @@ -145,17 +137,12 @@ impl Resolve for State { const SECRET_LENGTH: usize = 40; const BCRYPT_COST: u32 = 10; -impl Resolve for State { - #[instrument( - name = "CreateApiKey", - level = "debug", - skip(self, user) - )] +impl Resolve for CreateApiKey { + #[instrument(name = "CreateApiKey", level = "debug", skip(user))] async fn resolve( - &self, - CreateApiKey { name, expires }: CreateApiKey, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let user = get_user(&user.id).await?; let key = format!("K-{}", random_string(SECRET_LENGTH)); @@ -164,12 +151,12 @@ impl Resolve for State { .context("failed at hashing secret string")?; let api_key = ApiKey { - name, + name: self.name, key: key.clone(), secret: secret_hash, user_id: user.id.clone(), created_at: komodo_timestamp(), - expires, + expires: self.expires, }; db_client() .api_keys @@ -180,26 +167,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "DeleteApiKey", - level = "debug", - skip(self, user) - )] +impl Resolve for DeleteApiKey { + #[instrument(name = "DeleteApiKey", level = "debug", skip(user))] async fn resolve( - &self, - DeleteApiKey { key }: DeleteApiKey, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let client = db_client(); let key = client .api_keys - .find_one(doc! { "key": &key }) + .find_one(doc! { "key": &self.key }) .await .context("failed at db query")? .context("no api key with key found")?; if user.id != key.user_id { - return Err(anyhow!("api key does not belong to user")); + return Err(anyhow!("api key does not belong to user").into()); } client .api_keys diff --git a/bin/core/src/api/write/action.rs b/bin/core/src/api/write/action.rs index f18c3a6b9..2511ceb82 100644 --- a/bin/core/src/api/write/action.rs +++ b/bin/core/src/api/write/action.rs @@ -2,70 +2,70 @@ use komodo_client::{ api::write::*, entities::{ action::Action, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateAction", skip(self, user))] - async fn resolve( - &self, - CreateAction { name, config }: CreateAction, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyAction", skip(self, user))] +impl Resolve for CreateAction { + #[instrument(name = "CreateAction", skip(user))] async fn resolve( - &self, - CopyAction { name, id }: CopyAction, - user: User, - ) -> anyhow::Result { - let Action { config, .. } = resource::get_check_permissions::< - Action, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "UpdateAction", skip(self, user))] +impl Resolve for CopyAction { + #[instrument(name = "CopyAction", skip(user))] async fn resolve( - &self, - UpdateAction { id, config }: UpdateAction, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Action { config, .. } = + resource::get_check_permissions::( + &self.id, + &user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameAction", skip(self, user))] +impl Resolve for UpdateAction { + #[instrument(name = "UpdateAction", skip(user))] async fn resolve( - &self, - RenameAction { id, name }: RenameAction, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "DeleteAction", skip(self, user))] +impl Resolve for RenameAction { + #[instrument(name = "RenameAction", skip(user))] async fn resolve( - &self, - DeleteAction { id }: DeleteAction, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) + } +} + +impl Resolve for DeleteAction { + #[instrument(name = "DeleteAction", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } diff --git a/bin/core/src/api/write/alerter.rs b/bin/core/src/api/write/alerter.rs index 2047f4872..e59afbdcc 100644 --- a/bin/core/src/api/write/alerter.rs +++ b/bin/core/src/api/write/alerter.rs @@ -2,70 +2,76 @@ use komodo_client::{ api::write::*, entities::{ alerter::Alerter, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateAlerter", skip(self, user))] - async fn resolve( - &self, - CreateAlerter { name, config }: CreateAlerter, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyAlerter", skip(self, user))] +impl Resolve for CreateAlerter { + #[instrument(name = "CreateAlerter", skip(user))] async fn resolve( - &self, - CopyAlerter { name, id }: CopyAlerter, - user: User, - ) -> anyhow::Result { - let Alerter { config, .. } = resource::get_check_permissions::< - Alerter, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "DeleteAlerter", skip(self, user))] +impl Resolve for CopyAlerter { + #[instrument(name = "CopyAlerter", skip(user))] async fn resolve( - &self, - DeleteAlerter { id }: DeleteAlerter, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Alerter { config, .. } = + resource::get_check_permissions::( + &self.id, + &user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateAlerter", skip(self, user))] +impl Resolve for DeleteAlerter { + #[instrument(name = "DeleteAlerter", skip(args))] async fn resolve( - &self, - UpdateAlerter { id, config }: UpdateAlerter, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "RenameAlerter", skip(self, user))] +impl Resolve for UpdateAlerter { + #[instrument(name = "UpdateAlerter", skip(user))] async fn resolve( - &self, - RenameAlerter { id, name }: RenameAlerter, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) + } +} + +impl Resolve for RenameAlerter { + #[instrument(name = "RenameAlerter", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index e1ee86abd..307890eb9 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -7,7 +7,6 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, update::Update, - user::User, CloneArgs, NoData, }, }; @@ -22,89 +21,88 @@ use crate::{ config::core_config, helpers::git_token, resource, - state::{db_client, github_client, State}, + state::{db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateBuild", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateBuild { + #[instrument(name = "CreateBuild", skip(user))] async fn resolve( - &self, - CreateBuild { name, config }: CreateBuild, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyBuild", skip(self, user))] +impl Resolve for CopyBuild { + #[instrument(name = "CopyBuild", skip(user))] async fn resolve( - &self, - CopyBuild { name, id }: CopyBuild, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Build { mut config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; // reset version to 0.0.0 config.version = Default::default(); - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteBuild", skip(self, user))] +impl Resolve for DeleteBuild { + #[instrument(name = "DeleteBuild", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) + } +} + +impl Resolve for UpdateBuild { + #[instrument(name = "UpdateBuild", skip(user))] async fn resolve( - &self, - DeleteBuild { id }: DeleteBuild, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateBuild", skip(self, user))] +impl Resolve for RenameBuild { + #[instrument(name = "RenameBuild", skip(user))] async fn resolve( - &self, - UpdateBuild { id, config }: UpdateBuild, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameBuild", skip(self, user))] - async fn resolve( - &self, - RenameBuild { id, name }: RenameBuild, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await - } -} - -impl Resolve for State { +impl Resolve for RefreshBuildCache { #[instrument( name = "RefreshBuildCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshBuildCache { build }: RefreshBuildCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // build should be able to do this. let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Execute, ) .await?; @@ -178,39 +176,44 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateBuildWebhook", skip(self, user))] +impl Resolve for CreateBuildWebhook { + #[instrument(name = "CreateBuildWebhook", skip(args))] async fn resolve( - &self, - CreateBuildWebhook { build }: CreateBuildWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; + let WriteArgs { user } = args; + let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Write, ) .await?; if build.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = build.config.repo.split('/'); let owner = split.next().context("Build repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -271,64 +274,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !build.config.webhook_enabled { - self - .resolve( - UpdateBuild { - id: build.id, - config: PartialBuildConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update build to enable webhook")?; + UpdateBuild { + id: build.id, + config: PartialBuildConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update build to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteBuildWebhook", skip(self, user))] +impl Resolve for DeleteBuildWebhook { + #[instrument(name = "DeleteBuildWebhook", skip(user))] async fn resolve( - &self, - DeleteBuildWebhook { build }: DeleteBuildWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let build = resource::get_check_permissions::( - &build, + &self.build, &user, PermissionLevel::Write, ) .await?; if build.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if build.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't delete webhook" - )); + return Err( + anyhow!("No repo configured, can't delete webhook").into(), + ); } let mut split = build.config.repo.split('/'); let owner = split.next().context("Build repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = diff --git a/bin/core/src/api/write/builder.rs b/bin/core/src/api/write/builder.rs index d99fabe0d..434513b7a 100644 --- a/bin/core/src/api/write/builder.rs +++ b/bin/core/src/api/write/builder.rs @@ -2,70 +2,79 @@ use komodo_client::{ api::write::*, entities::{ builder::Builder, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateBuilder", skip(self, user))] - async fn resolve( - &self, - CreateBuilder { name, config }: CreateBuilder, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyBuilder", skip(self, user))] +impl Resolve for CreateBuilder { + #[instrument(name = "CreateBuilder", skip(user))] async fn resolve( - &self, - CopyBuilder { name, id }: CopyBuilder, - user: User, - ) -> anyhow::Result { - let Builder { config, .. } = resource::get_check_permissions::< - Builder, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "DeleteBuilder", skip(self, user))] +impl Resolve for CopyBuilder { + #[instrument(name = "CopyBuilder", skip(user))] async fn resolve( - &self, - DeleteBuilder { id }: DeleteBuilder, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Builder { config, .. } = + resource::get_check_permissions::( + &self.id, + user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateBuilder", skip(self, user))] +impl Resolve for DeleteBuilder { + #[instrument(name = "DeleteBuilder", skip(args))] async fn resolve( - &self, - UpdateBuilder { id, config }: UpdateBuilder, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "RenameBuilder", skip(self, user))] +impl Resolve for UpdateBuilder { + #[instrument(name = "UpdateBuilder", skip(user))] async fn resolve( - &self, - RenameBuilder { id, name }: RenameBuilder, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) + } +} + +impl Resolve for RenameBuilder { + #[instrument(name = "RenameBuilder", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, &user) + .await?, + ) } } diff --git a/bin/core/src/api/write/deployment.rs b/bin/core/src/api/write/deployment.rs index dd4ccca51..3dbadf230 100644 --- a/bin/core/src/api/write/deployment.rs +++ b/bin/core/src/api/write/deployment.rs @@ -12,7 +12,6 @@ use komodo_client::{ server::{Server, ServerState}, to_komodo_name, update::Update, - user::User, Operation, }, }; @@ -27,51 +26,57 @@ use crate::{ update::{add_update, make_update}, }, resource, - state::{action_states, db_client, server_status_cache, State}, + state::{action_states, db_client, server_status_cache}, }; -impl Resolve for State { - #[instrument(name = "CreateDeployment", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateDeployment { + #[instrument(name = "CreateDeployment", skip(user))] async fn resolve( - &self, - CreateDeployment { name, config }: CreateDeployment, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyDeployment", skip(self, user))] +impl Resolve for CopyDeployment { + #[instrument(name = "CopyDeployment", skip(user))] async fn resolve( - &self, - CopyDeployment { name, id }: CopyDeployment, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Deployment { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::( + &self.name, + config.into(), + &user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument( - name = "CreateDeploymentFromContainer", - skip(self, user) - )] +impl Resolve for CreateDeploymentFromContainer { + #[instrument(name = "CreateDeploymentFromContainer", skip(user))] async fn resolve( - &self, - CreateDeploymentFromContainer { name, server }: CreateDeploymentFromContainer, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Write, ) .await?; @@ -79,13 +84,18 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect container: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect container: server is {:?}", + cache.state + ) + .into(), + ); } let container = periphery_client(&server)? - .request(InspectContainer { name: name.clone() }) + .request(InspectContainer { + name: self.name.clone(), + }) .await .context("Failed to inspect container")?; @@ -146,42 +156,45 @@ impl Resolve for State { }); } - resource::create::(&name, config, &user).await + Ok( + resource::create::(&self.name, config, &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteDeployment", skip(self, user))] +impl Resolve for DeleteDeployment { + #[instrument(name = "DeleteDeployment", skip(args))] async fn resolve( - &self, - DeleteDeployment { id }: DeleteDeployment, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateDeployment", skip(self, user))] +impl Resolve for UpdateDeployment { + #[instrument(name = "UpdateDeployment", skip(user))] async fn resolve( - &self, - UpdateDeployment { id, config }: UpdateDeployment, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameDeployment", skip(self, user))] +impl Resolve for RenameDeployment { + #[instrument(name = "RenameDeployment", skip(user))] async fn resolve( - &self, - RenameDeployment { id, name }: RenameDeployment, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; @@ -197,14 +210,17 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.renaming = true)?; - let name = to_komodo_name(&name); + let name = to_komodo_name(&self.name); let container_state = get_deployment_state(&deployment).await?; if container_state == DeploymentState::Unknown { - return Err(anyhow!( - "Cannot rename Deployment when container status is unknown" - )); + return Err( + anyhow!( + "Cannot rename Deployment when container status is unknown" + ) + .into(), + ); } let mut update = diff --git a/bin/core/src/api/write/description.rs b/bin/core/src/api/write/description.rs index 2740e722b..93d3b7df6 100644 --- a/bin/core/src/api/write/description.rs +++ b/bin/core/src/api/write/description.rs @@ -5,33 +5,34 @@ use komodo_client::{ action::Action, alerter::Alerter, build::Build, builder::Builder, deployment::Deployment, procedure::Procedure, repo::Repo, server::Server, server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, user::User, ResourceTarget, + sync::ResourceSync, ResourceTarget, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "UpdateDescription", skip(self, user))] +use super::WriteArgs; + +impl Resolve for UpdateDescription { + #[instrument(name = "UpdateDescription", skip(user))] async fn resolve( - &self, - UpdateDescription { - target, - description, - }: UpdateDescription, - user: User, - ) -> anyhow::Result { - match target { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + match self.target { ResourceTarget::System(_) => { - return Err(anyhow!( - "cannot update description of System resource target" - )) + return Err( + anyhow!( + "cannot update description of System resource target" + ) + .into(), + ) } ResourceTarget::Server(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -39,7 +40,7 @@ impl Resolve for State { ResourceTarget::Deployment(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -47,7 +48,7 @@ impl Resolve for State { ResourceTarget::Build(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -55,7 +56,7 @@ impl Resolve for State { ResourceTarget::Repo(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -63,7 +64,7 @@ impl Resolve for State { ResourceTarget::Builder(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -71,7 +72,7 @@ impl Resolve for State { ResourceTarget::Alerter(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -79,7 +80,7 @@ impl Resolve for State { ResourceTarget::Procedure(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -87,7 +88,7 @@ impl Resolve for State { ResourceTarget::Action(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -95,7 +96,7 @@ impl Resolve for State { ResourceTarget::ServerTemplate(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -103,7 +104,7 @@ impl Resolve for State { ResourceTarget::ResourceSync(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; @@ -111,7 +112,7 @@ impl Resolve for State { ResourceTarget::Stack(id) => { resource::update_description::( &id, - &description, + &self.description, &user, ) .await?; diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index f2d14fce1..4acb72e1c 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -1,17 +1,17 @@ use std::time::Instant; -use anyhow::{anyhow, Context}; +use anyhow::Context; use axum::{middleware, routing::post, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; use derive_variants::{EnumVariants, ExtractVariant}; use komodo_client::{api::write::*, entities::user::User}; -use resolver_api::{derive::Resolver, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; use uuid::Uuid; -use crate::{auth::auth_request, state::State}; +use crate::auth::auth_request; mod action; mod alerter; @@ -33,13 +33,18 @@ mod user; mod user_group; mod variable; +pub struct WriteArgs { + pub user: User, +} + #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] #[variant_derive(Debug)] -#[resolver_target(State)] -#[resolver_args(User)] +#[args(WriteArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] pub enum WriteRequest { // ==== USER ==== @@ -194,7 +199,7 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let req_id = Uuid::new_v4(); let res = tokio::spawn(task(req_id, request, user)) @@ -205,7 +210,7 @@ async fn handler( warn!("/write request {req_id} spawn error: {e:#}"); } - Ok((TypedHeader(ContentType::json()), res??)) + res? } #[instrument( @@ -220,28 +225,19 @@ async fn task( req_id: Uuid, request: WriteRequest, user: User, -) -> anyhow::Result { +) -> serror::Result { info!("/write request | user: {}", user.username); let timer = Instant::now(); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&WriteArgs { user }).await; if let Err(e) = &res { - warn!("/write request {req_id} error: {e:#}"); + warn!("/write request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/write request {req_id} | resolve time: {elapsed:?}"); - res + res.map(|res| res.0) } diff --git a/bin/core/src/api/write/permissions.rs b/bin/core/src/api/write/permissions.rs index 65e96655b..632e3ab59 100644 --- a/bin/core/src/api/write/permissions.rs +++ b/bin/core/src/api/write/permissions.rs @@ -2,16 +2,9 @@ use std::str::FromStr; use anyhow::{anyhow, Context}; use komodo_client::{ - api::write::{ - UpdatePermissionOnResourceType, - UpdatePermissionOnResourceTypeResponse, UpdatePermissionOnTarget, - UpdatePermissionOnTargetResponse, UpdateUserAdmin, - UpdateUserAdminResponse, UpdateUserBasePermissions, - UpdateUserBasePermissionsResponse, - }, + api::write::*, entities::{ permission::{UserTarget, UserTargetVariant}, - user::User, ResourceTarget, ResourceTargetVariant, }, }; @@ -24,37 +17,40 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{ - helpers::query::get_user, - state::{db_client, State}, -}; +use crate::{helpers::query::get_user, state::db_client}; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for UpdateUserAdmin { + #[instrument(name = "UpdateUserAdmin", skip(super_admin))] async fn resolve( - &self, - UpdateUserAdmin { user_id, admin }: UpdateUserAdmin, - super_admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: super_admin }: &WriteArgs, + ) -> serror::Result { if !super_admin.super_admin { - return Err(anyhow!("Only super admins can call this method.")); + return Err( + anyhow!("Only super admins can call this method.").into(), + ); } - let user = find_one_by_id(&db_client().users, &user_id) + let user = find_one_by_id(&db_client().users, &self.user_id) .await .context("failed to query mongo for user")? .context("did not find user with given id")?; if !user.enabled { - return Err(anyhow!("User is disabled. Enable user first.")); + return Err( + anyhow!("User is disabled. Enable user first.").into(), + ); } if user.super_admin { - return Err(anyhow!("Cannot update other super admins")); + return Err(anyhow!("Cannot update other super admins").into()); } update_one_by_id( &db_client().users, - &user_id, - doc! { "$set": { "admin": admin } }, + &self.user_id, + doc! { "$set": { "admin": self.admin } }, None, ) .await?; @@ -63,20 +59,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UpdateUserBasePermissions", skip(self, admin))] +impl Resolve for UpdateUserBasePermissions { + #[instrument(name = "UpdateUserBasePermissions", skip(admin))] async fn resolve( - &self, - UpdateUserBasePermissions { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdateUserBasePermissions { user_id, enabled, create_servers, create_builds, - }: UpdateUserBasePermissions, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } let user = find_one_by_id(&db_client().users, &user_id) @@ -84,14 +81,17 @@ impl Resolve for State { .context("failed to query mongo for user")? .context("did not find user with given id")?; if user.super_admin { - return Err(anyhow!( - "Cannot use this method to update super admins permissions" - )); + return Err( + anyhow!( + "Cannot use this method to update super admins permissions" + ) + .into(), + ); } if user.admin && !admin.super_admin { return Err(anyhow!( "Only super admins can use this method to update other admins permissions" - )); + ).into()); } let mut update_doc = Document::new(); if let Some(enabled) = enabled { @@ -116,34 +116,35 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "UpdatePermissionOnResourceType", - skip(self, admin) - )] +impl Resolve for UpdatePermissionOnResourceType { + #[instrument(name = "UpdatePermissionOnResourceType", skip(admin))] async fn resolve( - &self, - UpdatePermissionOnResourceType { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdatePermissionOnResourceType { user_target, resource_type, permission, - }: UpdatePermissionOnResourceType, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } // Some extra checks if user target is an actual User if let UserTarget::User(user_id) = &user_target { let user = get_user(user_id).await?; if user.admin { - return Err(anyhow!( + return Err( + anyhow!( "cannot use this method to update other admins permissions" - )); + ) + .into(), + ); } if !user.enabled { - return Err(anyhow!("user not enabled")); + return Err(anyhow!("user not enabled").into()); } } @@ -181,31 +182,35 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UpdatePermissionOnTarget", skip(self, admin))] +impl Resolve for UpdatePermissionOnTarget { + #[instrument(name = "UpdatePermissionOnTarget", skip(admin))] async fn resolve( - &self, - UpdatePermissionOnTarget { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdatePermissionOnTarget { user_target, resource_target, permission, - }: UpdatePermissionOnTarget, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } // Some extra checks if user target is an actual User if let UserTarget::User(user_id) = &user_target { let user = get_user(user_id).await?; if user.admin { - return Err(anyhow!( + return Err( + anyhow!( "cannot use this method to update other admins permissions" - )); + ) + .into(), + ); } if !user.enabled { - return Err(anyhow!("user not enabled")); + return Err(anyhow!("user not enabled").into()); } } @@ -247,7 +252,7 @@ impl Resolve for State { /// checks if inner id is actually a `name`, and replaces it with id if so. async fn extract_user_target_with_validation( user_target: &UserTarget, -) -> anyhow::Result<(UserTargetVariant, String)> { +) -> serror::Result<(UserTargetVariant, String)> { match user_target { UserTarget::User(ident) => { let filter = match ObjectId::from_str(ident) { @@ -283,7 +288,7 @@ async fn extract_user_target_with_validation( /// checks if inner id is actually a `name`, and replaces it with id if so. async fn extract_resource_target_with_validation( resource_target: &ResourceTarget, -) -> anyhow::Result<(ResourceTargetVariant, String)> { +) -> serror::Result<(ResourceTargetVariant, String)> { match resource_target { ResourceTarget::System(_) => { let res = resource_target.extract_variant_id(); diff --git a/bin/core/src/api/write/procedure.rs b/bin/core/src/api/write/procedure.rs index 393c6d820..c4745f2b5 100644 --- a/bin/core/src/api/write/procedure.rs +++ b/bin/core/src/api/write/procedure.rs @@ -1,72 +1,80 @@ use komodo_client::{ api::write::*, entities::{ - permission::PermissionLevel, procedure::Procedure, - update::Update, user::User, + permission::PermissionLevel, procedure::Procedure, update::Update, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateProcedure", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateProcedure { + #[instrument(name = "CreateProcedure", skip(user))] async fn resolve( - &self, - CreateProcedure { name, config }: CreateProcedure, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyProcedure", skip(self, user))] +impl Resolve for CopyProcedure { + #[instrument(name = "CopyProcedure", skip(user))] async fn resolve( - &self, - CopyProcedure { name, id }: CopyProcedure, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Procedure { config, .. } = resource::get_check_permissions::( - &id, + &self.id, &user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateProcedure", skip(self, user))] +impl Resolve for UpdateProcedure { + #[instrument(name = "UpdateProcedure", skip(user))] async fn resolve( - &self, - UpdateProcedure { id, config }: UpdateProcedure, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameProcedure", skip(self, user))] +impl Resolve for RenameProcedure { + #[instrument(name = "RenameProcedure", skip(user))] async fn resolve( - &self, - RenameProcedure { id, name }: RenameProcedure, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteProcedure", skip(self, user))] +impl Resolve for DeleteProcedure { + #[instrument(name = "DeleteProcedure", skip(args))] async fn resolve( - &self, - DeleteProcedure { id }: DeleteProcedure, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } diff --git a/bin/core/src/api/write/provider.rs b/bin/core/src/api/write/provider.rs index 1b5f20f19..f92cc6450 100644 --- a/bin/core/src/api/write/provider.rs +++ b/bin/core/src/api/write/provider.rs @@ -3,7 +3,6 @@ use komodo_client::{ api::write::*, entities::{ provider::{DockerRegistryAccount, GitProviderAccount}, - user::User, Operation, ResourceTarget, }, }; @@ -15,29 +14,31 @@ use resolver_api::Resolve; use crate::{ helpers::update::{add_update, make_update}, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for CreateGitProviderAccount { async fn resolve( - &self, - CreateGitProviderAccount { account }: CreateGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can create git provider accounts" - )); + return Err( + anyhow!("only admins can create git provider accounts") + .into(), + ); } - let mut account: GitProviderAccount = account.into(); + let mut account: GitProviderAccount = self.account.into(); if account.domain.is_empty() { - return Err(anyhow!("domain cannot be empty string.")); + return Err(anyhow!("domain cannot be empty string.").into()); } if account.username.is_empty() { - return Err(anyhow!("username cannot be empty string.")); + return Err(anyhow!("username cannot be empty string.").into()); } let mut update = make_update( @@ -77,36 +78,38 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for UpdateGitProviderAccount { async fn resolve( - &self, - UpdateGitProviderAccount { id, mut account }: UpdateGitProviderAccount, - user: User, - ) -> anyhow::Result { + mut self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can update git provider accounts" - )); + return Err( + anyhow!("only admins can update git provider accounts") + .into(), + ); } - if let Some(domain) = &account.domain { + if let Some(domain) = &self.account.domain { if domain.is_empty() { - return Err(anyhow!( - "cannot update git provider with empty domain" - )); + return Err( + anyhow!("cannot update git provider with empty domain") + .into(), + ); } } - if let Some(username) = &account.username { + if let Some(username) = &self.account.username { if username.is_empty() { - return Err(anyhow!( - "cannot update git provider with empty username" - )); + return Err( + anyhow!("cannot update git provider with empty username") + .into(), + ); } } // Ensure update does not change id - account.id = None; + self.account.id = None; let mut update = make_update( ResourceTarget::system(), @@ -114,25 +117,24 @@ impl Resolve for State { &user, ); - let account = to_document(&account).context( + let account = to_document(&self.account).context( "failed to serialize partial git provider account to bson", )?; let db = db_client(); update_one_by_id( &db.git_accounts, - &id, + &self.id, doc! { "$set": account }, None, ) .await .context("failed to update git provider account on db")?; - let Some(account) = - find_one_by_id(&db.git_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = find_one_by_id(&db.git_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; update.push_simple_log( @@ -156,16 +158,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for DeleteGitProviderAccount { async fn resolve( - &self, - DeleteGitProviderAccount { id }: DeleteGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can delete git provider accounts" - )); + return Err( + anyhow!("only admins can delete git provider accounts") + .into(), + ); } let mut update = make_update( @@ -175,14 +177,13 @@ impl Resolve for State { ); let db = db_client(); - let Some(account) = - find_one_by_id(&db.git_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = find_one_by_id(&db.git_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; - delete_one_by_id(&db.git_accounts, &id, None) + delete_one_by_id(&db.git_accounts, &self.id, None) .await .context("failed to delete git account on db")?; @@ -207,26 +208,28 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for CreateDockerRegistryAccount { async fn resolve( - &self, - CreateDockerRegistryAccount { account }: CreateDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can create docker registry account accounts" - )); + return Err( + anyhow!( + "only admins can create docker registry account accounts" + ) + .into(), + ); } - let mut account: DockerRegistryAccount = account.into(); + let mut account: DockerRegistryAccount = self.account.into(); if account.domain.is_empty() { - return Err(anyhow!("domain cannot be empty string.")); + return Err(anyhow!("domain cannot be empty string.").into()); } if account.username.is_empty() { - return Err(anyhow!("username cannot be empty string.")); + return Err(anyhow!("username cannot be empty string.").into()); } let mut update = make_update( @@ -268,35 +271,41 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for UpdateDockerRegistryAccount { async fn resolve( - &self, - UpdateDockerRegistryAccount { id, mut account }: UpdateDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + mut self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can update docker registry accounts" - )); + return Err( + anyhow!("only admins can update docker registry accounts") + .into(), + ); } - if let Some(domain) = &account.domain { + if let Some(domain) = &self.account.domain { if domain.is_empty() { - return Err(anyhow!( - "cannot update docker registry account with empty domain" - )); + return Err( + anyhow!( + "cannot update docker registry account with empty domain" + ) + .into(), + ); } } - if let Some(username) = &account.username { + if let Some(username) = &self.account.username { if username.is_empty() { - return Err(anyhow!( + return Err( + anyhow!( "cannot update docker registry account with empty username" - )); + ) + .into(), + ); } } - account.id = None; + self.account.id = None; let mut update = make_update( ResourceTarget::system(), @@ -304,14 +313,14 @@ impl Resolve for State { &user, ); - let account = to_document(&account).context( + let account = to_document(&self.account).context( "failed to serialize partial docker registry account account to bson", )?; let db = db_client(); update_one_by_id( &db.registry_accounts, - &id, + &self.id, doc! { "$set": account }, None, ) @@ -320,11 +329,12 @@ impl Resolve for State { "failed to update docker registry account account on db", )?; - let Some(account) = find_one_by_id(&db.registry_accounts, &id) - .await - .context("failed to query db for registry accounts")? + let Some(account) = + find_one_by_id(&db.registry_accounts, &self.id) + .await + .context("failed to query db for registry accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; update.push_simple_log( @@ -348,16 +358,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for DeleteDockerRegistryAccount { async fn resolve( - &self, - DeleteDockerRegistryAccount { id }: DeleteDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can delete docker registry accounts" - )); + return Err( + anyhow!("only admins can delete docker registry accounts") + .into(), + ); } let mut update = make_update( @@ -367,13 +377,14 @@ impl Resolve for State { ); let db = db_client(); - let Some(account) = find_one_by_id(&db.registry_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = + find_one_by_id(&db.registry_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; - delete_one_by_id(&db.registry_accounts, &id, None) + delete_one_by_id(&db.registry_accounts, &self.id, None) .await .context("failed to delete registry account on db")?; diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index 11def0dc9..2189380a0 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -11,7 +11,6 @@ use komodo_client::{ server::Server, to_komodo_name, update::{Log, Update}, - user::User, CloneArgs, NoData, Operation, }, }; @@ -30,69 +29,66 @@ use crate::{ update::{add_update, make_update}, }, resource, - state::{action_states, db_client, github_client, State}, + state::{action_states, db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateRepo", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateRepo { + #[instrument(name = "CreateRepo", skip(user))] async fn resolve( - &self, - CreateRepo { name, config }: CreateRepo, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::create::(&self.name, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "CopyRepo", skip(self, user))] +impl Resolve for CopyRepo { + #[instrument(name = "CopyRepo", skip(user))] async fn resolve( - &self, - CopyRepo { name, id }: CopyRepo, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Repo { config, .. } = resource::get_check_permissions::( - &id, + &self.id, &user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), &user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteRepo", skip(self, user))] - async fn resolve( - &self, - DeleteRepo { id }: DeleteRepo, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteRepo { + #[instrument(name = "DeleteRepo", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateRepo", skip(self, user))] +impl Resolve for UpdateRepo { + #[instrument(name = "UpdateRepo", skip(user))] async fn resolve( - &self, - UpdateRepo { id, config }: UpdateRepo, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameRepo", skip(self, user))] +impl Resolve for RenameRepo { + #[instrument(name = "RenameRepo", skip(user))] async fn resolve( - &self, - RenameRepo { id, name }: RenameRepo, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &id, + &self.id, &user, PermissionLevel::Write, ) @@ -101,7 +97,9 @@ impl Resolve for State { if repo.config.server_id.is_empty() || !repo.config.path.is_empty() { - return resource::rename::(&repo.id, &name, &user).await; + return Ok( + resource::rename::(&repo.id, &self.name, &user).await?, + ); } // get the action state for the repo (or insert default). @@ -113,7 +111,7 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.renaming = true)?; - let name = to_komodo_name(&name); + let name = to_komodo_name(&self.name); let mut update = make_update(&repo, Operation::RenameRepo, &user); @@ -159,21 +157,20 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RefreshRepoCache { #[instrument( name = "RefreshRepoCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshRepoCache { repo }: RefreshRepoCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // repo should be able to do this. let repo = resource::get_check_permissions::( - &repo, + &self.repo, &user, PermissionLevel::Execute, ) @@ -245,39 +242,42 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateRepoWebhook", skip(self, user))] +impl Resolve for CreateRepoWebhook { + #[instrument(name = "CreateRepoWebhook", skip(args))] async fn resolve( - &self, - CreateRepoWebhook { repo, action }: CreateRepoWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + &args.user, PermissionLevel::Write, ) .await?; if repo.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = repo.config.repo.split('/'); let owner = split.next().context("Repo repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo_name = @@ -310,7 +310,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { RepoWebhookAction::Clone => { format!("{host}/listener/github/repo/{}/clone", repo.id) } @@ -348,64 +348,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !repo.config.webhook_enabled { - self - .resolve( - UpdateRepo { - id: repo.id, - config: PartialRepoConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update repo to enable webhook")?; + UpdateRepo { + id: repo.id, + config: PartialRepoConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update repo to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteRepoWebhook", skip(self, user))] +impl Resolve for DeleteRepoWebhook { + #[instrument(name = "DeleteRepoWebhook", skip(user))] async fn resolve( - &self, - DeleteRepoWebhook { repo, action }: DeleteRepoWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Write, ) .await?; if repo.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if repo.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = repo.config.repo.split('/'); let owner = split.next().context("Repo repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo_name = @@ -431,7 +432,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { RepoWebhookAction::Clone => { format!("{host}/listener/github/repo/{}/clone", repo.id) } diff --git a/bin/core/src/api/write/server.rs b/bin/core/src/api/write/server.rs index d9a60abc7..da9a91d73 100644 --- a/bin/core/src/api/write/server.rs +++ b/bin/core/src/api/write/server.rs @@ -5,7 +5,6 @@ use komodo_client::{ permission::PermissionLevel, server::Server, update::{Update, UpdateStatus}, - user::User, Operation, }, }; @@ -18,63 +17,59 @@ use crate::{ update::{add_update, make_update, update_update}, }, resource, - state::State, }; -impl Resolve for State { - #[instrument(name = "CreateServer", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServer { + #[instrument(name = "CreateServer", skip(user))] async fn resolve( - &self, - CreateServer { name, config }: CreateServer, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteServer", skip(self, user))] - async fn resolve( - &self, - DeleteServer { id }: DeleteServer, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteServer { + #[instrument(name = "DeleteServer", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateServer", skip(self, user))] +impl Resolve for UpdateServer { + #[instrument(name = "UpdateServer", skip(user))] async fn resolve( - &self, - UpdateServer { id, config }: UpdateServer, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameServer", skip(self, user))] +impl Resolve for RenameServer { + #[instrument(name = "RenameServer", skip(user))] async fn resolve( - &self, - RenameServer { id, name }: RenameServer, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { - #[instrument(name = "CreateNetwork", skip(self, user))] +impl Resolve for CreateNetwork { + #[instrument(name = "CreateNetwork", skip(user))] async fn resolve( - &self, - CreateNetwork { server, name }: CreateNetwork, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Write, ) .await?; @@ -87,7 +82,10 @@ impl Resolve for State { update.id = add_update(update.clone()).await?; match periphery - .request(api::network::CreateNetwork { name, driver: None }) + .request(api::network::CreateNetwork { + name: self.name, + driver: None, + }) .await { Ok(log) => update.logs.push(log), diff --git a/bin/core/src/api/write/server_template.rs b/bin/core/src/api/write/server_template.rs index 0a873349d..9d8118787 100644 --- a/bin/core/src/api/write/server_template.rs +++ b/bin/core/src/api/write/server_template.rs @@ -5,72 +5,88 @@ use komodo_client::{ }, entities::{ permission::PermissionLevel, server_template::ServerTemplate, - update::Update, user::User, + update::Update, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateServerTemplate", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServerTemplate { + #[instrument(name = "CreateServerTemplate", skip(user))] async fn resolve( - &self, - CreateServerTemplate { name, config }: CreateServerTemplate, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::( + &self.name, + self.config, + &user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyServerTemplate", skip(self, user))] +impl Resolve for CopyServerTemplate { + #[instrument(name = "CopyServerTemplate", skip(user))] async fn resolve( - &self, - CopyServerTemplate { name, id }: CopyServerTemplate, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let ServerTemplate { config, .. } = resource::get_check_permissions::( - &id, + &self.id, &user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user) - .await + Ok( + resource::create::( + &self.name, + config.into(), + &user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteServerTemplate", skip(self, user))] +impl Resolve for DeleteServerTemplate { + #[instrument(name = "DeleteServerTemplate", skip(args))] async fn resolve( - &self, - DeleteServerTemplate { id }: DeleteServerTemplate, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateServerTemplate", skip(self, user))] +impl Resolve for UpdateServerTemplate { + #[instrument(name = "UpdateServerTemplate", skip(user))] async fn resolve( - &self, - UpdateServerTemplate { id, config }: UpdateServerTemplate, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameServerTemplate", skip(self, user))] +impl Resolve for RenameServerTemplate { + #[instrument(name = "RenameServerTemplate", skip(user))] async fn resolve( - &self, - RenameServerTemplate { id, name }: RenameServerTemplate, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) } } diff --git a/bin/core/src/api/write/service_user.rs b/bin/core/src/api/write/service_user.rs index e23884656..964250089 100644 --- a/bin/core/src/api/write/service_user.rs +++ b/bin/core/src/api/write/service_user.rs @@ -2,16 +2,7 @@ use std::str::FromStr; use anyhow::{anyhow, Context}; use komodo_client::{ - api::{ - user::CreateApiKey, - write::{ - CreateApiKeyForServiceUser, CreateApiKeyForServiceUserResponse, - CreateServiceUser, CreateServiceUserResponse, - DeleteApiKeyForServiceUser, DeleteApiKeyForServiceUserResponse, - UpdateServiceUserDescription, - UpdateServiceUserDescriptionResponse, - }, - }, + api::{user::CreateApiKey, write::*}, entities::{ komodo_timestamp, user::{User, UserConfig}, @@ -23,28 +14,30 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{api::user::UserArgs, state::db_client}; -impl Resolve for State { - #[instrument(name = "CreateServiceUser", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServiceUser { + #[instrument(name = "CreateServiceUser", skip(user))] async fn resolve( - &self, - CreateServiceUser { - username, - description, - }: CreateServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } - if ObjectId::from_str(&username).is_ok() { - return Err(anyhow!("username cannot be valid ObjectId")); + if ObjectId::from_str(&self.username).is_ok() { + return Err( + anyhow!("username cannot be valid ObjectId").into(), + ); } - let config = UserConfig::Service { description }; + let config = UserConfig::Service { + description: self.description, + }; let mut user = User { id: Default::default(), - username, + username: self.username, config, enabled: true, admin: false, @@ -69,88 +62,81 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "UpdateServiceUserDescription", - skip(self, user) - )] +impl Resolve for UpdateServiceUserDescription { + #[instrument(name = "UpdateServiceUserDescription", skip(user))] async fn resolve( - &self, - UpdateServiceUserDescription { - username, - description, - }: UpdateServiceUserDescription, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } let db = db_client(); let service_user = db .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("failed to query db for user")? .context("no user with given username")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; db.users .update_one( - doc! { "username": &username }, - doc! { "$set": { "config.data.description": description } }, + doc! { "username": &self.username }, + doc! { "$set": { "config.data.description": self.description } }, ) .await .context("failed to update user on db")?; - db.users - .find_one(doc! { "username": &username }) + let res = db + .users + .find_one(doc! { "username": &self.username }) .await .context("failed to query db for user")? - .context("user with username not found") + .context("user with username not found")?; + Ok(res) } } -impl Resolve for State { - #[instrument(name = "CreateApiKeyForServiceUser", skip(self, user))] +impl Resolve for CreateApiKeyForServiceUser { + #[instrument(name = "CreateApiKeyForServiceUser", skip(user))] async fn resolve( - &self, - CreateApiKeyForServiceUser { - user_id, - name, - expires, - }: CreateApiKeyForServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } - let service_user = find_one_by_id(&db_client().users, &user_id) - .await - .context("failed to query db for user")? - .context("no user found with id")?; + let service_user = + find_one_by_id(&db_client().users, &self.user_id) + .await + .context("failed to query db for user")? + .context("no user found with id")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; - self - .resolve(CreateApiKey { name, expires }, service_user) - .await + CreateApiKey { + name: self.name, + expires: self.expires, + } + .resolve(&UserArgs { user: service_user }) + .await } } -impl Resolve for State { - #[instrument(name = "DeleteApiKeyForServiceUser", skip(self, user))] +impl Resolve for DeleteApiKeyForServiceUser { + #[instrument(name = "DeleteApiKeyForServiceUser", skip(user))] async fn resolve( - &self, - DeleteApiKeyForServiceUser { key }: DeleteApiKeyForServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } let db = db_client(); let api_key = db .api_keys - .find_one(doc! { "key": &key }) + .find_one(doc! { "key": &self.key }) .await .context("failed to query db for api key")? .context("did not find matching api key")?; @@ -160,10 +146,10 @@ impl Resolve for State { .context("failed to query db for user")? .context("no user found with id")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; db.api_keys - .delete_one(doc! { "key": key }) + .delete_one(doc! { "key": self.key }) .await .context("failed to delete api key on db")?; Ok(DeleteApiKeyForServiceUserResponse {}) diff --git a/bin/core/src/api/write/stack.rs b/bin/core/src/api/write/stack.rs index 4c14764b0..74401f92d 100644 --- a/bin/core/src/api/write/stack.rs +++ b/bin/core/src/api/write/stack.rs @@ -8,7 +8,7 @@ use komodo_client::{ server::ServerState, stack::{PartialStackConfig, Stack, StackInfo}, update::Update, - user::{stack_user, User}, + user::stack_user, FileContents, NoData, Operation, }, }; @@ -36,81 +36,81 @@ use crate::{ remote::{get_repo_compose_contents, RemoteComposeContents}, services::extract_services_into_res, }, - state::{db_client, github_client, State}, + state::{db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateStack", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateStack { + #[instrument(name = "CreateStack", skip(user))] async fn resolve( - &self, - CreateStack { name, config }: CreateStack, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyStack", skip(self, user))] +impl Resolve for CopyStack { + #[instrument(name = "CopyStack", skip(user))] async fn resolve( - &self, - CopyStack { name, id }: CopyStack, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Stack { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteStack", skip(self, user))] - async fn resolve( - &self, - DeleteStack { id }: DeleteStack, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteStack { + #[instrument(name = "DeleteStack", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateStack", skip(self, user))] +impl Resolve for UpdateStack { + #[instrument(name = "UpdateStack", skip(user))] async fn resolve( - &self, - UpdateStack { id, config }: UpdateStack, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameStack", skip(self, user))] +impl Resolve for RenameStack { + #[instrument(name = "RenameStack", skip(user))] async fn resolve( - &self, - RenameStack { id, name }: RenameStack, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { +impl Resolve for WriteStackFileContents { async fn resolve( - &self, - WriteStackFileContents { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let WriteStackFileContents { stack, file_path, contents, - }: WriteStackFileContents, - user: User, - ) -> anyhow::Result { + } = self; let (mut stack, server) = get_stack_and_server( &stack, &user, @@ -122,7 +122,7 @@ impl Resolve for State { if !stack.config.files_on_host && stack.config.repo.is_empty() { return Err(anyhow!( "Stack is not configured to use Files on Host or Git Repo, can't write file contents" - )); + ).into()); } let mut update = @@ -173,7 +173,7 @@ impl Resolve for State { match periphery_client(&server)? .request(WriteCommitComposeContents { stack, - username: Some(user.username), + username: Some(user.username.clone()), file_path, contents, git_token, @@ -193,12 +193,12 @@ impl Resolve for State { }; } - if let Err(e) = State - .resolve( - RefreshStackCache { stack: stack_id }, - stack_user().to_owned(), - ) + if let Err(e) = (RefreshStackCache { stack: stack_id }) + .resolve(&WriteArgs { + user: stack_user().to_owned(), + }) .await + .map_err(|e| e.error) .context( "Failed to refresh stack cache after writing file contents", ) @@ -216,22 +216,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RefreshStackCache { #[instrument( name = "RefreshStackCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshStackCache { stack }: RefreshStackCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // stack should be able to do this. let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, ) .await?; @@ -300,9 +299,9 @@ impl Resolve for State { &mut services, ) { warn!( - "failed to extract stack services, things won't works correctly. stack: {} | {e:#}", - stack.name - ); + "failed to extract stack services, things won't works correctly. stack: {} | {e:#}", + stack.name + ); } } @@ -414,39 +413,44 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateStackWebhook", skip(self, user))] +impl Resolve for CreateStackWebhook { + #[instrument(name = "CreateStackWebhook", skip(args))] async fn resolve( - &self, - CreateStackWebhook { stack, action }: CreateStackWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; + let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Write, ) .await?; if stack.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = stack.config.repo.split('/'); let owner = split.next().context("Stack repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -479,7 +483,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { StackWebhookAction::Refresh => { format!("{host}/listener/github/stack/{}/refresh", stack.id) } @@ -514,64 +518,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !stack.config.webhook_enabled { - self - .resolve( - UpdateStack { - id: stack.id, - config: PartialStackConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update stack to enable webhook")?; + UpdateStack { + id: stack.id, + config: PartialStackConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update stack to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteStackWebhook", skip(self, user))] +impl Resolve for DeleteStackWebhook { + #[instrument(name = "DeleteStackWebhook", skip(user))] async fn resolve( - &self, - DeleteStackWebhook { stack, action }: DeleteStackWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Write, ) .await?; if stack.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if stack.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = stack.config.repo.split('/'); let owner = split.next().context("Stack repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -597,7 +602,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { StackWebhookAction::Refresh => { format!("{host}/listener/github/stack/{}/refresh", stack.id) } diff --git a/bin/core/src/api/write/sync.rs b/bin/core/src/api/write/sync.rs index 7d522c657..47ec02b3b 100644 --- a/bin/core/src/api/write/sync.rs +++ b/bin/core/src/api/write/sync.rs @@ -26,7 +26,7 @@ use komodo_client::{ }, to_komodo_name, update::{Log, Update}, - user::{sync_user, User}, + user::sync_user, CloneArgs, NoData, Operation, ResourceTarget, }, }; @@ -42,104 +42,123 @@ use tokio::fs; use crate::{ alert::send_alerts, + api::read::ReadArgs, config::core_config, helpers::{ query::get_id_to_tags, update::{add_update, make_update, update_update}, }, resource::{self, refresh_resource_sync_state_cache}, - state::{db_client, github_client, State}, + state::{db_client, github_client}, sync::{ deploy::SyncDeployParams, remote::RemoteResources, view::push_updates_for_view, AllResourcesById, }, }; -impl Resolve for State { - #[instrument(name = "CreateResourceSync", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateResourceSync { + #[instrument(name = "CreateResourceSync", skip(user))] async fn resolve( - &self, - CreateResourceSync { name, config }: CreateResourceSync, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::( + &self.name, + self.config, + &user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyResourceSync", skip(self, user))] +impl Resolve for CopyResourceSync { + #[instrument(name = "CopyResourceSync", skip(user))] async fn resolve( - &self, - CopyResourceSync { name, id }: CopyResourceSync, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let ResourceSync { config, .. } = resource::get_check_permissions::( - &id, + &self.id, &user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user) - .await + Ok( + resource::create::( + &self.name, + config.into(), + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteResourceSync", skip(self, user))] +impl Resolve for DeleteResourceSync { + #[instrument(name = "DeleteResourceSync", skip(args))] async fn resolve( - &self, - DeleteResourceSync { id }: DeleteResourceSync, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateResourceSync", skip(self, user))] +impl Resolve for UpdateResourceSync { + #[instrument(name = "UpdateResourceSync", skip(user))] async fn resolve( - &self, - UpdateResourceSync { id, config }: UpdateResourceSync, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameResourceSync", skip(self, user))] +impl Resolve for RenameResourceSync { + #[instrument(name = "RenameResourceSync", skip(user))] async fn resolve( - &self, - RenameResourceSync { id, name }: RenameResourceSync, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) } } -impl Resolve for State { - async fn resolve( - &self, - WriteSyncFileContents { +impl Resolve for WriteSyncFileContents { + async fn resolve(self, args: &WriteArgs) -> serror::Result { + let WriteSyncFileContents { sync, resource_path, file_path, contents, - }: WriteSyncFileContents, - user: User, - ) -> anyhow::Result { + } = self; + let WriteArgs { user } = args; + let sync = resource::get_check_permissions::( &sync, - &user, + user, PermissionLevel::Write, ) .await?; if !sync.config.files_on_host && sync.config.repo.is_empty() { - return Err(anyhow!( + return Err( + anyhow!( "This method is only for files on host, or repo based syncs." - )); + ) + .into(), + ); } let mut update = @@ -187,12 +206,14 @@ impl Resolve for State { } if sync.config.files_on_host { - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) .await { - update - .push_error_log("Refresh failed", format_serror(&e.into())); + update.push_error_log( + "Refresh failed", + format_serror(&e.error.into()), + ); } update.finalize(); @@ -210,12 +231,14 @@ impl Resolve for State { update.logs.extend(commit_res.logs); - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) .await { - update - .push_error_log("Refresh failed", format_serror(&e.into())); + update.push_error_log( + "Refresh failed", + format_serror(&e.error.into()), + ); } update.finalize(); @@ -225,16 +248,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CommitSync", skip(self, user))] - async fn resolve( - &self, - CommitSync { sync }: CommitSync, - user: User, - ) -> anyhow::Result { +impl Resolve for CommitSync { + #[instrument(name = "CommitSync", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + let WriteArgs { user } = args; + let sync = resource::get_check_permissions::< entities::sync::ResourceSync, - >(&sync, &user, PermissionLevel::Write) + >(&self.sync, &user, PermissionLevel::Write) .await?; let file_contents_empty = sync.config.file_contents_empty(); @@ -244,9 +265,10 @@ impl Resolve for State { && file_contents_empty; if !sync.config.managed && !fresh_sync { - return Err(anyhow!( - "Cannot commit to sync. Enabled 'managed' mode." - )); + return Err( + anyhow!("Cannot commit to sync. Enabled 'managed' mode.") + .into(), + ); } // Get this here so it can fail before update created. @@ -265,23 +287,22 @@ impl Resolve for State { .context("Resource path missing '.toml' extension")? != "toml" { - return Err(anyhow!( - "Resource path missing '.toml' extension" - )); + return Err( + anyhow!("Resource path missing '.toml' extension").into(), + ); } Some(resource_path) } else { None }; - let res = State - .resolve( - ExportAllResourcesToToml { - tags: sync.config.match_tags.clone(), - }, - sync_user().to_owned(), - ) - .await?; + let res = ExportAllResourcesToToml { + tags: sync.config.match_tags.clone(), + } + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await?; let mut update = make_update(&sync, Operation::CommitSync, &user); update.id = add_update(update.clone()).await?; @@ -366,13 +387,13 @@ impl Resolve for State { return Ok(update); } - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) .await { update.push_error_log( "Refresh sync pending", - format_serror(&(&e).into()), + format_serror(&e.error.into()), ); }; @@ -398,22 +419,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RefreshResourceSyncPending { #[instrument( name = "RefreshResourceSyncPending", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshResourceSyncPending { sync }: RefreshResourceSyncPending, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // sync should be able to do this. let mut sync = resource::get_check_permissions::< entities::sync::ResourceSync, - >(&sync, &user, PermissionLevel::Execute) + >(&self.sync, user, PermissionLevel::Execute) .await?; if !sync.config.managed @@ -443,9 +463,12 @@ impl Resolve for State { sync.info.pending_message = message; if !sync.info.remote_errors.is_empty() { - return Err(anyhow!( - "Remote resources have errors. Cannot compute diffs." - )); + return Err( + anyhow!( + "Remote resources have errors. Cannot compute diffs." + ) + .into(), + ); } let resources = resources?; @@ -744,43 +767,47 @@ impl Resolve for State { } }); - crate::resource::get::(&sync.id).await + Ok(crate::resource::get::(&sync.id).await?) } } -impl Resolve for State { - #[instrument(name = "CreateSyncWebhook", skip(self, user))] +impl Resolve for CreateSyncWebhook { + #[instrument(name = "CreateSyncWebhook", skip(args))] async fn resolve( - &self, - CreateSyncWebhook { sync, action }: CreateSyncWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Write, ) .await?; if sync.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = sync.config.repo.split('/'); let owner = split.next().context("Sync repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -813,7 +840,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { SyncWebhookAction::Refresh => { format!("{host}/listener/github/sync/{}/refresh", sync.id) } @@ -848,64 +875,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !sync.config.webhook_enabled { - self - .resolve( - UpdateResourceSync { - id: sync.id, - config: PartialResourceSyncConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update sync to enable webhook")?; + UpdateResourceSync { + id: sync.id, + config: PartialResourceSyncConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update sync to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteSyncWebhook", skip(self, user))] +impl Resolve for DeleteSyncWebhook { + #[instrument(name = "DeleteSyncWebhook", skip(user))] async fn resolve( - &self, - DeleteSyncWebhook { sync, action }: DeleteSyncWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Write, ) .await?; if sync.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if sync.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = sync.config.repo.split('/'); let owner = split.next().context("Sync repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -931,7 +959,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { SyncWebhookAction::Refresh => { format!("{host}/listener/github/sync/{}/refresh", sync.id) } diff --git a/bin/core/src/api/write/tag.rs b/bin/core/src/api/write/tag.rs index a45b37850..fdb87fa1a 100644 --- a/bin/core/src/api/write/tag.rs +++ b/bin/core/src/api/write/tag.rs @@ -11,7 +11,7 @@ use komodo_client::{ deployment::Deployment, permission::PermissionLevel, procedure::Procedure, repo::Repo, server::Server, server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, tag::Tag, user::User, ResourceTarget, + sync::ResourceSync, tag::Tag, ResourceTarget, }, }; use mungos::{ @@ -23,23 +23,24 @@ use resolver_api::Resolve; use crate::{ helpers::query::{get_tag, get_tag_check_owner}, resource, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "CreateTag", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateTag { + #[instrument(name = "CreateTag", skip(user))] async fn resolve( - &self, - CreateTag { name }: CreateTag, - user: User, - ) -> anyhow::Result { - if ObjectId::from_str(&name).is_ok() { - return Err(anyhow!("tag name cannot be ObjectId")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + if ObjectId::from_str(&self.name).is_ok() { + return Err(anyhow!("tag name cannot be ObjectId").into()); } let mut tag = Tag { id: Default::default(), - name, + name: self.name, owner: user.id.clone(), }; @@ -57,167 +58,170 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RenameTag", skip(self, user))] +impl Resolve for RenameTag { + #[instrument(name = "RenameTag", skip(user))] async fn resolve( - &self, - RenameTag { id, name }: RenameTag, - user: User, - ) -> anyhow::Result { - if ObjectId::from_str(&name).is_ok() { - return Err(anyhow!("tag name cannot be ObjectId")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + if ObjectId::from_str(&self.name).is_ok() { + return Err(anyhow!("tag name cannot be ObjectId").into()); } - get_tag_check_owner(&id, &user).await?; + get_tag_check_owner(&self.id, &user).await?; update_one_by_id( &db_client().tags, - &id, - doc! { "$set": { "name": name } }, + &self.id, + doc! { "$set": { "name": self.name } }, None, ) .await .context("failed to rename tag on db")?; - get_tag(&id).await + Ok(get_tag(&self.id).await?) } } -impl Resolve for State { - #[instrument(name = "DeleteTag", skip(self, user))] +impl Resolve for DeleteTag { + #[instrument(name = "DeleteTag", skip(user))] async fn resolve( - &self, - DeleteTag { id }: DeleteTag, - user: User, - ) -> anyhow::Result { - let tag = get_tag_check_owner(&id, &user).await?; + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let tag = get_tag_check_owner(&self.id, &user).await?; tokio::try_join!( - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), )?; - delete_one_by_id(&db_client().tags, &id, None).await?; + delete_one_by_id(&db_client().tags, &self.id, None).await?; Ok(tag) } } -impl Resolve for State { - #[instrument(name = "UpdateTagsOnResource", skip(self, user))] +impl Resolve for UpdateTagsOnResource { + #[instrument(name = "UpdateTagsOnResource", skip(args))] async fn resolve( - &self, - UpdateTagsOnResource { target, tags }: UpdateTagsOnResource, - user: User, - ) -> anyhow::Result { - match target { - ResourceTarget::System(_) => return Err(anyhow!("")), + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; + match self.target { + ResourceTarget::System(_) => { + return Err(anyhow!("Invalid target type: System").into()) + } ResourceTarget::Build(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await?; + resource::update_tags::(&id, self.tags, args).await?; } ResourceTarget::Builder(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Deployment(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Server(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Repo(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Alerter(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Procedure(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Action(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::ServerTemplate(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user) + resource::update_tags::(&id, self.tags, args) .await? } ResourceTarget::ResourceSync(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Stack(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } }; Ok(UpdateTagsOnResourceResponse {}) diff --git a/bin/core/src/api/write/user.rs b/bin/core/src/api/write/user.rs index 764a435b6..5ddd638c5 100644 --- a/bin/core/src/api/write/user.rs +++ b/bin/core/src/api/write/user.rs @@ -7,46 +7,41 @@ use komodo_client::{ UpdateUserPasswordResponse, UpdateUserUsername, UpdateUserUsernameResponse, }, - entities::{ - user::{User, UserConfig}, - NoData, - }, + entities::{user::UserConfig, NoData}, }; use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; -use crate::{ - helpers::hash_password, - state::{db_client, State}, -}; +use crate::{helpers::hash_password, state::db_client}; + +use super::WriteArgs; // -impl Resolve for State { +impl Resolve for UpdateUserUsername { async fn resolve( - &self, - UpdateUserUsername { username }: UpdateUserUsername, - user: User, - ) -> anyhow::Result { - if username.is_empty() { - return Err(anyhow!("Username cannot be empty.")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + if self.username.is_empty() { + return Err(anyhow!("Username cannot be empty.").into()); } let db = db_client(); if db .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("Failed to query for existing users")? .is_some() { - return Err(anyhow!("Username already taken.")); + return Err(anyhow!("Username already taken.").into()); } let id = ObjectId::from_str(&user.id) .context("User id not valid ObjectId.")?; db.users .update_one( doc! { "_id": id }, - doc! { "$set": { "username": username } }, + doc! { "$set": { "username": self.username } }, ) .await .context("Failed to update user username on database.")?; @@ -56,21 +51,20 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for UpdateUserPassword { async fn resolve( - &self, - UpdateUserPassword { password }: UpdateUserPassword, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let UserConfig::Local { .. } = user.config else { - return Err(anyhow!("User is not local user")); + return Err(anyhow!("User is not local user").into()); }; - if password.is_empty() { - return Err(anyhow!("Password cannot be empty.")); + if self.password.is_empty() { + return Err(anyhow!("Password cannot be empty.").into()); } let id = ObjectId::from_str(&user.id) .context("User id not valid ObjectId.")?; - let hashed_password = hash_password(password)?; + let hashed_password = hash_password(self.password)?; db_client() .users .update_one( @@ -87,22 +81,21 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for DeleteUser { async fn resolve( - &self, - DeleteUser { user }: DeleteUser, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("Calling user is not admin.")); + return Err(anyhow!("Calling user is not admin.").into()); } - if admin.username == user || admin.id == user { - return Err(anyhow!("User cannot delete themselves.")); + if admin.username == self.user || admin.id == self.user { + return Err(anyhow!("User cannot delete themselves.").into()); } - let query = if let Ok(id) = ObjectId::from_str(&user) { + let query = if let Ok(id) = ObjectId::from_str(&self.user) { doc! { "_id": id } } else { - doc! { "username": user } + doc! { "username": self.user } }; let db = db_client(); let Some(user) = db @@ -111,15 +104,20 @@ impl Resolve for State { .await .context("Failed to query database for users.")? else { - return Err(anyhow!("No user found with given id / username")); + return Err( + anyhow!("No user found with given id / username").into(), + ); }; if user.super_admin { - return Err(anyhow!("Cannot delete a super admin user.")); + return Err( + anyhow!("Cannot delete a super admin user.").into(), + ); } if user.admin && !admin.super_admin { - return Err(anyhow!( - "Only a Super Admin can delete an admin user." - )); + return Err( + anyhow!("Only a Super Admin can delete an admin user.") + .into(), + ); } db.users .delete_one(query) diff --git a/bin/core/src/api/write/user_group.rs b/bin/core/src/api/write/user_group.rs index 95f34170a..407c7dc21 100644 --- a/bin/core/src/api/write/user_group.rs +++ b/bin/core/src/api/write/user_group.rs @@ -6,7 +6,7 @@ use komodo_client::{ AddUserToUserGroup, CreateUserGroup, DeleteUserGroup, RemoveUserFromUserGroup, RenameUserGroup, SetUsersInUserGroup, }, - entities::{komodo_timestamp, user::User, user_group::UserGroup}, + entities::{komodo_timestamp, user_group::UserGroup}, }; use mungos::{ by_id::{delete_one_by_id, find_one_by_id, update_one_by_id}, @@ -15,23 +15,24 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for CreateUserGroup { async fn resolve( - &self, - CreateUserGroup { name }: CreateUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let user_group = UserGroup { id: Default::default(), users: Default::default(), all: Default::default(), updated_at: komodo_timestamp(), - name, + name: self.name, }; let db = db_client(); let id = db @@ -43,63 +44,63 @@ impl Resolve for State { .as_object_id() .context("inserted id is not ObjectId")? .to_string(); - find_one_by_id(&db.user_groups, &id) + let res = find_one_by_id(&db.user_groups, &id) .await .context("failed to query db for user groups")? - .context("user group at id not found") + .context("user group at id not found")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for RenameUserGroup { async fn resolve( - &self, - RenameUserGroup { id, name }: RenameUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); update_one_by_id( &db.user_groups, - &id, - doc! { "$set": { "name": name } }, + &self.id, + doc! { "$set": { "name": self.name } }, None, ) .await .context("failed to rename UserGroup on db")?; - find_one_by_id(&db.user_groups, &id) + let res = find_one_by_id(&db.user_groups, &self.id) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for DeleteUserGroup { async fn resolve( - &self, - DeleteUserGroup { id }: DeleteUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let ug = find_one_by_id(&db.user_groups, &id) + let ug = find_one_by_id(&db.user_groups, &self.id) .await .context("failed to query db for UserGroups")? .context("no UserGroup found with given id")?; - delete_one_by_id(&db.user_groups, &id, None) + delete_one_by_id(&db.user_groups, &self.id, None) .await .context("failed to delete UserGroup from db")?; db.permissions .delete_many(doc! { "user_target.type": "UserGroup", - "user_target.id": id, + "user_target.id": self.id, }) .await .context("failed to clean up UserGroups permissions. User Group has been deleted")?; @@ -108,21 +109,20 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for AddUserToUserGroup { async fn resolve( - &self, - AddUserToUserGroup { user_group, user }: AddUserToUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let filter = match ObjectId::from_str(&user) { + let filter = match ObjectId::from_str(&self.user) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "username": &user }, + Err(_) => doc! { "username": &self.user }, }; let user = db .users @@ -131,9 +131,9 @@ impl Resolve for State { .context("failed to query mongo for users")? .context("no matching user found")?; - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one( @@ -142,32 +142,30 @@ impl Resolve for State { ) .await .context("failed to add user to group on db")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for RemoveUserFromUserGroup { async fn resolve( - &self, - RemoveUserFromUserGroup { - user_group, - user, - }: RemoveUserFromUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let filter = match ObjectId::from_str(&user) { + let filter = match ObjectId::from_str(&self.user) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "username": &user }, + Err(_) => doc! { "username": &self.user }, }; let user = db .users @@ -176,9 +174,9 @@ impl Resolve for State { .context("failed to query mongo for users")? .context("no matching user found")?; - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one( @@ -187,22 +185,23 @@ impl Resolve for State { ) .await .context("failed to add user to group on db")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SetUsersInUserGroup { async fn resolve( - &self, - SetUsersInUserGroup { user_group, users }: SetUsersInUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); @@ -215,7 +214,8 @@ impl Resolve for State { .collect::>(); // Make sure all users are user ids - let users = users + let users = self + .users .into_iter() .filter_map(|user| match ObjectId::from_str(&user) { Ok(_) => Some(user), @@ -223,18 +223,20 @@ impl Resolve for State { }) .collect::>(); - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one(filter.clone(), doc! { "$set": { "users": users } }) .await .context("failed to set users on user group")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } diff --git a/bin/core/src/api/write/variable.rs b/bin/core/src/api/write/variable.rs index aef49517c..2f5335420 100644 --- a/bin/core/src/api/write/variable.rs +++ b/bin/core/src/api/write/variable.rs @@ -1,15 +1,7 @@ use anyhow::{anyhow, Context}; use komodo_client::{ - api::write::{ - CreateVariable, CreateVariableResponse, DeleteVariable, - DeleteVariableResponse, UpdateVariableDescription, - UpdateVariableDescriptionResponse, UpdateVariableIsSecret, - UpdateVariableIsSecretResponse, UpdateVariableValue, - UpdateVariableValueResponse, - }, - entities::{ - user::User, variable::Variable, Operation, ResourceTarget, - }, + api::write::*, + entities::{variable::Variable, Operation, ResourceTarget}, }; use mungos::mongodb::bson::doc; use resolver_api::Resolve; @@ -19,23 +11,26 @@ use crate::{ query::get_variable, update::{add_update, make_update}, }, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "CreateVariable", skip(self, user, value))] +use super::WriteArgs; + +impl Resolve for CreateVariable { + #[instrument(name = "CreateVariable", skip(user, self), fields(name = &self.name))] async fn resolve( - &self, - CreateVariable { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let CreateVariable { name, value, description, is_secret, - }: CreateVariable, - user: User, - ) -> anyhow::Result { + } = self; + if !user.admin { - return Err(anyhow!("only admins can create variables")); + return Err(anyhow!("only admins can create variables").into()); } let variable = Variable { @@ -63,21 +58,22 @@ impl Resolve for State { add_update(update).await?; - get_variable(&variable.name).await + Ok(get_variable(&variable.name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableValue", skip(self, user, value))] +impl Resolve for UpdateVariableValue { + #[instrument(name = "UpdateVariableValue", skip(user, self), fields(name = &self.name))] async fn resolve( - &self, - UpdateVariableValue { name, value }: UpdateVariableValue, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } + let UpdateVariableValue { name, value } = self; + let variable = get_variable(&name).await?; if value == variable.value { @@ -116,74 +112,71 @@ impl Resolve for State { add_update(update).await?; - get_variable(&name).await + Ok(get_variable(&name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableDescription", skip(self, user))] +impl Resolve for UpdateVariableDescription { + #[instrument(name = "UpdateVariableDescription", skip(user))] async fn resolve( - &self, - UpdateVariableDescription { name, description }: UpdateVariableDescription, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } db_client() .variables .update_one( - doc! { "name": &name }, - doc! { "$set": { "description": &description } }, + doc! { "name": &self.name }, + doc! { "$set": { "description": &self.description } }, ) .await .context("failed to update variable description on db")?; - get_variable(&name).await + Ok(get_variable(&self.name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableIsSecret", skip(self, user))] +impl Resolve for UpdateVariableIsSecret { + #[instrument(name = "UpdateVariableIsSecret", skip(user))] async fn resolve( - &self, - UpdateVariableIsSecret { name, is_secret }: UpdateVariableIsSecret, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } db_client() .variables .update_one( - doc! { "name": &name }, - doc! { "$set": { "is_secret": is_secret } }, + doc! { "name": &self.name }, + doc! { "$set": { "is_secret": self.is_secret } }, ) .await .context("failed to update variable is secret on db")?; - get_variable(&name).await + Ok(get_variable(&self.name).await?) } } -impl Resolve for State { +impl Resolve for DeleteVariable { async fn resolve( - &self, - DeleteVariable { name }: DeleteVariable, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can delete variables")); + return Err(anyhow!("only admins can delete variables").into()); } - let variable = get_variable(&name).await?; + let variable = get_variable(&self.name).await?; db_client() .variables - .delete_one(doc! { "name": &name }) + .delete_one(doc! { "name": &self.name }) .await .context("failed to delete variable on db")?; let mut update = make_update( ResourceTarget::system(), Operation::DeleteVariable, - &user, + user, ); update diff --git a/bin/core/src/auth/local.rs b/bin/core/src/auth/local.rs index 619d90e93..402e47da9 100644 --- a/bin/core/src/auth/local.rs +++ b/bin/core/src/auth/local.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; -use axum::http::HeaderMap; use komodo_client::{ api::auth::{ CreateLocalUser, CreateLocalUserResponse, LoginLocalUser, @@ -15,50 +14,52 @@ use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ + api::auth::AuthArgs, config::core_config, helpers::hash_password, - state::{db_client, jwt_client, State}, + state::{db_client, jwt_client}, }; -impl Resolve for State { +impl Resolve for CreateLocalUser { #[instrument(name = "CreateLocalUser", skip(self))] async fn resolve( - &self, - CreateLocalUser { username, password }: CreateLocalUser, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { let core_config = core_config(); if !core_config.local_auth { - return Err(anyhow!("Local auth is not enabled")); + return Err(anyhow!("Local auth is not enabled").into()); } - if username.is_empty() { - return Err(anyhow!("Username cannot be empty string")); + if self.username.is_empty() { + return Err(anyhow!("Username cannot be empty string").into()); } - if ObjectId::from_str(&username).is_ok() { - return Err(anyhow!("Username cannot be valid ObjectId")); + if ObjectId::from_str(&self.username).is_ok() { + return Err( + anyhow!("Username cannot be valid ObjectId").into(), + ); } - if password.is_empty() { - return Err(anyhow!("Password cannot be empty string")); + if self.password.is_empty() { + return Err(anyhow!("Password cannot be empty string").into()); } - let hashed_password = hash_password(password)?; + let hashed_password = hash_password(self.password)?; let no_users_exist = db_client().users.find_one(Document::new()).await?.is_none(); if !no_users_exist && core_config.disable_user_registration { - return Err(anyhow!("User registration is disabled")); + return Err(anyhow!("User registration is disabled").into()); } let ts = unix_timestamp_ms() as i64; let user = User { id: Default::default(), - username, + username: self.username, enabled: no_users_exist || core_config.enable_new_users, admin: no_users_exist, super_admin: no_users_exist, @@ -91,40 +92,42 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for LoginLocalUser { #[instrument(name = "LoginLocalUser", level = "debug", skip(self))] async fn resolve( - &self, - LoginLocalUser { username, password }: LoginLocalUser, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { if !core_config().local_auth { - return Err(anyhow!("local auth is not enabled")); + return Err(anyhow!("local auth is not enabled").into()); } let user = db_client() .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("failed at db query for users")? .with_context(|| { - format!("did not find user with username {username}") + format!("did not find user with username {}", self.username) })?; let UserConfig::Local { password: user_pw_hash, } = user.config else { - return Err(anyhow!( - "non-local auth users can not log in with a password" - )); + return Err( + anyhow!( + "non-local auth users can not log in with a password" + ) + .into(), + ); }; - let verified = bcrypt::verify(password, &user_pw_hash) + let verified = bcrypt::verify(self.password, &user_pw_hash) .context("failed at verify password")?; if !verified { - return Err(anyhow!("invalid credentials")); + return Err(anyhow!("invalid credentials").into()); } let jwt = jwt_client() diff --git a/bin/core/src/cloud/aws/ec2.rs b/bin/core/src/cloud/aws/ec2.rs index 03770c34d..4f8ace86d 100644 --- a/bin/core/src/cloud/aws/ec2.rs +++ b/bin/core/src/cloud/aws/ec2.rs @@ -1087,7 +1087,90 @@ fn handle_unknown_instance_type( | InstanceType::Z1d6xlarge | InstanceType::Z1dLarge | InstanceType::Z1dMetal - | InstanceType::Z1dXlarge => Ok(instance_type), + | InstanceType::Z1dXlarge + | InstanceType::C7gdMetal + | InstanceType::C7gnMetal + | InstanceType::C7iFlex2xlarge + | InstanceType::C7iFlex4xlarge + | InstanceType::C7iFlex8xlarge + | InstanceType::C7iFlexLarge + | InstanceType::C7iFlexXlarge + | InstanceType::C8g12xlarge + | InstanceType::C8g16xlarge + | InstanceType::C8g24xlarge + | InstanceType::C8g2xlarge + | InstanceType::C8g48xlarge + | InstanceType::C8g4xlarge + | InstanceType::C8g8xlarge + | InstanceType::C8gLarge + | InstanceType::C8gMedium + | InstanceType::C8gMetal24xl + | InstanceType::C8gMetal48xl + | InstanceType::C8gXlarge + | InstanceType::G612xlarge + | InstanceType::G616xlarge + | InstanceType::G624xlarge + | InstanceType::G62xlarge + | InstanceType::G648xlarge + | InstanceType::G64xlarge + | InstanceType::G68xlarge + | InstanceType::G6Xlarge + | InstanceType::G6e12xlarge + | InstanceType::G6e16xlarge + | InstanceType::G6e24xlarge + | InstanceType::G6e2xlarge + | InstanceType::G6e48xlarge + | InstanceType::G6e4xlarge + | InstanceType::G6e8xlarge + | InstanceType::G6eXlarge + | InstanceType::Gr64xlarge + | InstanceType::Gr68xlarge + | InstanceType::M7gdMetal + | InstanceType::M8g12xlarge + | InstanceType::M8g16xlarge + | InstanceType::M8g24xlarge + | InstanceType::M8g2xlarge + | InstanceType::M8g48xlarge + | InstanceType::M8g4xlarge + | InstanceType::M8g8xlarge + | InstanceType::M8gLarge + | InstanceType::M8gMedium + | InstanceType::M8gMetal24xl + | InstanceType::M8gMetal48xl + | InstanceType::M8gXlarge + | InstanceType::Mac2M1ultraMetal + | InstanceType::R7gdMetal + | InstanceType::R7izMetal16xl + | InstanceType::R7izMetal32xl + | InstanceType::R8g12xlarge + | InstanceType::R8g16xlarge + | InstanceType::R8g24xlarge + | InstanceType::R8g2xlarge + | InstanceType::R8g48xlarge + | InstanceType::R8g4xlarge + | InstanceType::R8g8xlarge + | InstanceType::R8gLarge + | InstanceType::R8gMedium + | InstanceType::R8gMetal24xl + | InstanceType::R8gMetal48xl + | InstanceType::R8gXlarge + | InstanceType::U7i12tb224xlarge + | InstanceType::U7ib12tb224xlarge + | InstanceType::U7in16tb224xlarge + | InstanceType::U7in24tb224xlarge + | InstanceType::U7in32tb224xlarge + | InstanceType::X8g12xlarge + | InstanceType::X8g16xlarge + | InstanceType::X8g24xlarge + | InstanceType::X8g2xlarge + | InstanceType::X8g48xlarge + | InstanceType::X8g4xlarge + | InstanceType::X8g8xlarge + | InstanceType::X8gLarge + | InstanceType::X8gMedium + | InstanceType::X8gMetal24xl + | InstanceType::X8gMetal48xl + | InstanceType::X8gXlarge => Ok(instance_type), other => Err(anyhow!("unknown InstanceType: {other:?}")), } } diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 086f3e290..cd229e786 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -25,9 +25,8 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng}; use resolver_api::Resolve; use crate::{ - config::core_config, - resource, - state::{db_client, State}, + api::write::WriteArgs, config::core_config, resource, + state::db_client, }; pub mod action_state; @@ -305,46 +304,44 @@ pub async fn ensure_first_server_and_builder() { let server = if let Some(server) = server { server } else { - match State - .resolve( - CreateServer { - name: format!("server-{}", random_string(5)), - config: PartialServerConfig { - address: Some(first_server.to_string()), - enabled: Some(true), - ..Default::default() - }, - }, - system_user().to_owned(), - ) - .await + match (CreateServer { + name: format!("server-{}", random_string(5)), + config: PartialServerConfig { + address: Some(first_server.to_string()), + enabled: Some(true), + ..Default::default() + }, + }) + .resolve(&WriteArgs { + user: system_user().to_owned(), + }) + .await { Ok(server) => server, Err(e) => { - error!("Failed to initialize 'first_server'. Failed to CreateServer. {e:?}"); + error!("Failed to initialize 'first_server'. Failed to CreateServer. {:#}", e.error); return; } } }; let Ok(None) = db.builders .find_one(Document::new()).await - .inspect_err(|e| error!("Failed to initialize 'first_builder'. Failed to query db. {e:?}")) else { + .inspect_err(|e| error!("Failed to initialize 'first_builder' | Failed to query db | {e:?}")) else { return; }; - if let Err(e) = State - .resolve( - CreateBuilder { - name: String::from("local"), - config: PartialBuilderConfig::Server( - PartialServerBuilderConfig { - server_id: Some(server.id), - }, - ), + if let Err(e) = (CreateBuilder { + name: String::from("local"), + config: PartialBuilderConfig::Server( + PartialServerBuilderConfig { + server_id: Some(server.id), }, - system_user().to_owned(), - ) - .await + ), + }) + .resolve(&WriteArgs { + user: system_user().to_owned(), + }) + .await { - error!("Failed to initialize 'first_builder'. Failed to CreateBuilder. {e:?}"); + error!("Failed to initialize 'first_builder' | Failed to CreateBuilder | {:#}", e.error); } } diff --git a/bin/core/src/helpers/procedure.rs b/bin/core/src/helpers/procedure.rs index a55a6fb10..3652fe9a8 100644 --- a/bin/core/src/helpers/procedure.rs +++ b/bin/core/src/helpers/procedure.rs @@ -21,9 +21,12 @@ use resolver_api::Resolve; use tokio::sync::Mutex; use crate::{ - api::execute::ExecuteRequest, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + write::WriteArgs, + }, resource::{list_full_for_user_using_pattern, KomodoResource}, - state::{db_client, State}, + state::db_client, }; use super::update::{init_execution_update, update_update}; @@ -227,9 +230,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunProcedure"), &update_id, ) @@ -249,9 +253,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunAction"), &update_id, ) @@ -271,9 +276,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunBuild"), &update_id, ) @@ -293,9 +299,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CancelBuild"), &update_id, ) @@ -309,9 +316,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at Deploy"), &update_id, ) @@ -331,9 +339,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullDeployment"), &update_id, ) @@ -347,9 +356,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartDeployment"), &update_id, ) @@ -363,9 +373,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartDeployment"), &update_id, ) @@ -379,9 +390,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseDeployment"), &update_id, ) @@ -395,9 +407,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseDeployment"), &update_id, ) @@ -411,9 +424,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopDeployment"), &update_id, ) @@ -427,9 +441,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RemoveDeployment"), &update_id, ) @@ -449,9 +464,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CloneRepo"), &update_id, ) @@ -471,9 +487,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullRepo"), &update_id, ) @@ -493,9 +510,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at BuildRepo"), &update_id, ) @@ -515,9 +533,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CancelRepoBuild"), &update_id, ) @@ -531,9 +550,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartContainer"), &update_id, ) @@ -547,9 +567,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartContainer"), &update_id, ) @@ -563,9 +584,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseContainer"), &update_id, ) @@ -579,9 +601,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseContainer"), &update_id, ) @@ -595,9 +618,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopContainer"), &update_id, ) @@ -611,9 +635,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RemoveContainer"), &update_id, ) @@ -627,9 +652,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartAllContainers"), &update_id, ) @@ -643,9 +669,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartAllContainers"), &update_id, ) @@ -659,9 +686,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseAllContainers"), &update_id, ) @@ -675,9 +703,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseAllContainers"), &update_id, ) @@ -691,9 +720,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopAllContainers"), &update_id, ) @@ -707,9 +737,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneContainers"), &update_id, ) @@ -723,9 +754,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteNetwork"), &update_id, ) @@ -739,9 +771,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneNetworks"), &update_id, ) @@ -755,9 +788,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteImage"), &update_id, ) @@ -771,9 +805,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneImages"), &update_id, ) @@ -787,9 +822,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteVolume"), &update_id, ) @@ -803,9 +839,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneVolumes"), &update_id, ) @@ -819,9 +856,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneDockerBuilders"), &update_id, ) @@ -835,9 +873,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneBuildx"), &update_id, ) @@ -851,9 +890,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneSystem"), &update_id, ) @@ -867,18 +907,20 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunSync"), &update_id, ) .await? } // Exception: This is a write operation. - Execution::CommitSync(req) => State - .resolve(req, user) + Execution::CommitSync(req) => req + .resolve(&WriteArgs { user }) .await + .map_err(|e| e.error) .context("Failed at CommitSync")?, Execution::DeployStack(req) => { let req = ExecuteRequest::DeployStack(req); @@ -888,9 +930,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeployStack"), &update_id, ) @@ -910,9 +953,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeployStackIfChanged"), &update_id, ) @@ -932,9 +976,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullStack"), &update_id, ) @@ -948,9 +993,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartStack"), &update_id, ) @@ -964,9 +1010,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartStack"), &update_id, ) @@ -980,9 +1027,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseStack"), &update_id, ) @@ -996,9 +1044,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseStack"), &update_id, ) @@ -1012,9 +1061,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopStack"), &update_id, ) @@ -1028,9 +1078,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DestroyStack"), &update_id, ) diff --git a/bin/core/src/listener/resources.rs b/bin/core/src/listener/resources.rs index b37a300dd..c46ebf886 100644 --- a/bin/core/src/listener/resources.rs +++ b/bin/core/src/listener/resources.rs @@ -15,8 +15,11 @@ use resolver_api::Resolve; use serde::Deserialize; use crate::{ - api::execute::ExecuteRequest, - helpers::update::init_execution_update, state::State, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + write::WriteArgs, + }, + helpers::update::init_execution_update, }; use super::{ListenerLockCache, ANY_BRANCH}; @@ -58,7 +61,10 @@ pub async fn handle_build_webhook( let ExecuteRequest::RunBuild(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } @@ -93,7 +99,10 @@ impl RepoExecution for CloneRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -110,7 +119,10 @@ impl RepoExecution for PullRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -127,7 +139,10 @@ impl RepoExecution for BuildRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -196,21 +211,22 @@ fn stack_locks() -> &'static ListenerLockCache { } pub trait StackExecution { - async fn resolve(stack: Stack) -> anyhow::Result<()>; + async fn resolve(stack: Stack) -> serror::Result<()>; } impl StackExecution for RefreshStackCache { - async fn resolve(stack: Stack) -> anyhow::Result<()> { - let user = git_webhook_user().to_owned(); - State - .resolve(RefreshStackCache { stack: stack.id }, user) + async fn resolve(stack: Stack) -> serror::Result<()> { + RefreshStackCache { stack: stack.id } + .resolve(&WriteArgs { + user: git_webhook_user().to_owned(), + }) .await?; Ok(()) } } impl StackExecution for DeployStack { - async fn resolve(stack: Stack) -> anyhow::Result<()> { + async fn resolve(stack: Stack) -> serror::Result<()> { let user = git_webhook_user().to_owned(); if stack.config.webhook_force_deploy { let req = ExecuteRequest::DeployStack(DeployStack { @@ -222,7 +238,10 @@ impl StackExecution for DeployStack { let ExecuteRequest::DeployStack(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; } else { let req = ExecuteRequest::DeployStackIfChanged(DeployStackIfChanged { @@ -233,7 +252,10 @@ impl StackExecution for DeployStack { let ExecuteRequest::DeployStackIfChanged(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; } Ok(()) @@ -282,7 +304,7 @@ pub async fn handle_stack_webhook_inner< B::verify_branch(&body, &stack.config.branch)?; - E::resolve(stack).await + E::resolve(stack).await.map_err(|e| e.error) } // ====== @@ -306,10 +328,12 @@ pub trait SyncExecution { impl SyncExecution for RefreshResourceSyncPending { async fn resolve(sync: ResourceSync) -> anyhow::Result<()> { - let user = git_webhook_user().to_owned(); - State - .resolve(RefreshResourceSyncPending { sync: sync.id }, user) - .await?; + RefreshResourceSyncPending { sync: sync.id } + .resolve(&WriteArgs { + user: git_webhook_user().to_owned(), + }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -326,7 +350,10 @@ impl SyncExecution for RunSync { let ExecuteRequest::RunSync(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -422,7 +449,10 @@ pub async fn handle_procedure_webhook( let ExecuteRequest::RunProcedure(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } @@ -467,6 +497,9 @@ pub async fn handle_action_webhook( let ExecuteRequest::RunAction(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } diff --git a/bin/core/src/main.rs b/bin/core/src/main.rs index 608c21f4e..e992bd418 100644 --- a/bin/core/src/main.rs +++ b/bin/core/src/main.rs @@ -23,6 +23,7 @@ mod helpers; mod listener; mod monitor; mod resource; +mod resource2; mod stack; mod state; mod sync; @@ -101,14 +102,16 @@ async fn app() -> anyhow::Result<()> { .context("Invalid ssl cert / key")?; axum_server::bind_rustls(socket_addr, ssl_config) .serve(app) - .await? + .await + .context("failed to start https server") } else { info!("🔓 Core SSL Disabled"); info!("Komodo Core starting on http://{socket_addr}"); - axum_server::bind(socket_addr).serve(app).await? + axum_server::bind(socket_addr) + .serve(app) + .await + .context("failed to start http server") } - - Ok(()) } #[tokio::main] @@ -116,15 +119,10 @@ async fn main() -> anyhow::Result<()> { let mut term_signal = tokio::signal::unix::signal( tokio::signal::unix::SignalKind::terminate(), )?; - - let app = tokio::spawn(app()); - tokio::select! { - res = app => return res?, - _ = term_signal.recv() => {}, + res = tokio::spawn(app()) => res?, + _ = term_signal.recv() => Ok(()), } - - Ok(()) } fn cors() -> anyhow::Result { diff --git a/bin/core/src/monitor/resources.rs b/bin/core/src/monitor/resources.rs index f2b3ea065..180b8759a 100644 --- a/bin/core/src/monitor/resources.rs +++ b/bin/core/src/monitor/resources.rs @@ -395,7 +395,7 @@ pub async fn update_stack_cache( send_alerts(&[alert]).await; } Err(e) => { - warn!("Failed auto update Stack {} | {e:#}", stack.name) + warn!("Failed auto update Stack {} | {e:#}", stack.name,) } } }); diff --git a/bin/core/src/resource/mod.rs b/bin/core/src/resource/mod.rs index 289b1a786..cba347f14 100644 --- a/bin/core/src/resource/mod.rs +++ b/bin/core/src/resource/mod.rs @@ -34,6 +34,7 @@ use resolver_api::Resolve; use serde::{de::DeserializeOwned, Serialize}; use crate::{ + api::{read::ReadArgs, write::WriteArgs}, config::core_config, helpers::{ create_permission, flatten_document, @@ -43,7 +44,7 @@ use crate::{ }, update::{add_update, make_update}, }, - state::{db_client, State}, + state::db_client, }; mod action; @@ -783,20 +784,17 @@ pub async fn update_description( pub async fn update_tags( id_or_name: &str, tags: Vec, - user: User, + args: &WriteArgs, ) -> anyhow::Result<()> { let futures = tags.iter().map(|tag| async { match get_tag(tag).await { Ok(tag) => Ok(tag.id), - Err(_) => State - .resolve( - CreateTag { - name: tag.to_string(), - }, - user.clone(), - ) - .await - .map(|tag| tag.id), + Err(_) => CreateTag { + name: tag.to_string(), + } + .resolve(args) + .await + .map(|tag| tag.id), } }); let tags = join_all(futures) @@ -884,11 +882,11 @@ pub async fn rename( pub async fn delete( id_or_name: &str, - user: &User, + args: &WriteArgs, ) -> anyhow::Result> { let resource = get_check_permissions::( id_or_name, - user, + &args.user, PermissionLevel::Write, ) .await?; @@ -898,19 +896,19 @@ pub async fn delete( } let target = resource_target::(resource.id.clone()); - let toml = State - .resolve( - ExportResourcesToToml { - targets: vec![target.clone()], - ..Default::default() - }, - user.clone(), - ) - .await? - .toml; + let toml = ExportResourcesToToml { + targets: vec![target.clone()], + ..Default::default() + } + .resolve(&ReadArgs { + user: args.user.clone(), + }) + .await + .map_err(|e| e.error)? + .toml; let mut update = - make_update(target.clone(), T::delete_operation(), user); + make_update(target.clone(), T::delete_operation(), &args.user); T::pre_delete(&resource, &mut update).await?; diff --git a/bin/core/src/resource/refresh.rs b/bin/core/src/resource/refresh.rs index 85da9807d..5740d6afd 100644 --- a/bin/core/src/resource/refresh.rs +++ b/bin/core/src/resource/refresh.rs @@ -12,9 +12,9 @@ use mungos::find::find_collect; use resolver_api::Resolve; use crate::{ - api::execute::pull_deployment_inner, + api::{execute::pull_deployment_inner, write::WriteArgs}, config::core_config, - state::{db_client, State}, + state::db_client, }; pub fn spawn_resource_refresh_loop() { @@ -53,14 +53,13 @@ async fn refresh_stacks() { return; }; for stack in stacks { - State + RefreshStackCache { stack: stack.id } .resolve( - RefreshStackCache { stack: stack.id }, - stack_user().clone(), + &WriteArgs { user: stack_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Stack cache in refresh task | Stack: {} | {e:#}", stack.name) + warn!("Failed to refresh Stack cache in refresh task | Stack: {} | {:#}", stack.name, e.error) }) .ok(); } @@ -115,14 +114,13 @@ async fn refresh_builds() { return; }; for build in builds { - State + RefreshBuildCache { build: build.id } .resolve( - RefreshBuildCache { build: build.id }, - build_user().clone(), + &WriteArgs { user: build_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Build cache in refresh task | Build: {} | {e:#}", build.name) + warn!("Failed to refresh Build cache in refresh task | Build: {} | {:#}", build.name, e.error) }) .ok(); } @@ -140,14 +138,13 @@ async fn refresh_repos() { return; }; for repo in repos { - State + RefreshRepoCache { repo: repo.id } .resolve( - RefreshRepoCache { repo: repo.id }, - repo_user().clone(), + &WriteArgs { user: repo_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Repo cache in refresh task | Repo: {} | {e:#}", repo.name) + warn!("Failed to refresh Repo cache in refresh task | Repo: {} | {:#}", repo.name, e.error) }) .ok(); } @@ -169,14 +166,13 @@ async fn refresh_syncs() { if sync.config.repo.is_empty() { continue; } - State + RefreshResourceSyncPending { sync: sync.id } .resolve( - RefreshResourceSyncPending { sync: sync.id }, - sync_user().clone(), + &WriteArgs { user: sync_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh ResourceSync in refresh task | Sync: {} | {e:#}", sync.name) + warn!("Failed to refresh ResourceSync in refresh task | Sync: {} | {:#}", sync.name, e.error) }) .ok(); } diff --git a/bin/core/src/resource/stack.rs b/bin/core/src/resource/stack.rs index 0de89ad9e..c51406377 100644 --- a/bin/core/src/resource/stack.rs +++ b/bin/core/src/resource/stack.rs @@ -21,12 +21,12 @@ use periphery_client::api::compose::ComposeExecution; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, config::core_config, helpers::{periphery_client, query::get_stack_state}, monitor::update_cache_for_server, state::{ - action_states, db_client, server_status_cache, - stack_status_cache, State, + action_states, db_client, server_status_cache, stack_status_cache, }, }; @@ -152,18 +152,17 @@ impl super::KomodoResource for Stack { created: &Resource, update: &mut Update, ) -> anyhow::Result<()> { - if let Err(e) = State - .resolve( - RefreshStackCache { - stack: created.name.clone(), - }, - stack_user().to_owned(), - ) - .await + if let Err(e) = (RefreshStackCache { + stack: created.name.clone(), + }) + .resolve(&WriteArgs { + user: stack_user().to_owned(), + }) + .await { update.push_error_log( "Refresh stack cache", - format_serror(&e.context("The stack cache has failed to refresh. This is likely due to a misconfiguration of the Stack").into()) + format_serror(&e.error.context("The stack cache has failed to refresh. This is likely due to a misconfiguration of the Stack").into()) ); }; if created.config.server_id.is_empty() { diff --git a/bin/core/src/resource/sync.rs b/bin/core/src/resource/sync.rs index b7b856647..b48442ecb 100644 --- a/bin/core/src/resource/sync.rs +++ b/bin/core/src/resource/sync.rs @@ -25,8 +25,9 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{ - action_states, db_client, resource_sync_state_cache, State, +use crate::{ + api::write::WriteArgs, + state::{action_states, db_client, resource_sync_state_cache}, }; impl super::KomodoResource for ResourceSync { @@ -103,18 +104,17 @@ impl super::KomodoResource for ResourceSync { created: &Resource, update: &mut Update, ) -> anyhow::Result<()> { - if let Err(e) = State - .resolve( - RefreshResourceSyncPending { - sync: created.id.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (RefreshResourceSyncPending { + sync: created.id.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { update.push_error_log( "Refresh sync pending", - format_serror(&e.context("The sync pending cache has failed to refresh. This is likely due to a misconfiguration of the sync").into()) + format_serror(&e.error.context("The sync pending cache has failed to refresh. This is likely due to a misconfiguration of the sync").into()) ); }; refresh_resource_sync_state_cache().await; diff --git a/bin/core/src/resource2/crud.rs b/bin/core/src/resource2/crud.rs new file mode 100644 index 000000000..e508dcb60 --- /dev/null +++ b/bin/core/src/resource2/crud.rs @@ -0,0 +1,233 @@ +use std::str::FromStr; + +use anyhow::{anyhow, Context}; +use komodo_client::entities::{ + komodo_timestamp, + permission::{Permission, PermissionLevel, UserTarget}, + resource::Resource, + to_komodo_name, + update::Update, + user::{system_user, User}, + Operation, +}; +use mungos::mongodb::bson::{oid::ObjectId, to_document, Document}; +use serde::Serialize; + +use crate::{ + helpers::update::{add_update, make_update}, + state::db_client, +}; + +use super::{query::ResourceQuery, resource_target, ResourceBase}; + +pub trait ResourceCrud: ResourceQuery { + type PartialConfig: Into + Serialize; + + // ======== + // CREATE + // ======== + + fn create_operation() -> Operation; + + fn user_can_create(user: &User) -> bool; + + async fn validate_create_config( + config: &mut Self::PartialConfig, + user: &User, + ) -> anyhow::Result<()>; + + async fn default_info() -> anyhow::Result { + Ok(Default::default()) + } + + async fn post_create( + created: &Resource, + update: &mut Update, + ) -> anyhow::Result<()>; + + // ======== + // UPDATE + // ======== + + fn update_operation() -> Operation; + + async fn validate_update_config( + id: &str, + config: &mut Self::PartialConfig, + user: &User, + ) -> anyhow::Result<()>; + + /// Should be overridden for enum configs, eg Alerter, Builder, ... + fn update_document( + _original: Resource, + config: Self::PartialConfig, + ) -> Result { + to_document(&config) + } + + /// Run any required task after resource updated in database but + /// before the request resolves. + async fn post_update( + updated: &Resource, + update: &mut Update, + ) -> anyhow::Result<()>; + + // ======== + // RENAME + // ======== + + fn rename_operation() -> Operation; + + // ======== + // DELETE + // ======== + + fn delete_operation() -> Operation; + + /// Clean up all links to this resource before deleting it. + async fn pre_delete( + resource: &Resource, + update: &mut Update, + ) -> anyhow::Result<()>; + + /// Run any required task after resource deleted from database but + /// before the request resolves. + async fn post_delete( + resource: &Resource, + update: &mut Update, + ) -> anyhow::Result<()>; +} + +// ======== +// CREATE +// ======== + +pub async fn create( + name: &str, + mut config: R::PartialConfig, + user: &User, +) -> anyhow::Result> { + if !R::user_can_create(user) { + return Err(anyhow!( + "User does not have permissions to create {}.", + R::resource_type() + )); + } + + if name.is_empty() { + return Err(anyhow!("Must provide non-empty name for resource.")); + } + + let name = to_komodo_name(name); + + if ObjectId::from_str(&name).is_ok() { + return Err(anyhow!("valid ObjectIds cannot be used as names.")); + } + + // Ensure an existing resource with same name doesn't already exist + // The database indexing also ensures this but doesn't give a good error message. + if super::query::list_full_for_user::( + Default::default(), + system_user(), + &[], + ) + .await + .context("Failed to list all resources for duplicate name check")? + .into_iter() + .any(|r| r.name == name) + { + return Err(anyhow!("Must provide unique name for resource.")); + } + + let start_ts = komodo_timestamp(); + + R::validate_create_config(&mut config, user).await?; + + let resource = Resource:: { + id: Default::default(), + name, + updated_at: start_ts, + description: Default::default(), + tags: Default::default(), + config: config.into(), + info: R::default_info().await?, + base_permission: PermissionLevel::None, + }; + + let resource_id = R::coll() + .insert_one(&resource) + .await + .with_context(|| { + format!("failed to add {} to db", R::resource_type()) + })? + .inserted_id + .as_object_id() + .context("inserted_id is not ObjectId")? + .to_string(); + + let resource = super::query::get::(&resource_id).await?; + + create_permission::(user, resource_id, PermissionLevel::Write) + .await; + + let mut update = make_update( + resource_target::(resource.id.clone()), + R::create_operation(), + user, + ); + update.start_ts = start_ts; + update.push_simple_log( + &format!("create {}", R::resource_type()), + format!( + "created {}\nid: {}\nname: {}", + R::resource_type(), + resource.id, + resource.name + ), + ); + update.push_simple_log( + "config", + serde_json::to_string_pretty(&resource.config) + .context("failed to serialize resource config to JSON")?, + ); + + R::post_create(&resource, &mut update).await?; + + update.finalize(); + add_update(update).await?; + + Ok(resource) +} + +#[instrument] +pub async fn create_permission( + user: &User, + id: String, + level: PermissionLevel, +) { + // No need to actually create permissions for admins + if user.admin { + return; + } + let target = super::resource_target::(id); + if let Err(e) = db_client() + .permissions + .insert_one(Permission { + id: Default::default(), + user_target: UserTarget::User(user.id.clone()), + resource_target: target.clone(), + level, + }) + .await + { + error!("failed to create permission for {target:?} | {e:#}"); + }; +} + +// ======== +// UPDATE +// ======== + +// ======== +// DELETE +// ======== diff --git a/bin/core/src/resource2/mod.rs b/bin/core/src/resource2/mod.rs new file mode 100644 index 000000000..8a67d0556 --- /dev/null +++ b/bin/core/src/resource2/mod.rs @@ -0,0 +1,102 @@ +use komodo_client::entities::{ + action::Action, build::Build, builder::Builder, + deployment::Deployment, procedure::Procedure, repo::Repo, + server::Server, server_template::ServerTemplate, stack::Stack, + sync::ResourceSync, ResourceTarget, ResourceTargetVariant, +}; + +pub mod crud; +pub mod query; + +pub trait ResourceBase { + fn resource_type() -> ResourceTargetVariant; +} + +pub fn resource_target( + id: String, +) -> ResourceTarget { + match R::resource_type() { + ResourceTargetVariant::System => ResourceTarget::System(id), + ResourceTargetVariant::Build => ResourceTarget::Build(id), + ResourceTargetVariant::Builder => ResourceTarget::Builder(id), + ResourceTargetVariant::Deployment => { + ResourceTarget::Deployment(id) + } + ResourceTargetVariant::Server => ResourceTarget::Server(id), + ResourceTargetVariant::Repo => ResourceTarget::Repo(id), + ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id), + ResourceTargetVariant::Procedure => ResourceTarget::Procedure(id), + ResourceTargetVariant::ServerTemplate => { + ResourceTarget::ServerTemplate(id) + } + ResourceTargetVariant::ResourceSync => { + ResourceTarget::ResourceSync(id) + } + ResourceTargetVariant::Stack => ResourceTarget::Stack(id), + ResourceTargetVariant::Action => ResourceTarget::Action(id), + } +} + +// ================= +// IMPLEMENTATIONS +// ================= + +impl ResourceBase for Server { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Server + } +} + +impl ResourceBase for Stack { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Stack + } +} + +impl ResourceBase for Deployment { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Deployment + } +} + +impl ResourceBase for Build { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Build + } +} + +impl ResourceBase for Repo { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Repo + } +} + +impl ResourceBase for Procedure { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Procedure + } +} + +impl ResourceBase for Action { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Action + } +} + +impl ResourceBase for Builder { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::Builder + } +} + +impl ResourceBase for ServerTemplate { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::ServerTemplate + } +} + +impl ResourceBase for ResourceSync { + fn resource_type() -> ResourceTargetVariant { + ResourceTargetVariant::ResourceSync + } +} diff --git a/bin/core/src/resource2/query.rs b/bin/core/src/resource2/query.rs new file mode 100644 index 000000000..036f5b039 --- /dev/null +++ b/bin/core/src/resource2/query.rs @@ -0,0 +1,1027 @@ +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; + +use anyhow::{anyhow, Context}; +use futures::future::{join_all, FutureExt}; +use komodo_client::{ + entities::{ + action::*, + build::*, + builder::*, + deployment::*, + permission::PermissionLevel, + procedure::*, + repo::*, + resource::{ + AddFilters, Resource, ResourceQuery as ResourceQuerySchema, + }, + server::*, + server_template::*, + stack::*, + sync::*, + tag::Tag, + user::User, + ResourceTargetVariant, + }, + parsers::parse_string_list, +}; +use mongo_indexed::Document; +use mungos::{ + find::find_collect, + mongodb::{ + bson::{doc, oid::ObjectId}, + options::FindOptions, + Collection, + }, +}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + config::core_config, + helpers::query::{ + get_user_user_groups, id_or_name_filter, user_target_query, + }, + state::{ + action_state_cache, action_states, build_state_cache, db_client, + deployment_status_cache, procedure_state_cache, repo_state_cache, + repo_status_cache, resource_sync_state_cache, + server_status_cache, stack_status_cache, + }, +}; + +use super::ResourceBase; + +pub trait ResourceQuery: ResourceBase { + type Config: Default + + Send + + Sync + + Serialize + + DeserializeOwned + + 'static; + type Info: Default + + Send + + Sync + + Serialize + + DeserializeOwned + + 'static; + type ListItem: Send; + type QuerySpecifics: AddFilters + Default + std::fmt::Debug; + + fn coll() -> &'static Collection>; + + async fn to_list_item( + resource: Resource, + ) -> Self::ListItem; +} + +#[instrument(level = "debug")] +pub async fn get( + id_or_name: &str, +) -> anyhow::Result> { + R::coll() + .find_one(id_or_name_filter(id_or_name)) + .await + .context("failed to query db for resource")? + .with_context(|| { + format!( + "did not find any {} matching {id_or_name}", + R::resource_type() + ) + }) +} + +#[instrument(level = "debug")] +pub async fn get_check_permissions( + id_or_name: &str, + user: &User, + permission_level: PermissionLevel, +) -> anyhow::Result> { + let resource = get::(id_or_name).await?; + if user.admin + // Allow if its just read or below, and transparent mode enabled + || (permission_level <= PermissionLevel::Read + && core_config().transparent_mode) + // Allow if resource has base permission level greater than or equal to required permission level + || resource.base_permission >= permission_level + { + return Ok(resource); + } + let permissions = + get_user_permission_on_resource::(user, &resource.id).await?; + if permissions >= permission_level { + Ok(resource) + } else { + Err(anyhow!( + "User does not have required permissions on this {}. Must have at least {permission_level} permissions", + R::resource_type() + )) + } +} + +#[instrument(level = "debug")] +pub async fn get_user_permission_on_resource( + user: &User, + resource_id: &str, +) -> anyhow::Result { + if user.admin { + return Ok(PermissionLevel::Write); + } + + let resource_type = R::resource_type(); + + // Start with base of Read or None + let mut base = if core_config().transparent_mode { + PermissionLevel::Read + } else { + PermissionLevel::None + }; + + // Add in the resource level global base permission + let resource_base = get::(resource_id).await?.base_permission; + if resource_base > base { + base = resource_base; + } + + // Overlay users base on resource variant + if let Some(level) = user.all.get(&resource_type).cloned() { + if level > base { + base = level; + } + } + if base == PermissionLevel::Write { + // No reason to keep going if already Write at this point. + return Ok(PermissionLevel::Write); + } + + // Overlay any user groups base on resource variant + let groups = get_user_user_groups(&user.id).await?; + for group in &groups { + if let Some(level) = group.all.get(&resource_type).cloned() { + if level > base { + base = level; + } + } + } + if base == PermissionLevel::Write { + // No reason to keep going if already Write at this point. + return Ok(PermissionLevel::Write); + } + + // Overlay any specific permissions + let permission = find_collect( + &db_client().permissions, + doc! { + + + "$or": user_target_query(&user.id, &groups)?, + "resource_target.type": resource_type.as_ref(), + "resource_target.id": resource_id + }, + None, + ) + .await + .context("failed to query db for permissions")? + .into_iter() + // get the max permission user has between personal / any user groups + .fold(base, |level, permission| { + if permission.level > level { + permission.level + } else { + level + } + }); + Ok(permission) +} + +// ====== +// LIST +// ====== + +/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access). +#[instrument(level = "debug")] +pub async fn get_resource_object_ids_for_user( + user: &User, +) -> anyhow::Result>> { + get_resource_ids_for_user::(user).await.map(|ids| { + ids.map(|ids| { + ids + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect() + }) + }) +} + +/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access). +#[instrument(level = "debug")] +pub async fn get_resource_ids_for_user( + user: &User, +) -> anyhow::Result>> { + // Check admin or transparent mode + if user.admin || core_config().transparent_mode { + return Ok(None); + } + + let resource_type = R::resource_type(); + + // Check user 'all' on variant + if let Some(level) = user.all.get(&resource_type).cloned() { + if level > PermissionLevel::None { + return Ok(None); + } + } + + // Check user groups 'all' on variant + let groups = get_user_user_groups(&user.id).await?; + for group in &groups { + if let Some(level) = group.all.get(&resource_type).cloned() { + if level > PermissionLevel::None { + return Ok(None); + } + } + } + + let (base, perms) = tokio::try_join!( + // Get any resources with non-none base permission, + find_collect( + R::coll(), + doc! { "base_permission": { "$exists": true, "$ne": "None" } }, + None, + ) + .map(|res| res.with_context(|| format!( + "failed to query {resource_type} on db" + ))), + // And any ids using the permissions table + find_collect( + &db_client().permissions, + doc! { + + + "$or": user_target_query(&user.id, &groups)?, + "resource_target.type": resource_type.as_ref(), + "level": { "$exists": true, "$ne": "None" } + }, + None, + ) + .map(|res| res.context("failed to query permissions on db")) + )?; + + // Add specific ids + let ids = perms + .into_iter() + .map(|p| p.resource_target.extract_variant_id().1.to_string()) + // Chain in the ones with non-None base permissions + .chain(base.into_iter().map(|res| res.id)) + // collect into hashset first to remove any duplicates + .collect::>(); + + Ok(Some(ids.into_iter().collect())) +} + +#[instrument(level = "debug")] +pub async fn list_for_user( + mut query: ResourceQuerySchema, + user: &User, + all_tags: &[Tag], +) -> anyhow::Result> { + validate_resource_query_tags(&mut query, all_tags)?; + let mut filters = Document::new(); + query.add_filters(&mut filters); + list_for_user_using_document::(filters, user).await +} + +#[instrument(level = "debug")] +pub async fn list_for_user_using_pattern( + pattern: &str, + query: ResourceQuerySchema, + user: &User, + all_tags: &[Tag], +) -> anyhow::Result> { + let list = list_full_for_user_using_pattern::( + pattern, query, user, all_tags, + ) + .await? + .into_iter() + .map(|resource| R::to_list_item(resource)); + Ok(join_all(list).await) +} + +#[instrument(level = "debug")] +pub async fn list_for_user_using_document( + filters: Document, + user: &User, +) -> anyhow::Result> { + let list = list_full_for_user_using_document::(filters, user) + .await? + .into_iter() + .map(|resource| R::to_list_item(resource)); + Ok(join_all(list).await) +} + +/// Lists full resource matching wildcard syntax, +/// or regex if wrapped with "\\" +/// +/// ## Example +/// ``` +/// let items = list_full_for_user_using_match_string::("foo-*", Default::default(), user, all_tags).await?; +/// let items = list_full_for_user_using_match_string::("\\^foo-.*$\\", Default::default(), user, all_tags).await?; +/// ``` +#[instrument(level = "debug")] +pub async fn list_full_for_user_using_pattern( + pattern: &str, + query: ResourceQuerySchema, + user: &User, + all_tags: &[Tag], +) -> anyhow::Result>> { + let resources = + list_full_for_user::(query, user, all_tags).await?; + + let patterns = parse_string_list(pattern); + let mut names = HashSet::::new(); + + for pattern in patterns { + if pattern.starts_with('\\') && pattern.ends_with('\\') { + let regex = regex::Regex::new(&pattern[1..(pattern.len() - 1)]) + .context("Regex matching string invalid")?; + for resource in &resources { + if regex.is_match(&resource.name) { + names.insert(resource.name.clone()); + } + } + } else { + let wildcard = wildcard::Wildcard::new(pattern.as_bytes()) + .context("Wildcard matching string invalid")?; + for resource in &resources { + if wildcard.is_match(resource.name.as_bytes()) { + names.insert(resource.name.clone()); + } + } + }; + } + + Ok( + resources + .into_iter() + .filter(|resource| names.contains(resource.name.as_str())) + .collect(), + ) +} + +#[instrument(level = "debug")] +pub async fn list_full_for_user( + mut query: ResourceQuerySchema, + user: &User, + all_tags: &[Tag], +) -> anyhow::Result>> { + validate_resource_query_tags(&mut query, all_tags)?; + let mut filters = Document::new(); + query.add_filters(&mut filters); + list_full_for_user_using_document::(filters, user).await +} + +#[instrument(level = "debug")] +pub async fn list_full_for_user_using_document( + mut filters: Document, + user: &User, +) -> anyhow::Result>> { + if let Some(ids) = + get_resource_object_ids_for_user::(user).await? + { + filters.insert("_id", doc! { "$in": ids }); + } + find_collect( + R::coll(), + filters, + FindOptions::builder().sort(doc! { "name": 1 }).build(), + ) + .await + .with_context(|| { + format!("failed to pull {}s from mongo", R::resource_type()) + }) +} + +pub type IdResourceMap = HashMap< + String, + Resource<::Config, ::Info>, +>; + +#[instrument(level = "debug")] +pub async fn get_id_to_resource_map( + id_to_tags: &HashMap, + match_tags: &[String], +) -> anyhow::Result> { + let res = find_collect(R::coll(), None, None) + .await + .with_context(|| { + format!("failed to pull {}s from mongo", R::resource_type()) + })? + .into_iter() + .filter(|resource| { + if match_tags.is_empty() { + return true; + } + for tag in match_tags.iter() { + for resource_tag in &resource.tags { + match ObjectId::from_str(resource_tag) { + Ok(_) => match id_to_tags + .get(resource_tag) + .map(|tag| tag.name.as_str()) + { + Some(name) => { + if tag != name { + return false; + } + } + None => return false, + }, + Err(_) => { + if resource_tag != tag { + return false; + } + } + } + } + } + true + }) + .map(|r| (r.id.clone(), r)) + .collect(); + Ok(res) +} + +#[instrument(level = "debug")] +pub fn validate_resource_query_tags( + query: &mut ResourceQuerySchema, + all_tags: &[Tag], +) -> anyhow::Result<()> { + query.tags = query + .tags + .iter() + .map(|tag| { + all_tags + .iter() + .find(|t| t.name == *tag || t.id == *tag) + .map(|tag| tag.id.clone()) + .with_context(|| { + format!("No tag found matching name or id: {}", tag) + }) + }) + .collect::>>()?; + Ok(()) +} + +// ================= +// IMPLEMENTATIONS +// ================= + +impl ResourceQuery for Server { + type Config = ServerConfig; + type Info = (); + type ListItem = ServerListItem; + type QuerySpecifics = ServerQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().servers + } + + async fn to_list_item( + server: Resource, + ) -> Self::ListItem { + let status = server_status_cache().get(&server.id).await; + ServerListItem { + name: server.name, + id: server.id, + tags: server.tags, + resource_type: ResourceTargetVariant::Server, + info: ServerListItemInfo { + state: status.map(|s| s.state).unwrap_or_default(), + region: server.config.region, + address: server.config.address, + send_unreachable_alerts: server + .config + .send_unreachable_alerts, + send_cpu_alerts: server.config.send_cpu_alerts, + send_mem_alerts: server.config.send_mem_alerts, + send_disk_alerts: server.config.send_disk_alerts, + }, + } + } +} + +impl ResourceQuery for Stack { + type Config = StackConfig; + type Info = StackInfo; + type ListItem = StackListItem; + type QuerySpecifics = StackQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().stacks + } + + async fn to_list_item( + stack: Resource, + ) -> Self::ListItem { + let status = stack_status_cache().get(&stack.id).await; + let state = + status.as_ref().map(|s| s.curr.state).unwrap_or_default(); + let project_name = stack.project_name(false); + let services = status + .as_ref() + .map(|s| { + s.curr + .services + .iter() + .map(|service| StackServiceWithUpdate { + service: service.service.clone(), + image: service.image.clone(), + update_available: service.update_available, + }) + .collect::>() + }) + .unwrap_or_default(); + + // This is only true if it is KNOWN to be true. so other cases are false. + let (project_missing, status) = + if stack.config.server_id.is_empty() + || matches!(state, StackState::Down | StackState::Unknown) + { + (false, None) + } else if let Some(status) = server_status_cache() + .get(&stack.config.server_id) + .await + .as_ref() + { + if let Some(projects) = &status.projects { + if let Some(project) = projects + .iter() + .find(|project| project.name == project_name) + { + (false, project.status.clone()) + } else { + // The project doesn't exist + (true, None) + } + } else { + (false, None) + } + } else { + (false, None) + }; + + StackListItem { + id: stack.id, + name: stack.name, + tags: stack.tags, + resource_type: ResourceTargetVariant::Stack, + info: StackListItemInfo { + state, + status, + services, + project_missing, + file_contents: !stack.config.file_contents.is_empty(), + server_id: stack.config.server_id, + missing_files: stack.info.missing_files, + files_on_host: stack.config.files_on_host, + git_provider: stack.config.git_provider, + repo: stack.config.repo, + branch: stack.config.branch, + latest_hash: stack.info.latest_hash, + deployed_hash: stack.info.deployed_hash, + }, + } + } +} + +impl ResourceQuery for Deployment { + type Config = DeploymentConfig; + type Info = (); + type ListItem = DeploymentListItem; + type QuerySpecifics = DeploymentQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().deployments + } + + async fn to_list_item( + deployment: Resource, + ) -> Self::ListItem { + let status = deployment_status_cache().get(&deployment.id).await; + let (build_image, build_id) = match deployment.config.image { + DeploymentImage::Build { build_id, version } => { + let (build_name, build_id, build_version) = + super::query::get::(&build_id) + .await + .map(|b| (b.name, b.id, b.config.version)) + .unwrap_or(( + String::from("unknown"), + String::new(), + Default::default(), + )); + let version = if version.is_none() { + build_version.to_string() + } else { + version.to_string() + }; + (format!("{build_name}:{version}"), Some(build_id)) + } + DeploymentImage::Image { image } => (image, None), + }; + let (image, update_available) = status + .as_ref() + .and_then(|s| { + s.curr.container.as_ref().map(|c| { + ( + c.image + .clone() + .unwrap_or_else(|| String::from("Unknown")), + s.curr.update_available, + ) + }) + }) + .unwrap_or((build_image, false)); + DeploymentListItem { + name: deployment.name, + id: deployment.id, + tags: deployment.tags, + resource_type: ResourceTargetVariant::Deployment, + info: DeploymentListItemInfo { + state: status + .as_ref() + .map(|s| s.curr.state) + .unwrap_or_default(), + status: status.as_ref().and_then(|s| { + s.curr.container.as_ref().and_then(|c| c.status.to_owned()) + }), + image, + update_available, + server_id: deployment.config.server_id, + build_id, + }, + } + } +} + +impl ResourceQuery for Build { + type Config = BuildConfig; + type Info = BuildInfo; + type ListItem = BuildListItem; + type QuerySpecifics = BuildQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().builds + } + + async fn to_list_item( + build: Resource, + ) -> Self::ListItem { + let state = if action_states() + .build + .get(&build.id) + .await + .map(|s| s.get().map(|s| s.building)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + BuildState::Building + } else { + build_state_cache().get(&build.id).await.unwrap_or_default() + }; + BuildListItem { + name: build.name, + id: build.id, + tags: build.tags, + resource_type: ResourceTargetVariant::Build, + info: BuildListItemInfo { + last_built_at: build.info.last_built_at, + version: build.config.version, + builder_id: build.config.builder_id, + git_provider: build.config.git_provider, + image_registry_domain: build.config.image_registry.domain, + repo: build.config.repo, + branch: build.config.branch, + built_hash: build.info.built_hash, + latest_hash: build.info.latest_hash, + state, + }, + } + } +} + +impl ResourceQuery for Repo { + type Config = RepoConfig; + type Info = RepoInfo; + type ListItem = RepoListItem; + type QuerySpecifics = RepoQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().repos + } + + async fn to_list_item( + repo: Resource, + ) -> Self::ListItem { + let state = get_repo_state(&repo.id).await; + let status = + repo_status_cache().get(&repo.id).await.unwrap_or_default(); + RepoListItem { + name: repo.name, + id: repo.id, + tags: repo.tags, + resource_type: ResourceTargetVariant::Repo, + info: RepoListItemInfo { + server_id: repo.config.server_id, + builder_id: repo.config.builder_id, + last_pulled_at: repo.info.last_pulled_at, + last_built_at: repo.info.last_built_at, + git_provider: repo.config.git_provider, + repo: repo.config.repo, + branch: repo.config.branch, + state, + cloned_hash: status.latest_hash.clone(), + cloned_message: status.latest_message.clone(), + latest_hash: repo.info.latest_hash, + built_hash: repo.info.built_hash, + }, + } + } +} + +async fn get_repo_state(id: &String) -> RepoState { + if let Some(state) = action_states() + .repo + .get(id) + .await + .and_then(|s| { + s.get() + .map(|s| { + if s.cloning { + Some(RepoState::Cloning) + } else if s.pulling { + Some(RepoState::Pulling) + } else if s.building { + Some(RepoState::Building) + } else { + None + } + }) + .ok() + }) + .flatten() + { + return state; + } + repo_state_cache().get(id).await.unwrap_or_default() +} + +impl ResourceQuery for Procedure { + type Config = ProcedureConfig; + type Info = (); + type ListItem = ProcedureListItem; + type QuerySpecifics = ProcedureQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().procedures + } + + async fn to_list_item( + procedure: Resource, + ) -> Self::ListItem { + let state = if action_states() + .procedure + .get(&procedure.id) + .await + .map(|s| s.get().map(|s| s.running)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + ProcedureState::Running + } else { + procedure_state_cache() + .get(&procedure.id) + .await + .unwrap_or_default() + }; + ProcedureListItem { + name: procedure.name, + id: procedure.id, + tags: procedure.tags, + resource_type: ResourceTargetVariant::Procedure, + info: ProcedureListItemInfo { + stages: procedure.config.stages.len() as i64, + state, + }, + } + } +} + +impl ResourceQuery for Action { + type Config = ActionConfig; + type Info = ActionInfo; + type ListItem = ActionListItem; + type QuerySpecifics = ActionQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().actions + } + + async fn to_list_item( + action: Resource, + ) -> Self::ListItem { + let state = if action_states() + .action + .get(&action.id) + .await + .map(|s| s.get().map(|s| s.running)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + ActionState::Running + } else { + action_state_cache() + .get(&action.id) + .await + .unwrap_or_default() + }; + ActionListItem { + name: action.name, + id: action.id, + tags: action.tags, + resource_type: ResourceTargetVariant::Action, + info: ActionListItemInfo { + state, + last_run_at: action.info.last_run_at, + }, + } + } +} + +impl ResourceQuery for Builder { + type Config = BuilderConfig; + type Info = (); + type ListItem = BuilderListItem; + type QuerySpecifics = BuilderQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().builders + } + + async fn to_list_item( + builder: Resource, + ) -> Self::ListItem { + let (builder_type, instance_type) = match builder.config { + BuilderConfig::Url(_) => { + (BuilderConfigVariant::Url.to_string(), None) + } + BuilderConfig::Server(config) => ( + BuilderConfigVariant::Server.to_string(), + Some(config.server_id), + ), + BuilderConfig::Aws(config) => ( + BuilderConfigVariant::Aws.to_string(), + Some(config.instance_type), + ), + }; + BuilderListItem { + name: builder.name, + id: builder.id, + tags: builder.tags, + resource_type: ResourceTargetVariant::Builder, + info: BuilderListItemInfo { + builder_type, + instance_type, + }, + } + } +} + +impl ResourceQuery for ServerTemplate { + type Config = ServerTemplateConfig; + type Info = (); + type ListItem = ServerTemplateListItem; + type QuerySpecifics = ServerTemplateQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().server_templates + } + + async fn to_list_item( + server_template: Resource, + ) -> Self::ListItem { + let (template_type, instance_type) = match server_template.config + { + ServerTemplateConfig::Aws(config) => ( + ServerTemplateConfigVariant::Aws.to_string(), + Some(config.instance_type), + ), + ServerTemplateConfig::Hetzner(config) => ( + ServerTemplateConfigVariant::Hetzner.to_string(), + Some(config.server_type.as_ref().to_string()), + ), + }; + ServerTemplateListItem { + name: server_template.name, + id: server_template.id, + tags: server_template.tags, + resource_type: ResourceTargetVariant::ServerTemplate, + info: ServerTemplateListItemInfo { + provider: template_type.to_string(), + instance_type, + }, + } + } +} + +async fn get_resource_sync_state( + id: &String, + data: &ResourceSyncInfo, +) -> ResourceSyncState { + if let Some(state) = action_states() + .resource_sync + .get(id) + .await + .and_then(|s| { + s.get() + .map(|s| { + if s.syncing { + Some(ResourceSyncState::Syncing) + } else { + None + } + }) + .ok() + }) + .flatten() + { + return state; + } + if data.pending_error.is_some() { + return ResourceSyncState::Failed; + } + if !data.resource_updates.is_empty() + || !data.variable_updates.is_empty() + || !data.user_group_updates.is_empty() + || data.pending_deploy.to_deploy > 0 + { + return ResourceSyncState::Pending; + } + resource_sync_state_cache() + .get(id) + .await + .unwrap_or_default() +} + +impl ResourceQuery for ResourceSync { + type Config = ResourceSyncConfig; + type Info = ResourceSyncInfo; + type ListItem = ResourceSyncListItem; + type QuerySpecifics = ResourceSyncQuerySpecifics; + + fn coll() -> &'static Collection> + { + &db_client().resource_syncs + } + + async fn to_list_item( + resource_sync: Resource, + ) -> Self::ListItem { + let state = + get_resource_sync_state(&resource_sync.id, &resource_sync.info) + .await; + ResourceSyncListItem { + id: resource_sync.id, + name: resource_sync.name, + tags: resource_sync.tags, + resource_type: ResourceTargetVariant::ResourceSync, + info: ResourceSyncListItemInfo { + file_contents: !resource_sync.config.file_contents.is_empty(), + files_on_host: resource_sync.config.files_on_host, + managed: resource_sync.config.managed, + git_provider: resource_sync.config.git_provider, + repo: resource_sync.config.repo, + branch: resource_sync.config.branch, + last_sync_ts: resource_sync.info.last_sync_ts, + last_sync_hash: resource_sync.info.last_sync_hash, + last_sync_message: resource_sync.info.last_sync_message, + resource_path: resource_sync.config.resource_path, + state, + }, + } + } +} diff --git a/bin/core/src/state.rs b/bin/core/src/state.rs index df9eefd19..dfd8898d0 100644 --- a/bin/core/src/state.rs +++ b/bin/core/src/state.rs @@ -29,8 +29,6 @@ use crate::{ }, }; -pub struct State; - static DB_CLIENT: OnceLock = OnceLock::new(); pub fn db_client() -> &'static DbClient { diff --git a/bin/core/src/sync/deploy.rs b/bin/core/src/sync/deploy.rs index 29fbf5422..bbe303446 100644 --- a/bin/core/src/sync/deploy.rs +++ b/bin/core/src/sync/deploy.rs @@ -24,9 +24,12 @@ use komodo_client::{ use resolver_api::Resolve; use crate::{ - api::execute::ExecuteRequest, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + read::ReadArgs, + }, helpers::update::init_execution_update, - state::{deployment_status_cache, stack_status_cache, State}, + state::{deployment_status_cache, stack_status_cache}, }; use super::{AllResourcesById, ResourceSyncTrait}; @@ -92,7 +95,12 @@ pub async fn deploy_from_cache( let ExecuteRequest::Deploy(req) = req else { unreachable!() }; - State.resolve(req, (user.to_owned(), update)).await + req + .resolve(&ExecuteArgs { + user: user.to_owned(), + update, + }) + .await } ResourceTarget::Stack(name) => { let req = ExecuteRequest::DeployStack(DeployStack { @@ -105,7 +113,12 @@ pub async fn deploy_from_cache( let ExecuteRequest::DeployStack(req) = req else { unreachable!() }; - State.resolve(req, (user.to_owned(), update)).await + req + .resolve(&ExecuteArgs { + user: user.to_owned(), + update, + }) + .await } _ => unreachable!(), } @@ -124,10 +137,11 @@ pub async fn deploy_from_cache( if let Err(e) = res { has_error = true; log.push_str(&format!( - "\n{}: failed to deploy {resource} '{}' in round {} | {e:#}", + "\n{}: failed to deploy {resource} '{}' in round {} | {:#}", colored("ERROR", Color::Red), bold(name), - bold(round) + bold(round), + e.error )); } else { log.push_str(&format!( @@ -426,19 +440,18 @@ fn build_cache_for_deployment<'a>( // Build version is the same, still need to check 'after' Some(_) => {} None => { - let Some(version) = State - .resolve( - ListBuildVersions { - build: build_id.to_string(), - limit: Some(1), - ..Default::default() - }, - sync_user().to_owned(), - ) - .await - .context("failed to get build versions")? - .pop() - else { + let Some(version) = (ListBuildVersions { + build: build_id.to_string(), + limit: Some(1), + ..Default::default() + }) + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await + .map_err(|e| e.error) + .context("failed to get build versions")? + .pop() else { // The build has never been built. // Skip deploy regardless of 'after' (it can't be deployed) // Not sure how this would be reached on Running deployment... diff --git a/bin/core/src/sync/execute.rs b/bin/core/src/sync/execute.rs index 8ab030357..78a1bc898 100644 --- a/bin/core/src/sync/execute.rs +++ b/bin/core/src/sync/execute.rs @@ -13,7 +13,7 @@ use mungos::find::find_collect; use partial_derive2::MaybeNone; use resolver_api::Resolve; -use crate::state::State; +use crate::api::write::WriteArgs; use super::{ AllResourcesById, ResourceSyncTrait, ToCreate, ToDelete, ToUpdate, @@ -256,8 +256,13 @@ pub trait ExecuteResourceSync: ResourceSyncTrait { } for resource in to_delete { - if let Err(e) = - crate::resource::delete::(&resource, sync_user()).await + if let Err(e) = crate::resource::delete::( + &resource, + &WriteArgs { + user: sync_user().to_owned(), + }, + ) + .await { has_error = true; log.push_str(&format!( @@ -294,22 +299,22 @@ pub async fn run_update_tags( has_error: &mut bool, ) { // Update tags - if let Err(e) = State - .resolve( - UpdateTagsOnResource { - target: Resource::resource_target(id), - tags, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateTagsOnResource { + target: Resource::resource_target(id), + tags, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to update tags on {} '{}' | {e:#}", + "\n{}: failed to update tags on {} '{}' | {:#}", colored("ERROR", Color::Red), Resource::resource_type(), bold(name), + e.error )) } else { log.push_str(&format!( @@ -329,22 +334,22 @@ pub async fn run_update_description( log: &mut String, has_error: &mut bool, ) { - if let Err(e) = State - .resolve( - UpdateDescription { - target: Resource::resource_target(id.clone()), - description, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateDescription { + target: Resource::resource_target(id.clone()), + description, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to update description on {} '{}' | {e:#}", + "\n{}: failed to update description on {} '{}' | {:#}", colored("ERROR", Color::Red), Resource::resource_type(), bold(name), + e.error )) } else { log.push_str(&format!( diff --git a/bin/core/src/sync/resources.rs b/bin/core/src/sync/resources.rs index aa69c9731..cef792512 100644 --- a/bin/core/src/sync/resources.rs +++ b/bin/core/src/sync/resources.rs @@ -24,6 +24,7 @@ use komodo_client::{ use partial_derive2::{MaybeNone, PartialDiff}; use crate::{ + api::write::WriteArgs, resource::KomodoResource, sync::{ execute::{run_update_description, run_update_tags}, @@ -726,8 +727,13 @@ impl ExecuteResourceSync for Procedure { format!("running updates on {}s", Self::resource_type()); for name in to_delete { - if let Err(e) = - crate::resource::delete::(&name, sync_user()).await + if let Err(e) = crate::resource::delete::( + &name, + &WriteArgs { + user: sync_user().to_owned(), + }, + ) + .await { has_error = true; log.push_str(&format!( diff --git a/bin/core/src/sync/user_groups.rs b/bin/core/src/sync/user_groups.rs index 7a00bd82a..6ca7a65b5 100644 --- a/bin/core/src/sync/user_groups.rs +++ b/bin/core/src/sync/user_groups.rs @@ -24,7 +24,10 @@ use mungos::find::find_collect; use regex::Regex; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{ + api::{read::ReadArgs, write::WriteArgs}, + state::db_client, +}; use super::{toml::TOML_PRETTY_OPTIONS, AllResourcesById}; @@ -211,105 +214,105 @@ pub async fn get_updates_for_execution( }) .collect::>(); - let mut original_permissions = State - .resolve( - ListUserTargetPermissions { - user_target: UserTarget::UserGroup(original.id), - }, - sync_user().to_owned(), - ) - .await - .context("failed to query for existing UserGroup permissions")? - .into_iter() - .filter(|p| p.level > PermissionLevel::None) - .map(|mut p| { - // replace the ids with names - match &mut p.resource_target { - ResourceTarget::System(_) => {} - ResourceTarget::Build(id) => { - *id = all_resources - .builds - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Builder(id) => { - *id = all_resources - .builders - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Deployment(id) => { - *id = all_resources - .deployments - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Server(id) => { - *id = all_resources - .servers - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Repo(id) => { - *id = all_resources - .repos - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Alerter(id) => { - *id = all_resources - .alerters - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Procedure(id) => { - *id = all_resources - .procedures - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Action(id) => { - *id = all_resources - .actions - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ServerTemplate(id) => { - *id = all_resources - .templates - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ResourceSync(id) => { - *id = all_resources - .syncs - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Stack(id) => { - *id = all_resources - .stacks - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } + let mut original_permissions = (ListUserTargetPermissions { + user_target: UserTarget::UserGroup(original.id), + }) + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await + .map_err(|e| e.error) + .context("failed to query for existing UserGroup permissions")? + .into_iter() + .filter(|p| p.level > PermissionLevel::None) + .map(|mut p| { + // replace the ids with names + match &mut p.resource_target { + ResourceTarget::System(_) => {} + ResourceTarget::Build(id) => { + *id = all_resources + .builds + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() } - PermissionToml { - target: p.resource_target, - level: p.level, + ResourceTarget::Builder(id) => { + *id = all_resources + .builders + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() } - }) - .collect::>(); + ResourceTarget::Deployment(id) => { + *id = all_resources + .deployments + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Server(id) => { + *id = all_resources + .servers + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Repo(id) => { + *id = all_resources + .repos + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Alerter(id) => { + *id = all_resources + .alerters + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Procedure(id) => { + *id = all_resources + .procedures + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Action(id) => { + *id = all_resources + .actions + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ServerTemplate(id) => { + *id = all_resources + .templates + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ResourceSync(id) => { + *id = all_resources + .syncs + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Stack(id) => { + *id = all_resources + .stacks + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + } + PermissionToml { + target: p.resource_target, + level: p.level, + } + }) + .collect::>(); original_users.sort(); user_group.users.sort(); @@ -404,20 +407,20 @@ pub async fn run_updates( // Create the non-existant user groups for user_group in to_create { // Create the user group - if let Err(e) = State - .resolve( - CreateUserGroup { - name: user_group.name.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (CreateUserGroup { + name: user_group.name.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to create user group '{}' | {e:#}", + "\n{}: failed to create user group '{}' | {:#}", colored("ERROR", Color::Red), - bold(&user_group.name) + bold(&user_group.name), + e.error )); continue; } else { @@ -489,18 +492,18 @@ pub async fn run_updates( } for user_group in to_delete { - if let Err(e) = State - .resolve( - DeleteUserGroup { id: user_group.id }, - sync_user().to_owned(), - ) + if let Err(e) = (DeleteUserGroup { id: user_group.id }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) .await { has_error = true; log.push_str(&format!( - "\n{}: failed to delete user group '{}' | {e:#}", + "\n{}: failed to delete user group '{}' | {:#}", colored("ERROR", Color::Red), - bold(&user_group.name) + bold(&user_group.name), + e.error )) } else { log.push_str(&format!( @@ -526,21 +529,21 @@ async fn set_users( log: &mut String, has_error: &mut bool, ) { - if let Err(e) = State - .resolve( - SetUsersInUserGroup { - user_group: user_group.clone(), - users, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (SetUsersInUserGroup { + user_group: user_group.clone(), + users, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set users in group {} | {e:#}", + "\n{}: failed to set users in group {} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -559,22 +562,22 @@ async fn run_update_all( has_error: &mut bool, ) { for (resource_type, permission) in all_diff { - if let Err(e) = State - .resolve( - UpdatePermissionOnResourceType { - user_target: UserTarget::UserGroup(user_group.clone()), - resource_type, - permission, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdatePermissionOnResourceType { + user_target: UserTarget::UserGroup(user_group.clone()), + resource_type, + permission, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set base permissions on {resource_type} in group {} | {e:#}", + "\n{}: failed to set base permissions on {resource_type} in group {} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -594,22 +597,22 @@ async fn run_update_permissions( has_error: &mut bool, ) { for PermissionToml { target, level } in permissions { - if let Err(e) = State - .resolve( - UpdatePermissionOnTarget { - user_target: UserTarget::UserGroup(user_group.clone()), - resource_target: target.clone(), - permission: level, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdatePermissionOnTarget { + user_target: UserTarget::UserGroup(user_group.clone()), + resource_target: target.clone(), + permission: level, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set permission in group {} | target: {target:?} | {e:#}", + "\n{}: failed to set permission in group {} | target: {target:?} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -830,105 +833,105 @@ pub async fn convert_user_groups( for user_group in user_groups { // this method is admin only, but we already know user can see user group if above does not return Err - let mut permissions = State - .resolve( - ListUserTargetPermissions { - user_target: UserTarget::UserGroup(user_group.id.clone()), - }, - User { - admin: true, - ..Default::default() - }, - ) - .await? - .into_iter() - .map(|mut permission| { - match &mut permission.resource_target { - ResourceTarget::Build(id) => { - *id = all - .builds - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Builder(id) => { - *id = all - .builders - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Deployment(id) => { - *id = all - .deployments - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Server(id) => { - *id = all - .servers - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Repo(id) => { - *id = all - .repos - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Alerter(id) => { - *id = all - .alerters - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Procedure(id) => { - *id = all - .procedures - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Action(id) => { - *id = all - .actions - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ServerTemplate(id) => { - *id = all - .templates - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ResourceSync(id) => { - *id = all - .syncs - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Stack(id) => { - *id = all - .stacks - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::System(_) => {} + let mut permissions = (ListUserTargetPermissions { + user_target: UserTarget::UserGroup(user_group.id.clone()), + }) + .resolve(&ReadArgs { + user: User { + admin: true, + ..Default::default() + }, + }) + .await + .map_err(|e| e.error)? + .into_iter() + .map(|mut permission| { + match &mut permission.resource_target { + ResourceTarget::Build(id) => { + *id = all + .builds + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() } - PermissionToml { - target: permission.resource_target, - level: permission.level, + ResourceTarget::Builder(id) => { + *id = all + .builders + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() } - }) - .collect::>(); + ResourceTarget::Deployment(id) => { + *id = all + .deployments + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Server(id) => { + *id = all + .servers + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Repo(id) => { + *id = all + .repos + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Alerter(id) => { + *id = all + .alerters + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Procedure(id) => { + *id = all + .procedures + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Action(id) => { + *id = all + .actions + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ServerTemplate(id) => { + *id = all + .templates + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ResourceSync(id) => { + *id = all + .syncs + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Stack(id) => { + *id = all + .stacks + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::System(_) => {} + } + PermissionToml { + target: permission.resource_target, + level: permission.level, + } + }) + .collect::>(); let mut users = user_group .users .into_iter() diff --git a/bin/core/src/sync/variables.rs b/bin/core/src/sync/variables.rs index a251808f2..b447dc49d 100644 --- a/bin/core/src/sync/variables.rs +++ b/bin/core/src/sync/variables.rs @@ -3,10 +3,7 @@ use std::collections::HashMap; use anyhow::Context; use formatting::{bold, colored, muted, Color}; use komodo_client::{ - api::write::{ - CreateVariable, DeleteVariable, UpdateVariableDescription, - UpdateVariableIsSecret, UpdateVariableValue, - }, + api::write::*, entities::{ sync::DiffData, update::Log, user::sync_user, variable::Variable, }, @@ -14,7 +11,7 @@ use komodo_client::{ use mungos::find::find_collect; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{api::write::WriteArgs, state::db_client}; use super::toml::TOML_PRETTY_OPTIONS; @@ -154,23 +151,23 @@ pub async fn run_updates( let mut log = String::from("running updates on Variables"); for variable in to_create { - if let Err(e) = State - .resolve( - CreateVariable { - name: variable.name.clone(), - value: variable.value, - description: variable.description, - is_secret: variable.is_secret, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (CreateVariable { + name: variable.name.clone(), + value: variable.value, + description: variable.description, + is_secret: variable.is_secret, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to create variable '{}' | {e:#}", + "\n{}: failed to create variable '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )); } else { log.push_str(&format!( @@ -190,21 +187,21 @@ pub async fn run_updates( } in to_update { if update_value { - if let Err(e) = State - .resolve( - UpdateVariableValue { - name: variable.name.clone(), - value: variable.value, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableValue { + name: variable.name.clone(), + value: variable.value, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable value for '{}' | {e:#}", + "\n{}: failed to update variable value for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )) } else { log.push_str(&format!( @@ -216,21 +213,21 @@ pub async fn run_updates( }; } if update_description { - if let Err(e) = State - .resolve( - UpdateVariableDescription { - name: variable.name.clone(), - description: variable.description, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableDescription { + name: variable.name.clone(), + description: variable.description, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable description for '{}' | {e:#}", + "\n{}: failed to update variable description for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )) } else { log.push_str(&format!( @@ -242,21 +239,21 @@ pub async fn run_updates( }; } if update_is_secret { - if let Err(e) = State - .resolve( - UpdateVariableIsSecret { - name: variable.name.clone(), - is_secret: variable.is_secret, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableIsSecret { + name: variable.name.clone(), + is_secret: variable.is_secret, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable is secret for '{}' | {e:#}", + "\n{}: failed to update variable is secret for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error, )) } else { log.push_str(&format!( @@ -270,20 +267,20 @@ pub async fn run_updates( } for variable in to_delete { - if let Err(e) = State - .resolve( - DeleteVariable { - name: variable.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (DeleteVariable { + name: variable.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to delete variable '{}' | {e:#}", + "\n{}: failed to delete variable '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable) + bold(&variable), + e.error )) } else { log.push_str(&format!( diff --git a/bin/periphery/Cargo.toml b/bin/periphery/Cargo.toml index d7021f304..7ba93d19a 100644 --- a/bin/periphery/Cargo.toml +++ b/bin/periphery/Cargo.toml @@ -19,6 +19,7 @@ komodo_client.workspace = true periphery_client.workspace = true environment_file.workspace = true formatting.workspace = true +response.workspace = true command.workspace = true logger.workspace = true cache.workspace = true @@ -33,7 +34,6 @@ run_command.workspace = true svi.workspace = true # external axum-server.workspace = true -axum-extra.workspace = true serde_json.workspace = true futures.workspace = true tracing.workspace = true diff --git a/bin/periphery/src/api/build.rs b/bin/periphery/src/api/build.rs index 57852525c..504585dfd 100644 --- a/bin/periphery/src/api/build.rs +++ b/bin/periphery/src/api/build.rs @@ -20,21 +20,20 @@ use crate::{ config::periphery_config, docker::docker_login, helpers::{parse_extra_args, parse_labels}, - State, }; -impl Resolve for State { - #[instrument(name = "Build", skip_all)] +impl Resolve for build::Build { + #[instrument(name = "Build", skip_all, fields(build = self.build.name.to_string()))] async fn resolve( - &self, - build::Build { + self, + _: &super::Args, + ) -> serror::Result> { + let build::Build { build, registry_token, additional_tags, replacers: core_replacers, - }: build::Build, - _: (), - ) -> anyhow::Result> { + } = self; let Build { name, config: @@ -247,13 +246,9 @@ fn cleanup_secret_env_vars(secret_args: &[EnvironmentVar]) { // -impl Resolve for State { - #[instrument(name = "PruneBuilders", skip(self))] - async fn resolve( - &self, - _: PruneBuilders, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneBuilders { + #[instrument(name = "PruneBuilders", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker builder prune -a -f"); Ok( run_komodo_command("prune builders", None, command, false) @@ -264,13 +259,9 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PruneBuildx", skip(self))] - async fn resolve( - &self, - _: PruneBuildx, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneBuildx { + #[instrument(name = "PruneBuildx", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker buildx prune -a -f"); Ok(run_komodo_command("prune buildx", None, command, false).await) } diff --git a/bin/periphery/src/api/compose.rs b/bin/periphery/src/api/compose.rs index 6ea86a9ee..a5a28a209 100644 --- a/bin/periphery/src/api/compose.rs +++ b/bin/periphery/src/api/compose.rs @@ -17,16 +17,14 @@ use crate::{ config::periphery_config, docker::docker_login, helpers::{log_grep, pull_or_clone_stack}, - State, }; -impl Resolve for State { - #[instrument(name = "ComposeInfo", level = "debug", skip(self))] +impl Resolve for ListComposeProjects { + #[instrument(name = "ComposeInfo", level = "debug", skip_all)] async fn resolve( - &self, - ListComposeProjects {}: ListComposeProjects, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let docker_compose = docker_compose(); let res = run_komodo_command( "list projects", @@ -37,9 +35,13 @@ impl Resolve for State { .await; if !res.success { - return Err(anyhow!("{}", res.combined()).context(format!( + return Err( + anyhow!("{}", res.combined()) + .context(format!( "failed to list compose projects using {docker_compose} ls" - ))); + )) + .into(), + ); } let res = @@ -80,22 +82,15 @@ pub struct DockerComposeLsItem { // -impl Resolve for State { - #[instrument( - name = "GetComposeServiceLog", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetComposeServiceLog { +impl Resolve for GetComposeServiceLog { + #[instrument(name = "GetComposeServiceLog", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetComposeServiceLog { project, service, tail, timestamps, - }: GetComposeServiceLog, - _: (), - ) -> anyhow::Result { + } = self; let docker_compose = docker_compose(); let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); @@ -108,24 +103,17 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "GetComposeServiceLogSearch", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetComposeServiceLogSearch { +impl Resolve for GetComposeServiceLogSearch { + #[instrument(name = "GetComposeServiceLogSearch", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetComposeServiceLogSearch { project, service, terms, combinator, invert, timestamps, - }: GetComposeServiceLogSearch, - _: (), - ) -> anyhow::Result { + } = self; let docker_compose = docker_compose(); let grep = log_grep(&terms, combinator, invert); let timestamps = @@ -140,21 +128,17 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument( - name = "GetComposeContentsOnHost", - level = "debug", - skip(self) - )] +impl Resolve for GetComposeContentsOnHost { + #[instrument(name = "GetComposeContentsOnHost", level = "debug")] async fn resolve( - &self, - GetComposeContentsOnHost { + self, + _: &super::Args, + ) -> serror::Result { + let GetComposeContentsOnHost { name, run_directory, file_paths, - }: GetComposeContentsOnHost, - _: (), - ) -> anyhow::Result { + } = self; let root = periphery_config().stack_dir.join(to_komodo_name(&name)); let run_directory = @@ -196,18 +180,15 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "WriteComposeContentsToHost", skip(self))] - async fn resolve( - &self, - WriteComposeContentsToHost { +impl Resolve for WriteComposeContentsToHost { + #[instrument(name = "WriteComposeContentsToHost")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let WriteComposeContentsToHost { name, run_directory, file_path, contents, - }: WriteComposeContentsToHost, - _: (), - ) -> anyhow::Result { + } = self; let file_path = periphery_config() .stack_dir .join(to_komodo_name(&name)) @@ -233,19 +214,28 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "WriteCommitComposeContents", skip(self))] +impl Resolve for WriteCommitComposeContents { + #[instrument( + name = "WriteCommitComposeContents", + skip_all, + fields( + stack = &self.stack.name, + username = &self.username, + file_path = &self.file_path, + ) + )] async fn resolve( - &self, - WriteCommitComposeContents { + self, + _: &super::Args, + ) -> serror::Result { + let WriteCommitComposeContents { stack, username, file_path, contents, git_token, - }: WriteCommitComposeContents, - _: (), - ) -> anyhow::Result { + } = self; + let root = pull_or_clone_stack(&stack, git_token).await?; let file_path = stack @@ -285,21 +275,25 @@ impl<'a> WriteStackRes for &'a mut ComposePullResponse { } } -impl Resolve for State { +impl Resolve for ComposePull { #[instrument( name = "ComposePull", - skip(self, git_token, registry_token) + skip_all, + fields( + stack = &self.stack.name, + service = &self.service, + ) )] async fn resolve( - &self, - ComposePull { + self, + _: &super::Args, + ) -> serror::Result { + let ComposePull { stack, service, git_token, registry_token, - }: ComposePull, - _: (), - ) -> anyhow::Result { + } = self; let mut res = ComposePullResponse::default(); let (run_directory, env_file_path) = write_stack(&stack, git_token, &mut res).await?; @@ -323,7 +317,7 @@ impl Resolve for State { for (path, full_path) in &file_paths { if !full_path.exists() { - return Err(anyhow!("Missing compose file at {path}")); + return Err(anyhow!("Missing compose file at {path}").into()); } } @@ -392,22 +386,26 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for ComposeUp { #[instrument( name = "ComposeUp", - skip(self, git_token, registry_token) + skip_all, + fields( + stack = &self.stack.name, + service = &self.service, + ) )] async fn resolve( - &self, - ComposeUp { + self, + _: &super::Args, + ) -> serror::Result { + let ComposeUp { stack, service, git_token, registry_token, replacers, - }: ComposeUp, - _: (), - ) -> anyhow::Result { + } = self; let mut res = ComposeUpResponse::default(); if let Err(e) = compose_up( stack, @@ -430,13 +428,10 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "ComposeExecution", skip(self))] - async fn resolve( - &self, - ComposeExecution { project, command }: ComposeExecution, - _: (), - ) -> anyhow::Result { +impl Resolve for ComposeExecution { + #[instrument(name = "ComposeExecution")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let ComposeExecution { project, command } = self; let docker_compose = docker_compose(); let log = run_komodo_command( "compose command", diff --git a/bin/periphery/src/api/container.rs b/bin/periphery/src/api/container.rs index 8ad785e67..a4e51858b 100644 --- a/bin/periphery/src/api/container.rs +++ b/bin/periphery/src/api/container.rs @@ -12,7 +12,6 @@ use resolver_api::Resolve; use crate::{ docker::{container_stats, docker_client, stop_container_command}, helpers::log_grep, - State, }; // ====== @@ -21,34 +20,26 @@ use crate::{ // -impl Resolve for State { - #[instrument( - name = "InspectContainer", - level = "debug", - skip(self) - )] +impl Resolve for InspectContainer { + #[instrument(name = "InspectContainer", level = "debug")] async fn resolve( - &self, - InspectContainer { name }: InspectContainer, - _: (), - ) -> anyhow::Result { - docker_client().inspect_container(&name).await + self, + _: &super::Args, + ) -> serror::Result { + Ok(docker_client().inspect_container(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "GetContainerLog", level = "debug", skip(self))] - async fn resolve( - &self, - GetContainerLog { +impl Resolve for GetContainerLog { + #[instrument(name = "GetContainerLog", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetContainerLog { name, tail, timestamps, - }: GetContainerLog, - _: (), - ) -> anyhow::Result { + } = self; let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); let command = @@ -62,23 +53,16 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument( - name = "GetContainerLogSearch", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetContainerLogSearch { +impl Resolve for GetContainerLogSearch { + #[instrument(name = "GetContainerLogSearch", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetContainerLogSearch { name, terms, combinator, invert, timestamps, - }: GetContainerLogSearch, - _: (), - ) -> anyhow::Result { + } = self; let grep = log_grep(&terms, combinator, invert); let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); @@ -99,19 +83,14 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument( - name = "GetContainerStats", - level = "debug", - skip(self) - )] +impl Resolve for GetContainerStats { + #[instrument(name = "GetContainerStats", level = "debug")] async fn resolve( - &self, - req: GetContainerStats, - _: (), - ) -> anyhow::Result { - let error = anyhow!("no stats matching {}", req.name); - let mut stats = container_stats(Some(req.name)).await?; + self, + _: &super::Args, + ) -> serror::Result { + let error = anyhow!("no stats matching {}", self.name); + let mut stats = container_stats(Some(self.name)).await?; let stats = stats.pop().ok_or(error)?; Ok(stats) } @@ -119,18 +98,13 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument( - name = "GetContainerStatsList", - level = "debug", - skip(self) - )] +impl Resolve for GetContainerStatsList { + #[instrument(name = "GetContainerStatsList", level = "debug")] async fn resolve( - &self, - _: GetContainerStatsList, - _: (), - ) -> anyhow::Result> { - container_stats(None).await + self, + _: &super::Args, + ) -> serror::Result> { + Ok(container_stats(None).await?) } } @@ -138,18 +112,14 @@ impl Resolve for State { // ACTIONS // ========= -impl Resolve for State { - #[instrument(name = "StartContainer", skip(self))] - async fn resolve( - &self, - StartContainer { name }: StartContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for StartContainer { + #[instrument(name = "StartContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { Ok( run_komodo_command( "docker start", None, - format!("docker start {name}"), + format!("docker start {}", self.name), false, ) .await, @@ -159,18 +129,14 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "RestartContainer", skip(self))] - async fn resolve( - &self, - RestartContainer { name }: RestartContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for RestartContainer { + #[instrument(name = "RestartContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { Ok( run_komodo_command( "docker restart", None, - format!("docker restart {name}"), + format!("docker restart {}", self.name), false, ) .await, @@ -180,18 +146,14 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PauseContainer", skip(self))] - async fn resolve( - &self, - PauseContainer { name }: PauseContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for PauseContainer { + #[instrument(name = "PauseContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { Ok( run_komodo_command( "docker pause", None, - format!("docker pause {name}"), + format!("docker pause {}", self.name), false, ) .await, @@ -199,18 +161,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseContainer", skip(self))] - async fn resolve( - &self, - UnpauseContainer { name }: UnpauseContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for UnpauseContainer { + #[instrument(name = "UnpauseContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { Ok( run_komodo_command( "docker unpause", None, - format!("docker unpause {name}"), + format!("docker unpause {}", self.name), false, ) .await, @@ -220,13 +178,10 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "StopContainer", skip(self))] - async fn resolve( - &self, - StopContainer { name, signal, time }: StopContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for StopContainer { + #[instrument(name = "StopContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let StopContainer { name, signal, time } = self; let command = stop_container_command(&name, signal, time); let log = run_komodo_command("docker stop", None, command, false).await; @@ -251,13 +206,10 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "RemoveContainer", skip(self))] - async fn resolve( - &self, - RemoveContainer { name, signal, time }: RemoveContainer, - _: (), - ) -> anyhow::Result { +impl Resolve for RemoveContainer { + #[instrument(name = "RemoveContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RemoveContainer { name, signal, time } = self; let stop_command = stop_container_command(&name, signal, time); let command = format!("{stop_command} && docker container rm {name}"); @@ -291,16 +243,13 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "RenameContainer", skip(self))] - async fn resolve( - &self, - RenameContainer { +impl Resolve for RenameContainer { + #[instrument(name = "RenameContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RenameContainer { curr_name, new_name, - }: RenameContainer, - _: (), - ) -> anyhow::Result { + } = self; let new = to_komodo_name(&new_name); let command = format!("docker rename {curr_name} {new}"); Ok( @@ -311,13 +260,9 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PruneContainers", skip(self))] - async fn resolve( - &self, - _: PruneContainers, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneContainers { + #[instrument(name = "PruneContainers", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker container prune -f"); Ok( run_komodo_command("prune containers", None, command, false) @@ -328,13 +273,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "StartAllContainers", skip(self))] +impl Resolve for StartAllContainers { + #[instrument(name = "StartAllContainers", skip_all)] async fn resolve( - &self, - StartAllContainers {}: StartAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -357,13 +301,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "RestartAllContainers", skip(self))] +impl Resolve for RestartAllContainers { + #[instrument(name = "RestartAllContainers", skip_all)] async fn resolve( - &self, - RestartAllContainers {}: RestartAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -386,13 +329,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PauseAllContainers", skip(self))] +impl Resolve for PauseAllContainers { + #[instrument(name = "PauseAllContainers", skip_all)] async fn resolve( - &self, - PauseAllContainers {}: PauseAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -415,13 +357,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "UnpauseAllContainers", skip(self))] +impl Resolve for UnpauseAllContainers { + #[instrument(name = "UnpauseAllContainers", skip_all)] async fn resolve( - &self, - UnpauseAllContainers {}: UnpauseAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -444,13 +385,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "StopAllContainers", skip(self))] +impl Resolve for StopAllContainers { + #[instrument(name = "StopAllContainers", skip_all)] async fn resolve( - &self, - StopAllContainers {}: StopAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await diff --git a/bin/periphery/src/api/deploy.rs b/bin/periphery/src/api/deploy.rs index 8b9d614c8..109ea4998 100644 --- a/bin/periphery/src/api/deploy.rs +++ b/bin/periphery/src/api/deploy.rs @@ -20,25 +20,26 @@ use crate::{ config::periphery_config, docker::{docker_login, pull_image}, helpers::{parse_extra_args, parse_labels}, - State, }; -impl Resolve for State { +impl Resolve for Deploy { #[instrument( name = "Deploy", - skip(self, core_replacers, registry_token) + skip_all, + fields( + stack = &self.deployment.name, + stop_signal = format!("{:?}", self.stop_signal), + stop_time = self.stop_time, + ) )] - async fn resolve( - &self, - Deploy { + async fn resolve(self, _: &super::Args) -> serror::Result { + let Deploy { deployment, stop_signal, stop_time, registry_token, replacers: core_replacers, - }: Deploy, - _: (), - ) -> anyhow::Result { + } = self; let image = if let DeploymentImage::Image { image } = &deployment.config.image { @@ -73,16 +74,13 @@ impl Resolve for State { let _ = pull_image(image).await; debug!("image pulled"); - let _ = State - .resolve( - RemoveContainer { - name: deployment.name.clone(), - signal: stop_signal, - time: stop_time, - }, - (), - ) - .await; + let _ = (RemoveContainer { + name: deployment.name.clone(), + signal: stop_signal, + time: stop_time, + }) + .resolve(&super::Args) + .await; debug!("container stopped and removed"); let command = docker_run_command(&deployment, image) diff --git a/bin/periphery/src/api/git.rs b/bin/periphery/src/api/git.rs index 08b81ec24..50fab64c6 100644 --- a/bin/periphery/src/api/git.rs +++ b/bin/periphery/src/api/git.rs @@ -8,41 +8,45 @@ use periphery_client::api::git::{ use resolver_api::Resolve; use tokio::fs; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; -impl Resolve for State { +impl Resolve for GetLatestCommit { + #[instrument(name = "CloneRepo", level = "debug")] async fn resolve( - &self, - GetLatestCommit { name }: GetLatestCommit, - _: (), - ) -> anyhow::Result { - let repo_path = periphery_config().repo_dir.join(name); + self, + _: &super::Args, + ) -> serror::Result { + let repo_path = periphery_config().repo_dir.join(self.name); if !repo_path.is_dir() { - return Err(anyhow!( - "Repo path is not directory. is it cloned?" - )); + return Err( + anyhow!("Repo path is not directory. is it cloned?").into(), + ); } - git::get_commit_hash_info(&repo_path).await + Ok(git::get_commit_hash_info(&repo_path).await?) } } -impl Resolve for State { +impl Resolve for CloneRepo { #[instrument( name = "CloneRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - CloneRepo { + self, + _: &super::Args, + ) -> serror::Result { + let CloneRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: CloneRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -81,28 +85,33 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { +impl Resolve for PullRepo { #[instrument( name = "PullRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - PullRepo { + self, + _: &super::Args, + ) -> serror::Result { + let PullRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: PullRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -141,28 +150,33 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { +impl Resolve for PullOrCloneRepo { #[instrument( name = "PullOrCloneRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - PullOrCloneRepo { + self, + _: &super::Args, + ) -> serror::Result { + let PullOrCloneRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: PullOrCloneRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -201,21 +215,19 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { - #[instrument(name = "RenameRepo", skip(self))] - async fn resolve( - &self, - RenameRepo { +impl Resolve for RenameRepo { + #[instrument(name = "RenameRepo")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RenameRepo { curr_name, new_name, - }: RenameRepo, - _: (), - ) -> anyhow::Result { + } = self; let renamed = fs::rename( periphery_config().repo_dir.join(&curr_name), periphery_config().repo_dir.join(&new_name), @@ -231,13 +243,10 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "DeleteRepo", skip(self))] - async fn resolve( - &self, - DeleteRepo { name }: DeleteRepo, - _: (), - ) -> anyhow::Result { +impl Resolve for DeleteRepo { + #[instrument(name = "DeleteRepo")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let DeleteRepo { name } = self; // If using custom clone path, it will be passed by core instead of name. // So the join will resolve to just the absolute path. let deleted = diff --git a/bin/periphery/src/api/image.rs b/bin/periphery/src/api/image.rs index e226b7672..aa215a9e7 100644 --- a/bin/periphery/src/api/image.rs +++ b/bin/periphery/src/api/image.rs @@ -11,34 +11,26 @@ use komodo_client::entities::{ use periphery_client::api::image::*; use resolver_api::Resolve; -use crate::{ - docker::{docker_client, docker_login}, - State, -}; +use crate::docker::{docker_client, docker_login}; // -impl Resolve for State { - #[instrument(name = "InspectImage", level = "debug", skip(self))] - async fn resolve( - &self, - InspectImage { name }: InspectImage, - _: (), - ) -> anyhow::Result { - docker_client().inspect_image(&name).await +impl Resolve for InspectImage { + #[instrument(name = "InspectImage", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_image(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "ImageHistory", level = "debug", skip(self))] +impl Resolve for ImageHistory { + #[instrument(name = "ImageHistory", level = "debug")] async fn resolve( - &self, - ImageHistory { name }: ImageHistory, - _: (), - ) -> anyhow::Result> { - docker_client().image_history(&name).await + self, + _: &super::Args, + ) -> serror::Result> { + Ok(docker_client().image_history(&self.name).await?) } } @@ -53,17 +45,14 @@ fn pull_cache() -> &'static TimeoutCache { PULL_CACHE.get_or_init(Default::default) } -impl Resolve for State { - #[instrument(name = "PullImage", skip(self))] - async fn resolve( - &self, - PullImage { +impl Resolve for PullImage { + #[instrument(name = "PullImage", skip_all, fields(name = &self.name))] + async fn resolve(self, _: &super::Args) -> serror::Result { + let PullImage { name, account, token, - }: PullImage, - _: (), - ) -> anyhow::Result { + } = self; // Acquire the image lock let lock = pull_cache().get_lock(name.clone()).await; @@ -74,7 +63,7 @@ impl Resolve for State { // Early return from cache if lasted pulled with PULL_TIMEOUT if locked.last_ts + PULL_TIMEOUT > komodo_timestamp() { - return locked.clone_res(); + return locked.clone_res().map_err(Into::into); } let res = async { @@ -100,33 +89,25 @@ impl Resolve for State { // then immediately also use this same result. locked.set(&res, komodo_timestamp()); - res + res.map_err(Into::into) } } // -impl Resolve for State { - #[instrument(name = "DeleteImage", skip(self))] - async fn resolve( - &self, - DeleteImage { name }: DeleteImage, - _: (), - ) -> anyhow::Result { - let command = format!("docker image rm {name}"); +impl Resolve for DeleteImage { + #[instrument(name = "DeleteImage")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker image rm {}", self.name); Ok(run_komodo_command("delete image", None, command, false).await) } } // -impl Resolve for State { - #[instrument(name = "PruneImages", skip(self))] - async fn resolve( - &self, - _: PruneImages, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneImages { + #[instrument(name = "PruneImages")] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker image prune -a -f"); Ok(run_komodo_command("prune images", None, command, false).await) } diff --git a/bin/periphery/src/api/mod.rs b/bin/periphery/src/api/mod.rs index e4e9dea0e..536c3e887 100644 --- a/bin/periphery/src/api/mod.rs +++ b/bin/periphery/src/api/mod.rs @@ -2,24 +2,23 @@ use anyhow::Context; use command::run_komodo_command; use derive_variants::EnumVariants; use futures::TryFutureExt; -use komodo_client::entities::{update::Log, SystemCommand}; +use komodo_client::entities::{ + config::{DockerRegistry, GitProvider}, + update::Log, + SystemCommand, +}; use periphery_client::api::{ build::*, compose::*, container::*, git::*, image::*, network::*, stats::*, volume::*, GetDockerLists, GetDockerListsResponse, - GetHealth, GetVersion, GetVersionResponse, ListDockerRegistries, - ListGitProviders, ListSecrets, PruneSystem, RunCommand, + GetHealth, GetHealthResponse, GetVersion, GetVersionResponse, + ListDockerRegistries, ListGitProviders, ListSecrets, PruneSystem, + RunCommand, }; -use resolver_api::{derive::Resolver, Resolve, ResolveToString}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; -use crate::{ - config::{ - docker_registries_response, git_providers_response, - secrets_response, - }, - docker::docker_client, - State, -}; +use crate::{config::periphery_config, docker::docker_client}; mod build; mod compose; @@ -31,32 +30,29 @@ mod network; mod stats; mod volume; +pub struct Args; + #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] +#[args(Args)] +#[response(Response)] +#[error(serror::Error)] #[variant_derive(Debug)] #[serde(tag = "type", content = "params")] -#[resolver_target(State)] #[allow(clippy::enum_variant_names, clippy::large_enum_variant)] pub enum PeripheryRequest { GetVersion(GetVersion), - #[to_string_resolver] GetHealth(GetHealth), // Config (Read) - #[to_string_resolver] ListGitProviders(ListGitProviders), - #[to_string_resolver] ListDockerRegistries(ListDockerRegistries), - #[to_string_resolver] ListSecrets(ListSecrets), // Stats / Info (Read) - #[to_string_resolver] GetSystemInformation(GetSystemInformation), - #[to_string_resolver] GetSystemStats(GetSystemStats), - #[to_string_resolver] GetSystemProcesses(GetSystemProcesses), GetLatestCommit(GetLatestCommit), @@ -142,26 +138,24 @@ pub enum PeripheryRequest { // -impl ResolveToString for State { - #[instrument(name = "GetHealth", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: GetHealth, - _: (), - ) -> anyhow::Result { - Ok(String::from("{}")) +impl Resolve for GetHealth { + #[instrument(name = "GetHealth", level = "debug", skip_all)] + async fn resolve( + self, + _: &Args, + ) -> serror::Result { + Ok(GetHealthResponse {}) } } // -impl Resolve for State { +impl Resolve for GetVersion { #[instrument(name = "GetVersion", level = "debug", skip(self))] async fn resolve( - &self, - _: GetVersion, - _: (), - ) -> anyhow::Result { + self, + _: &Args, + ) -> serror::Result { Ok(GetVersionResponse { version: env!("CARGO_PKG_VERSION").to_string(), }) @@ -170,56 +164,51 @@ impl Resolve for State { // -impl ResolveToString for State { - #[instrument( - name = "ListGitProviders", - level = "debug", - skip(self) - )] - async fn resolve_to_string( - &self, - _: ListGitProviders, - _: (), - ) -> anyhow::Result { - Ok(git_providers_response().clone()) +impl Resolve for ListGitProviders { + #[instrument(name = "ListGitProviders", level = "debug", skip_all)] + async fn resolve( + self, + _: &Args, + ) -> serror::Result> { + Ok(periphery_config().git_providers.clone()) } } -impl ResolveToString for State { +impl Resolve for ListDockerRegistries { #[instrument( name = "ListDockerRegistries", level = "debug", - skip(self) + skip_all )] - async fn resolve_to_string( - &self, - _: ListDockerRegistries, - _: (), - ) -> anyhow::Result { - Ok(docker_registries_response().clone()) + async fn resolve( + self, + _: &Args, + ) -> serror::Result> { + Ok(periphery_config().docker_registries.clone()) } } // -impl ResolveToString for State { - #[instrument(name = "ListSecrets", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: ListSecrets, - _: (), - ) -> anyhow::Result { - Ok(secrets_response().clone()) +impl Resolve for ListSecrets { + #[instrument(name = "ListSecrets", level = "debug", skip_all)] + async fn resolve(self, _: &Args) -> serror::Result> { + Ok( + periphery_config() + .secrets + .keys() + .cloned() + .collect::>(), + ) } } -impl Resolve for State { - #[instrument(name = "GetDockerLists", level = "debug", skip(self))] +impl Resolve for GetDockerLists { + #[instrument(name = "GetDockerLists", level = "debug", skip_all)] async fn resolve( - &self, - GetDockerLists {}: GetDockerLists, - _: (), - ) -> anyhow::Result { + self, + _: &Args, + ) -> serror::Result { let docker = docker_client(); let containers = docker.list_containers().await.map_err(Into::into); @@ -232,7 +221,9 @@ impl Resolve for State { docker.list_networks(_containers).map_err(Into::into), docker.list_images(_containers).map_err(Into::into), docker.list_volumes(_containers).map_err(Into::into), - self.resolve(ListComposeProjects {}, ()).map_err(Into::into) + ListComposeProjects {} + .resolve(&Args) + .map_err(|e| e.error.into()) ); Ok(GetDockerListsResponse { containers, @@ -244,16 +235,13 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RunCommand", skip(self))] - async fn resolve( - &self, - RunCommand { +impl Resolve for RunCommand { + #[instrument(name = "RunCommand")] + async fn resolve(self, _: &Args) -> serror::Result { + let RunCommand { command: SystemCommand { path, command }, - }: RunCommand, - _: (), - ) -> anyhow::Result { - tokio::spawn(async move { + } = self; + let res = tokio::spawn(async move { let command = if path.is_empty() { command } else { @@ -262,17 +250,14 @@ impl Resolve for State { run_komodo_command("run command", None, command, false).await }) .await - .context("failure in spawned task") + .context("failure in spawned task")?; + Ok(res) } } -impl Resolve for State { - #[instrument(name = "PruneSystem", skip(self))] - async fn resolve( - &self, - PruneSystem {}: PruneSystem, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneSystem { + #[instrument(name = "PruneSystem", skip_all)] + async fn resolve(self, _: &Args) -> serror::Result { let command = String::from("docker system prune -a -f --volumes"); Ok(run_komodo_command("prune system", None, command, false).await) } diff --git a/bin/periphery/src/api/network.rs b/bin/periphery/src/api/network.rs index f13edcf43..3146b73c8 100644 --- a/bin/periphery/src/api/network.rs +++ b/bin/periphery/src/api/network.rs @@ -5,30 +5,23 @@ use komodo_client::entities::{ use periphery_client::api::network::*; use resolver_api::Resolve; -use crate::{docker::docker_client, State}; +use crate::docker::docker_client; // -impl Resolve for State { - #[instrument(name = "InspectNetwork", level = "debug", skip(self))] - async fn resolve( - &self, - InspectNetwork { name }: InspectNetwork, - _: (), - ) -> anyhow::Result { - docker_client().inspect_network(&name).await +impl Resolve for InspectNetwork { + #[instrument(name = "InspectNetwork", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_network(&self.name).await?) } } // -impl Resolve for State { +impl Resolve for CreateNetwork { #[instrument(name = "CreateNetwork", skip(self))] - async fn resolve( - &self, - CreateNetwork { name, driver }: CreateNetwork, - _: (), - ) -> anyhow::Result { + async fn resolve(self, _: &super::Args) -> serror::Result { + let CreateNetwork { name, driver } = self; let driver = match driver { Some(driver) => format!(" -d {driver}"), None => String::new(), @@ -43,14 +36,10 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for DeleteNetwork { #[instrument(name = "DeleteNetwork", skip(self))] - async fn resolve( - &self, - DeleteNetwork { name }: DeleteNetwork, - _: (), - ) -> anyhow::Result { - let command = format!("docker network rm {name}"); + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker network rm {}", self.name); Ok( run_komodo_command("delete network", None, command, false) .await, @@ -60,13 +49,9 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for PruneNetworks { #[instrument(name = "PruneNetworks", skip(self))] - async fn resolve( - &self, - _: PruneNetworks, - _: (), - ) -> anyhow::Result { + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker network prune -f"); Ok( run_komodo_command("prune networks", None, command, false) diff --git a/bin/periphery/src/api/stats.rs b/bin/periphery/src/api/stats.rs index f8df366d1..9f4429fbe 100644 --- a/bin/periphery/src/api/stats.rs +++ b/bin/periphery/src/api/stats.rs @@ -1,59 +1,48 @@ -use anyhow::Context; +use komodo_client::entities::stats::{ + SystemInformation, SystemProcess, SystemStats, +}; use periphery_client::api::stats::{ GetSystemInformation, GetSystemProcesses, GetSystemStats, }; -use resolver_api::ResolveToString; +use resolver_api::Resolve; -use crate::{stats::stats_client, State}; +use crate::stats::stats_client; -impl ResolveToString for State { +impl Resolve for GetSystemInformation { #[instrument( name = "GetSystemInformation", level = "debug", - skip(self) + skip_all )] - async fn resolve_to_string( - &self, - _: GetSystemInformation, - _: (), - ) -> anyhow::Result { - let info = &stats_client().read().await.info; - serde_json::to_string(info) - .context("failed to serialize response to string") + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result { + Ok(stats_client().read().await.info.clone()) } } // -impl ResolveToString for State { - #[instrument(name = "GetSystemStats", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: GetSystemStats, - _: (), - ) -> anyhow::Result { - let stats = &stats_client().read().await.stats; - serde_json::to_string(stats) - .context("failed to serialize response to string") +impl Resolve for GetSystemStats { + #[instrument(name = "GetSystemStats", level = "debug", skip_all)] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result { + Ok(stats_client().read().await.stats.clone()) } } // -impl ResolveToString for State { - #[instrument( - name = "GetSystemProcesses", - level = "debug", - skip(self) - )] - async fn resolve_to_string( - &self, - _: GetSystemProcesses, - _: (), - ) -> anyhow::Result { - let stats = &stats_client().read().await.get_processes(); - serde_json::to_string(&stats) - .context("failed to serialize response to string") +impl Resolve for GetSystemProcesses { + #[instrument(name = "GetSystemProcesses", level = "debug")] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result> { + Ok(stats_client().read().await.get_processes()) } } diff --git a/bin/periphery/src/api/volume.rs b/bin/periphery/src/api/volume.rs index 622fe6f4b..ef72a1d1e 100644 --- a/bin/periphery/src/api/volume.rs +++ b/bin/periphery/src/api/volume.rs @@ -3,31 +3,23 @@ use komodo_client::entities::{docker::volume::Volume, update::Log}; use periphery_client::api::volume::*; use resolver_api::Resolve; -use crate::{docker::docker_client, State}; +use crate::docker::docker_client; // -impl Resolve for State { - #[instrument(name = "InspectVolume", level = "debug", skip(self))] - async fn resolve( - &self, - InspectVolume { name }: InspectVolume, - _: (), - ) -> anyhow::Result { - docker_client().inspect_volume(&name).await +impl Resolve for InspectVolume { + #[instrument(name = "InspectVolume", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_volume(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "DeleteVolume", skip(self))] - async fn resolve( - &self, - DeleteVolume { name }: DeleteVolume, - _: (), - ) -> anyhow::Result { - let command = format!("docker volume rm {name}"); +impl Resolve for DeleteVolume { + #[instrument(name = "DeleteVolume")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker volume rm {}", self.name); Ok( run_komodo_command("delete volume", None, command, false).await, ) @@ -36,13 +28,9 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PruneVolumes", skip(self))] - async fn resolve( - &self, - _: PruneVolumes, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneVolumes { + #[instrument(name = "PruneVolumes")] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker volume prune -a -f"); Ok( run_komodo_command("prune volumes", None, command, false).await, diff --git a/bin/periphery/src/compose.rs b/bin/periphery/src/compose.rs index 784670262..2bb8877d6 100644 --- a/bin/periphery/src/compose.rs +++ b/bin/periphery/src/compose.rs @@ -25,7 +25,6 @@ use crate::{ config::periphery_config, docker::docker_login, helpers::{interpolate_variables, parse_extra_args}, - State, }; pub fn docker_compose() -> &'static str { @@ -588,37 +587,31 @@ pub async fn write_stack( .to_string(); let clone_or_pull_res = if stack.config.reclone { - State - .resolve( - CloneRepo { - args, - git_token, - environment: env_vars, - env_file_path, - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await + CloneRepo { + args, + git_token, + environment: env_vars, + env_file_path, + skip_secret_interp: stack.config.skip_secret_interp, + // repo replacer only needed for on_clone / on_pull, + // which aren't available for stacks + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await } else { - State - .resolve( - PullOrCloneRepo { - args, - git_token, - environment: env_vars, - env_file_path, - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await + PullOrCloneRepo { + args, + git_token, + environment: env_vars, + env_file_path, + skip_secret_interp: stack.config.skip_secret_interp, + // repo replacer only needed for on_clone / on_pull, + // which aren't available for stacks + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await }; let RepoActionResponse { @@ -630,7 +623,7 @@ pub async fn write_stack( Ok(res) => res, Err(e) => { let error = format_serror( - &e.context("failed to pull stack repo").into(), + &e.error.context("failed to pull stack repo").into(), ); res .logs() diff --git a/bin/periphery/src/config.rs b/bin/periphery/src/config.rs index 83c77015f..4fd2980f4 100644 --- a/bin/periphery/src/config.rs +++ b/bin/periphery/src/config.rs @@ -88,30 +88,3 @@ pub fn periphery_config() -> &'static PeripheryConfig { } }) } - -pub fn git_providers_response() -> &'static String { - static GIT_PROVIDERS_RESPONSE: OnceLock = OnceLock::new(); - GIT_PROVIDERS_RESPONSE.get_or_init(|| { - let config = periphery_config(); - serde_json::to_string(&config.git_providers).unwrap() - }) -} - -pub fn docker_registries_response() -> &'static String { - static DOCKER_REGISTRIES_RESPONSE: OnceLock = - OnceLock::new(); - DOCKER_REGISTRIES_RESPONSE.get_or_init(|| { - let config = periphery_config(); - serde_json::to_string(&config.docker_registries).unwrap() - }) -} - -pub fn secrets_response() -> &'static String { - static SECRETS_RESPONSE: OnceLock = OnceLock::new(); - SECRETS_RESPONSE.get_or_init(|| { - serde_json::to_string( - &periphery_config().secrets.keys().collect::>(), - ) - .unwrap() - }) -} diff --git a/bin/periphery/src/helpers.rs b/bin/periphery/src/helpers.rs index 790fd6a01..19ed9adb3 100644 --- a/bin/periphery/src/helpers.rs +++ b/bin/periphery/src/helpers.rs @@ -11,7 +11,7 @@ use komodo_client::{ use periphery_client::api::git::PullOrCloneRepo; use resolver_api::Resolve; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; pub fn git_token( domain: &str, @@ -140,21 +140,19 @@ pub async fn pull_or_clone_stack( } }; - State - .resolve( - PullOrCloneRepo { - args, - git_token, - environment: vec![], - env_file_path: stack.config.env_file_path.clone(), - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await?; + PullOrCloneRepo { + args, + git_token, + environment: vec![], + env_file_path: stack.config.env_file_path.clone(), + skip_secret_interp: stack.config.skip_secret_interp, + // repo replacer only needed for on_clone / on_pull, + // which aren't available for stacks + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await + .map_err(|e| e.error)?; Ok(root) } diff --git a/bin/periphery/src/main.rs b/bin/periphery/src/main.rs index 1cfdb1c37..e2272f257 100644 --- a/bin/periphery/src/main.rs +++ b/bin/periphery/src/main.rs @@ -16,8 +16,6 @@ mod router; mod ssl; mod stats; -struct State; - async fn app() -> anyhow::Result<()> { dotenvy::dotenv().ok(); let config = config::periphery_config(); @@ -26,7 +24,7 @@ async fn app() -> anyhow::Result<()> { info!("Komodo Periphery version: v{}", env!("CARGO_PKG_VERSION")); info!("{:?}", config.sanitized()); - stats::spawn_system_stats_polling_threads(); + stats::spawn_system_stats_polling_thread(); let socket_addr = SocketAddr::from_str(&format!("0.0.0.0:{}", config.port)) diff --git a/bin/periphery/src/router.rs b/bin/periphery/src/router.rs index daeda62bf..366fa29a9 100644 --- a/bin/periphery/src/router.rs +++ b/bin/periphery/src/router.rs @@ -10,13 +10,12 @@ use axum::{ routing::post, Router, }; -use axum_extra::{headers::ContentType, TypedHeader}; use derive_variants::ExtractVariant; -use resolver_api::Resolver; +use resolver_api::Resolve; use serror::{AddStatusCode, AddStatusCodeError, Json}; use uuid::Uuid; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; pub fn router() -> Router { Router::new() @@ -27,7 +26,7 @@ pub fn router() -> Router { async fn handler( Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let req_id = Uuid::new_v4(); let res = tokio::spawn(task(req_id, request)) @@ -38,28 +37,22 @@ async fn handler( warn!("request {req_id} spawn error: {e:#}"); } - Ok((TypedHeader(ContentType::json()), res??)) + res? } async fn task( req_id: Uuid, request: crate::api::PeripheryRequest, -) -> anyhow::Result { +) -> serror::Result { let variant = request.extract_variant(); - let res = - State - .resolve_request(request, ()) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&crate::api::Args).await.map(|res| res.0); if let Err(e) = &res { - warn!("request {req_id} | type: {variant:?} | error: {e:#}"); + warn!( + "request {req_id} | type: {variant:?} | error: {:#}", + e.error + ); } res diff --git a/bin/periphery/src/stats.rs b/bin/periphery/src/stats.rs index 76e520b2b..4181f3de9 100644 --- a/bin/periphery/src/stats.rs +++ b/bin/periphery/src/stats.rs @@ -16,21 +16,8 @@ pub fn stats_client() -> &'static RwLock { } /// This should be called before starting the server in main.rs. -/// Keeps the caches stats up to date -pub fn spawn_system_stats_polling_threads() { - tokio::spawn(async move { - let client = stats_client(); - loop { - let ts = wait_until_timelength( - async_timing_util::Timelength::FiveMinutes, - 0, - ) - .await; - let mut client = client.write().await; - client.refresh_lists(); - client.stats.refresh_list_ts = ts as i64; - } - }); +/// Keeps the cached stats up to date +pub fn spawn_system_stats_polling_thread() { tokio::spawn(async move { let polling_rate = periphery_config() .stats_polling_rate @@ -89,13 +76,8 @@ impl StatsClient { self.system.refresh_cpu_all(); self.system.refresh_memory(); self.system.refresh_processes(ProcessesToUpdate::All, true); - self.disks.refresh(); - self.networks.refresh(); - } - - fn refresh_lists(&mut self) { - self.disks.refresh_list(); - self.networks.refresh_list(); + self.disks.refresh(true); + self.networks.refresh(true); } pub fn get_system_stats(&self) -> SystemStats { diff --git a/client/core/rs/src/api/auth.rs b/client/core/rs/src/api/auth.rs index b15685c7c..c6ca3b20f 100644 --- a/client/core/rs/src/api/auth.rs +++ b/client/core/rs/src/api/auth.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -22,10 +22,11 @@ pub struct JwtResponse { /// Response: [GetLoginOptionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(GetLoginOptionsResponse)] +#[error(serror::Error)] pub struct GetLoginOptions {} /// The response for [GetLoginOptions]. @@ -53,10 +54,11 @@ pub struct GetLoginOptionsResponse { /// Note. This method is only available if the core api has `local_auth` enabled. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(CreateLocalUserResponse)] +#[error(serror::Error)] pub struct CreateLocalUser { /// The username for the new user. pub username: String, @@ -77,10 +79,11 @@ pub type CreateLocalUserResponse = JwtResponse; /// Note. This method is only available if the core api has `local_auth` enabled. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(LoginLocalUserResponse)] +#[error(serror::Error)] pub struct LoginLocalUser { /// The user's username pub username: String, @@ -99,10 +102,11 @@ pub type LoginLocalUserResponse = JwtResponse; /// Response: [ExchangeForJwtResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(ExchangeForJwtResponse)] +#[error(serror::Error)] pub struct ExchangeForJwt { /// The 'exchange token' pub token: String, @@ -118,10 +122,11 @@ pub type ExchangeForJwtResponse = JwtResponse; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(GetUserResponse)] +#[error(serror::Error)] pub struct GetUser {} #[typeshare] diff --git a/client/core/rs/src/api/execute/action.rs b/client/core/rs/src/api/execute/action.rs index 93b5978c0..b5f8a2fb6 100644 --- a/client/core/rs/src/api/execute/action.rs +++ b/client/core/rs/src/api/execute/action.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunAction { /// Id or name pub action: String, @@ -35,12 +36,13 @@ pub struct RunAction { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunAction { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/build.rs b/client/core/rs/src/api/execute/build.rs index 0fd0de62c..dadff102f 100644 --- a/client/core/rs/src/api/execute/build.rs +++ b/client/core/rs/src/api/execute/build.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -23,12 +23,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunBuild { /// Can be build id or name pub build: String, @@ -44,12 +45,13 @@ pub struct RunBuild { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunBuild { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -76,12 +78,13 @@ pub struct BatchRunBuild { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CancelBuild { /// Can be id or name pub build: String, diff --git a/client/core/rs/src/api/execute/deployment.rs b/client/core/rs/src/api/execute/deployment.rs index bdf83e533..a37833a40 100644 --- a/client/core/rs/src/api/execute/deployment.rs +++ b/client/core/rs/src/api/execute/deployment.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -22,12 +22,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct Deploy { /// Name or id pub deployment: String, @@ -49,12 +50,13 @@ pub struct Deploy { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeploy { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -79,12 +81,13 @@ pub struct BatchDeploy { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullDeployment { /// Name or id pub deployment: String, @@ -102,12 +105,13 @@ pub struct PullDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartDeployment { /// Name or id pub deployment: String, @@ -125,12 +129,13 @@ pub struct StartDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartDeployment { /// Name or id pub deployment: String, @@ -148,12 +153,13 @@ pub struct RestartDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseDeployment { /// Name or id pub deployment: String, @@ -173,12 +179,13 @@ pub struct PauseDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseDeployment { /// Name or id pub deployment: String, @@ -196,12 +203,13 @@ pub struct UnpauseDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopDeployment { /// Name or id pub deployment: String, @@ -224,12 +232,13 @@ pub struct StopDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyDeployment { /// Name or id. pub deployment: String, @@ -249,12 +258,13 @@ pub struct DestroyDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDestroyDeployment { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/procedure.rs b/client/core/rs/src/api/execute/procedure.rs index 950282af9..983d41427 100644 --- a/client/core/rs/src/api/execute/procedure.rs +++ b/client/core/rs/src/api/execute/procedure.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunProcedure { /// Id or name pub procedure: String, @@ -35,12 +36,13 @@ pub struct RunProcedure { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunProcedure { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/repo.rs b/client/core/rs/src/api/execute/repo.rs index 9d04ef358..0e0fed797 100644 --- a/client/core/rs/src/api/execute/repo.rs +++ b/client/core/rs/src/api/execute/repo.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -26,12 +26,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CloneRepo { /// Id or name pub repo: String, @@ -47,12 +48,13 @@ pub struct CloneRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchCloneRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -82,12 +84,13 @@ pub struct BatchCloneRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullRepo { /// Id or name pub repo: String, @@ -103,12 +106,13 @@ pub struct PullRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchPullRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -142,12 +146,13 @@ pub struct BatchPullRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct BuildRepo { /// Id or name pub repo: String, @@ -163,12 +168,13 @@ pub struct BuildRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchBuildRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -195,12 +201,13 @@ pub struct BatchBuildRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CancelRepoBuild { /// Can be id or name pub repo: String, diff --git a/client/core/rs/src/api/execute/server.rs b/client/core/rs/src/api/execute/server.rs index 87067ff40..76f76e038 100644 --- a/client/core/rs/src/api/execute/server.rs +++ b/client/core/rs/src/api/execute/server.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -22,12 +22,13 @@ use super::KomodoExecuteRequest; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartContainer { /// Name or id pub server: String, @@ -47,12 +48,13 @@ pub struct StartContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartContainer { /// Name or id pub server: String, @@ -72,12 +74,13 @@ pub struct RestartContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseContainer { /// Name or id pub server: String, @@ -99,12 +102,13 @@ pub struct PauseContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseContainer { /// Name or id pub server: String, @@ -124,12 +128,13 @@ pub struct UnpauseContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopContainer { /// Name or id pub server: String, @@ -154,12 +159,13 @@ pub struct StopContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyContainer { /// Name or id pub server: String, @@ -181,12 +187,13 @@ pub struct DestroyContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartAllContainers { /// Name or id pub server: String, @@ -202,12 +209,13 @@ pub struct StartAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartAllContainers { /// Name or id pub server: String, @@ -223,12 +231,13 @@ pub struct RestartAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseAllContainers { /// Name or id pub server: String, @@ -244,12 +253,13 @@ pub struct PauseAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseAllContainers { /// Name or id pub server: String, @@ -265,12 +275,13 @@ pub struct UnpauseAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopAllContainers { /// Name or id pub server: String, @@ -288,12 +299,13 @@ pub struct StopAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneContainers { /// Id or name pub server: String, @@ -312,12 +324,13 @@ pub struct PruneContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteNetwork { /// Id or name. pub server: String, @@ -337,12 +350,13 @@ pub struct DeleteNetwork { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneNetworks { /// Id or name pub server: String, @@ -359,12 +373,13 @@ pub struct PruneNetworks { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteImage { /// Id or name. pub server: String, @@ -384,12 +399,13 @@ pub struct DeleteImage { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneImages { /// Id or name pub server: String, @@ -406,12 +422,13 @@ pub struct PruneImages { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteVolume { /// Id or name. pub server: String, @@ -431,12 +448,13 @@ pub struct DeleteVolume { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneVolumes { /// Id or name pub server: String, @@ -454,12 +472,13 @@ pub struct PruneVolumes { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneDockerBuilders { /// Id or name pub server: String, @@ -477,12 +496,13 @@ pub struct PruneDockerBuilders { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneBuildx { /// Id or name pub server: String, @@ -500,12 +520,13 @@ pub struct PruneBuildx { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneSystem { /// Id or name pub server: String, diff --git a/client/core/rs/src/api/execute/server_template.rs b/client/core/rs/src/api/execute/server_template.rs index 7971edecd..2f5a0afbd 100644 --- a/client/core/rs/src/api/execute/server_template.rs +++ b/client/core/rs/src/api/execute/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoExecuteRequest; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct LaunchServer { /// The name of the created server. pub name: String, diff --git a/client/core/rs/src/api/execute/stack.rs b/client/core/rs/src/api/execute/stack.rs index 0d233d607..5a731063b 100644 --- a/client/core/rs/src/api/execute/stack.rs +++ b/client/core/rs/src/api/execute/stack.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeployStack { /// Id or name pub stack: String, @@ -42,12 +43,13 @@ pub struct DeployStack { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeployStack { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -74,12 +76,13 @@ pub struct BatchDeployStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeployStackIfChanged { /// Id or name pub stack: String, @@ -98,12 +101,13 @@ pub struct DeployStackIfChanged { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeployStackIfChanged { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -128,12 +132,13 @@ pub struct BatchDeployStackIfChanged { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullStack { /// Id or name pub stack: String, @@ -151,12 +156,13 @@ pub struct PullStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartStack { /// Id or name pub stack: String, @@ -174,12 +180,13 @@ pub struct StartStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartStack { /// Id or name pub stack: String, @@ -197,12 +204,13 @@ pub struct RestartStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseStack { /// Id or name pub stack: String, @@ -222,12 +230,13 @@ pub struct PauseStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseStack { /// Id or name pub stack: String, @@ -245,12 +254,13 @@ pub struct UnpauseStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopStack { /// Id or name pub stack: String, @@ -270,12 +280,13 @@ pub struct StopStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyStack { /// Id or name pub stack: String, @@ -298,12 +309,13 @@ pub struct DestroyStack { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDestroyStack { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/sync.rs b/client/core/rs/src/api/execute/sync.rs index a7e6d7f97..076c5d842 100644 --- a/client/core/rs/src/api/execute/sync.rs +++ b/client/core/rs/src/api/execute/sync.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::KomodoExecuteRequest; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunSync { /// Id or name pub sync: String, diff --git a/client/core/rs/src/api/read/action.rs b/client/core/rs/src/api/read/action.rs index 0f8aa8c46..476628c7f 100644 --- a/client/core/rs/src/api/read/action.rs +++ b/client/core/rs/src/api/read/action.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific action. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionResponse)] +#[error(serror::Error)] pub struct GetAction { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetActionResponse = Action; /// List actions matching optional query. Response: [ListActionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListActionsResponse)] +#[error(serror::Error)] pub struct ListActions { /// optional structured query to filter actions. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListActionsResponse = Vec; /// List actions matching optional query. Response: [ListFullActionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullActionsResponse)] +#[error(serror::Error)] pub struct ListFullActions { /// optional structured query to filter actions. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullActionsResponse = Vec; /// Get current action state for the action. Response: [ActionActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionActionStateResponse)] +#[error(serror::Error)] pub struct GetActionActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetActionActionStateResponse = ActionActionState; /// Response: [GetActionsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionsSummaryResponse)] +#[error(serror::Error)] pub struct GetActionsSummary {} /// Response for [GetActionsSummary]. diff --git a/client/core/rs/src/api/read/alert.rs b/client/core/rs/src/api/read/alert.rs index 4f27ecad3..534845ca9 100644 --- a/client/core/rs/src/api/read/alert.rs +++ b/client/core/rs/src/api/read/alert.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [ListAlertsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAlertsResponse)] +#[error(serror::Error)] pub struct ListAlerts { /// Pass a custom mongo query to filter the alerts. /// @@ -62,10 +63,11 @@ pub struct ListAlertsResponse { /// Get an alert: Response: [Alert]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlertResponse)] +#[error(serror::Error)] pub struct GetAlert { pub id: String, } diff --git a/client/core/rs/src/api/read/alerter.rs b/client/core/rs/src/api/read/alerter.rs index ad45f803b..15ce143e7 100644 --- a/client/core/rs/src/api/read/alerter.rs +++ b/client/core/rs/src/api/read/alerter.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific alerter. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlerterResponse)] +#[error(serror::Error)] pub struct GetAlerter { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetAlerterResponse = Alerter; /// List alerters matching optional query. Response: [ListAlertersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAlertersResponse)] +#[error(serror::Error)] pub struct ListAlerters { /// Structured query to filter alerters. #[serde(default)] @@ -48,10 +50,11 @@ pub type ListAlertersResponse = Vec; /// List full alerters matching optional query. Response: [ListFullAlertersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullAlertersResponse)] +#[error(serror::Error)] pub struct ListFullAlerters { /// Structured query to filter alerters. #[serde(default)] @@ -67,10 +70,11 @@ pub type ListFullAlertersResponse = Vec; /// Response: [GetAlertersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlertersSummaryResponse)] +#[error(serror::Error)] pub struct GetAlertersSummary {} /// Response for [GetAlertersSummary]. diff --git a/client/core/rs/src/api/read/build.rs b/client/core/rs/src/api/read/build.rs index cf2c5169c..e45c9a21f 100644 --- a/client/core/rs/src/api/read/build.rs +++ b/client/core/rs/src/api/read/build.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -17,10 +17,11 @@ use super::KomodoReadRequest; /// Get a specific build. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildResponse)] +#[error(serror::Error)] pub struct GetBuild { /// Id or name #[serde(alias = "id", alias = "name")] @@ -35,10 +36,11 @@ pub type GetBuildResponse = Build; /// List builds matching optional query. Response: [ListBuildsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildsResponse)] +#[error(serror::Error)] pub struct ListBuilds { /// optional structured query to filter builds. #[serde(default)] @@ -53,10 +55,11 @@ pub type ListBuildsResponse = Vec; /// List builds matching optional query. Response: [ListFullBuildsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullBuildsResponse)] +#[error(serror::Error)] pub struct ListFullBuilds { /// optional structured query to filter builds. #[serde(default)] @@ -71,10 +74,11 @@ pub type ListFullBuildsResponse = Vec; /// Get current action state for the build. Response: [BuildActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildActionStateResponse)] +#[error(serror::Error)] pub struct GetBuildActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -90,10 +94,11 @@ pub type GetBuildActionStateResponse = BuildActionState; /// Response: [GetBuildsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildsSummaryResponse)] +#[error(serror::Error)] pub struct GetBuildsSummary {} /// Response for [GetBuildsSummary]. @@ -121,10 +126,11 @@ pub struct GetBuildsSummaryResponse { /// Query for older pages by incrementing the page, starting at 0. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildMonthlyStatsResponse)] +#[error(serror::Error)] pub struct GetBuildMonthlyStats { /// Query for older data by incrementing the page. /// `page: 0` is the default, and will return the most recent data. @@ -182,10 +188,11 @@ impl GetBuildMonthlyStatsResponse { /// Response: [ListBuildVersionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildVersionsResponse)] +#[error(serror::Error)] pub struct ListBuildVersions { /// Id or name #[serde(alias = "id", alias = "name")] @@ -216,10 +223,11 @@ pub struct BuildVersionResponseItem { /// Useful to offer suggestions. Response: [ListCommonBuildExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonBuildExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonBuildExtraArgs { /// optional structured query to filter builds. #[serde(default)] @@ -234,10 +242,11 @@ pub type ListCommonBuildExtraArgsResponse = Vec; /// Get whether a Build's target repo has a webhook for the build configured. Response: [GetBuildWebhookEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildWebhookEnabledResponse)] +#[error(serror::Error)] pub struct GetBuildWebhookEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/builder.rs b/client/core/rs/src/api/read/builder.rs index 0dd7e09e7..4516fdec5 100644 --- a/client/core/rs/src/api/read/builder.rs +++ b/client/core/rs/src/api/read/builder.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific builder by id or name. Response: [Builder]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuilderResponse)] +#[error(serror::Error)] pub struct GetBuilder { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetBuilderResponse = Builder; /// List builders matching structured query. Response: [ListBuildersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildersResponse)] +#[error(serror::Error)] pub struct ListBuilders { #[serde(default)] pub query: BuilderQuery, @@ -49,10 +51,11 @@ pub type ListBuildersResponse = Vec; /// List builders matching structured query. Response: [ListFullBuildersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullBuildersResponse)] +#[error(serror::Error)] pub struct ListFullBuilders { #[serde(default)] pub query: BuilderQuery, @@ -67,10 +70,11 @@ pub type ListFullBuildersResponse = Vec; /// Response: [GetBuildersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildersSummaryResponse)] +#[error(serror::Error)] pub struct GetBuildersSummary {} /// Response for [GetBuildersSummary]. diff --git a/client/core/rs/src/api/read/deployment.rs b/client/core/rs/src/api/read/deployment.rs index 291a02dda..7194a1d3d 100644 --- a/client/core/rs/src/api/read/deployment.rs +++ b/client/core/rs/src/api/read/deployment.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -20,10 +20,11 @@ use super::KomodoReadRequest; /// Get a specific deployment by name or id. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentResponse)] +#[error(serror::Error)] pub struct GetDeployment { /// Id or name #[serde(alias = "id", alias = "name")] @@ -39,10 +40,11 @@ pub type GetDeploymentResponse = Deployment; /// Response: [ListDeploymentsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDeploymentsResponse)] +#[error(serror::Error)] pub struct ListDeployments { /// optional structured query to filter deployments. #[serde(default)] @@ -58,10 +60,11 @@ pub type ListDeploymentsResponse = Vec; /// Response: [ListFullDeploymentsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullDeploymentsResponse)] +#[error(serror::Error)] pub struct ListFullDeployments { /// optional structured query to filter deployments. #[serde(default)] @@ -81,10 +84,11 @@ pub type ListFullDeploymentsResponse = Vec; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentContainerResponse)] +#[error(serror::Error)] pub struct GetDeploymentContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -107,10 +111,11 @@ pub struct GetDeploymentContainerResponse { /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentLogResponse)] +#[error(serror::Error)] pub struct GetDeploymentLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -140,10 +145,11 @@ pub type GetDeploymentLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(SearchDeploymentLogResponse)] +#[error(serror::Error)] pub struct SearchDeploymentLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -175,10 +181,11 @@ pub type SearchDeploymentLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date stats. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentStatsResponse)] +#[error(serror::Error)] pub struct GetDeploymentStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -194,10 +201,11 @@ pub type GetDeploymentStatsResponse = ContainerStats; /// Response: [DeploymentActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(DeploymentActionState)] +#[error(serror::Error)] pub struct GetDeploymentActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -213,10 +221,11 @@ pub type GetDeploymentActionStateResponse = DeploymentActionState; /// Response: [GetDeploymentsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentsSummaryResponse)] +#[error(serror::Error)] pub struct GetDeploymentsSummary {} /// Response for [GetDeploymentsSummary]. @@ -243,10 +252,11 @@ pub struct GetDeploymentsSummaryResponse { /// Useful to offer suggestions. Response: [ListCommonDeploymentExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonDeploymentExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonDeploymentExtraArgs { /// optional structured query to filter deployments. #[serde(default)] diff --git a/client/core/rs/src/api/read/mod.rs b/client/core/rs/src/api/read/mod.rs index 409dc43d2..76e931d7a 100644 --- a/client/core/rs/src/api/read/mod.rs +++ b/client/core/rs/src/api/read/mod.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,7 +13,6 @@ mod permission; mod procedure; mod provider; mod repo; -mod search; mod server; mod server_template; mod stack; @@ -35,7 +34,6 @@ pub use permission::*; pub use procedure::*; pub use provider::*; pub use repo::*; -pub use search::*; pub use server::*; pub use server_template::*; pub use stack::*; @@ -60,10 +58,11 @@ pub trait KomodoReadRequest: HasResponse {} /// Response: [GetVersionResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetVersionResponse)] +#[error(serror::Error)] pub struct GetVersion {} /// Response for [GetVersion]. @@ -80,10 +79,11 @@ pub struct GetVersionResponse { /// Response: [GetCoreInfoResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetCoreInfoResponse)] +#[error(serror::Error)] pub struct GetCoreInfo {} /// Response for [GetCoreInfo]. @@ -119,10 +119,11 @@ pub struct GetCoreInfoResponse { /// - providers on the optional Server or Builder #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListGitProvidersFromConfigResponse)] +#[error(serror::Error)] pub struct ListGitProvidersFromConfig { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. @@ -143,10 +144,11 @@ pub type ListGitProvidersFromConfigResponse = Vec; /// - registries on the optional Server or Builder #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerRegistriesFromConfigResponse)] +#[error(serror::Error)] pub struct ListDockerRegistriesFromConfig { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. @@ -162,10 +164,11 @@ pub type ListDockerRegistriesFromConfigResponse = Vec; /// Response: [ListSecretsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListSecretsResponse)] +#[error(serror::Error)] pub struct ListSecrets { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. diff --git a/client/core/rs/src/api/read/permission.rs b/client/core/rs/src/api/read/permission.rs index 98cfa31cc..c812abc03 100644 --- a/client/core/rs/src/api/read/permission.rs +++ b/client/core/rs/src/api/read/permission.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoReadRequest; /// Response: [ListPermissionsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListPermissionsResponse)] +#[error(serror::Error)] pub struct ListPermissions {} #[typeshare] @@ -31,10 +32,11 @@ pub type ListPermissionsResponse = Vec; /// Response: [PermissionLevel] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetPermissionLevelResponse)] +#[error(serror::Error)] pub struct GetPermissionLevel { /// The target to get user permission on. pub target: ResourceTarget, @@ -49,10 +51,11 @@ pub type GetPermissionLevelResponse = PermissionLevel; /// Response: [ListUserTargetPermissionsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUserTargetPermissionsResponse)] +#[error(serror::Error)] pub struct ListUserTargetPermissions { /// Specify either a user or a user group. pub user_target: UserTarget, diff --git a/client/core/rs/src/api/read/procedure.rs b/client/core/rs/src/api/read/procedure.rs index 9663cd6d7..fe13edcc7 100644 --- a/client/core/rs/src/api/read/procedure.rs +++ b/client/core/rs/src/api/read/procedure.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific procedure. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProcedureResponse)] +#[error(serror::Error)] pub struct GetProcedure { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetProcedureResponse = Procedure; /// List procedures matching optional query. Response: [ListProceduresResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListProceduresResponse)] +#[error(serror::Error)] pub struct ListProcedures { /// optional structured query to filter procedures. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListProceduresResponse = Vec; /// List procedures matching optional query. Response: [ListFullProceduresResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullProceduresResponse)] +#[error(serror::Error)] pub struct ListFullProcedures { /// optional structured query to filter procedures. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullProceduresResponse = Vec; /// Get current action state for the procedure. Response: [ProcedureActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProcedureActionStateResponse)] +#[error(serror::Error)] pub struct GetProcedureActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetProcedureActionStateResponse = ProcedureActionState; /// Response: [GetProceduresSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProceduresSummaryResponse)] +#[error(serror::Error)] pub struct GetProceduresSummary {} /// Response for [GetProceduresSummary]. diff --git a/client/core/rs/src/api/read/provider.rs b/client/core/rs/src/api/read/provider.rs index fd7fb4984..c08b34dae 100644 --- a/client/core/rs/src/api/read/provider.rs +++ b/client/core/rs/src/api/read/provider.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,10 +13,11 @@ use super::KomodoReadRequest; /// Response: [GetGitProviderAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetGitProviderAccountResponse)] +#[error(serror::Error)] pub struct GetGitProviderAccount { pub id: String, } @@ -30,10 +31,11 @@ pub type GetGitProviderAccountResponse = GitProviderAccount; /// Response: [ListGitProviderAccountsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListGitProviderAccountsResponse)] +#[error(serror::Error)] pub struct ListGitProviderAccounts { /// Optionally filter by accounts with a specific domain. pub domain: Option, @@ -50,10 +52,11 @@ pub type ListGitProviderAccountsResponse = Vec; /// Response: [GetDockerRegistryAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct GetDockerRegistryAccount { pub id: String, } @@ -67,10 +70,11 @@ pub type GetDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [ListDockerRegistryAccountsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerRegistryAccountsResponse)] +#[error(serror::Error)] pub struct ListDockerRegistryAccounts { /// Optionally filter by accounts with a specific domain. pub domain: Option, diff --git a/client/core/rs/src/api/read/repo.rs b/client/core/rs/src/api/read/repo.rs index 4f8e7b5f3..16f2521ef 100644 --- a/client/core/rs/src/api/read/repo.rs +++ b/client/core/rs/src/api/read/repo.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific repo. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct GetRepo { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetRepoResponse = Repo; /// List repos matching optional query. Response: [ListReposResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListReposResponse)] +#[error(serror::Error)] pub struct ListRepos { /// optional structured query to filter repos. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListReposResponse = Vec; /// List repos matching optional query. Response: [ListFullReposResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullReposResponse)] +#[error(serror::Error)] pub struct ListFullRepos { /// optional structured query to filter repos. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullReposResponse = Vec; /// Get current action state for the repo. Response: [RepoActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetRepoActionStateResponse)] +#[error(serror::Error)] pub struct GetRepoActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetRepoActionStateResponse = RepoActionState; /// Response: [GetReposSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetReposSummaryResponse)] +#[error(serror::Error)] pub struct GetReposSummary {} /// Response for [GetReposSummary] @@ -118,10 +123,11 @@ pub struct GetReposSummaryResponse { /// Get a target Repo's configured webhooks. Response: [GetRepoWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetRepoWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetRepoWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/search.rs b/client/core/rs/src/api/read/search.rs deleted file mode 100644 index 67f1e2f77..000000000 --- a/client/core/rs/src/api/read/search.rs +++ /dev/null @@ -1,46 +0,0 @@ -use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -use crate::entities::{ - build::BuildListItem, deployment::DeploymentListItem, - procedure::ProcedureListItem, repo::RepoListItem, - server::ServerListItem, MongoDocument, ResourceTargetVariant, -}; - -use super::KomodoReadRequest; - -// - -/// Find resources matching a common query. Response: [FindResourcesResponse]. -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, -)] -#[empty_traits(KomodoReadRequest)] -#[response(FindResourcesResponse)] -pub struct FindResources { - /// The mongo query as JSON - #[serde(default)] - pub query: MongoDocument, - /// The resource variants to include in the response. - #[serde(default)] - pub resources: Vec, -} - -/// Response for [FindResources]. -#[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct FindResourcesResponse { - /// The matching servers. - pub servers: Vec, - /// The matching deployments. - pub deployments: Vec, - /// The matching builds. - pub builds: Vec, - /// The matching repos. - pub repos: Vec, - /// The matching procedures. - pub procedures: Vec, -} diff --git a/client/core/rs/src/api/read/server.rs b/client/core/rs/src/api/read/server.rs index a9af84e17..3ab08fbf9 100644 --- a/client/core/rs/src/api/read/server.rs +++ b/client/core/rs/src/api/read/server.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -29,10 +29,11 @@ use super::KomodoReadRequest; /// Get a specific server. Response: [Server]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(Server)] +#[error(serror::Error)] pub struct GetServer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -47,10 +48,11 @@ pub type GetServerResponse = Server; /// List servers matching optional query. Response: [ListServersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListServersResponse)] +#[error(serror::Error)] pub struct ListServers { /// optional structured query to filter servers. #[serde(default)] @@ -65,10 +67,11 @@ pub type ListServersResponse = Vec; /// List servers matching optional query. Response: [ListFullServersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullServersResponse)] +#[error(serror::Error)] pub struct ListFullServers { /// optional structured query to filter servers. #[serde(default)] @@ -83,10 +86,11 @@ pub type ListFullServersResponse = Vec; /// Get the state of the target server. Response: [GetServerStateResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerStateResponse)] +#[error(serror::Error)] pub struct GetServerState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -106,10 +110,11 @@ pub struct GetServerStateResponse { /// Get current action state for the servers. Response: [ServerActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ServerActionState)] +#[error(serror::Error)] pub struct GetServerActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -125,10 +130,11 @@ pub type GetServerActionStateResponse = ServerActionState; /// Response: [GetPeripheryVersionResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetPeripheryVersionResponse)] +#[error(serror::Error)] pub struct GetPeripheryVersion { /// Id or name #[serde(alias = "id", alias = "name")] @@ -148,10 +154,11 @@ pub struct GetPeripheryVersionResponse { /// List the docker networks on the server. Response: [ListDockerNetworksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerNetworksResponse)] +#[error(serror::Error)] pub struct ListDockerNetworks { /// Id or name #[serde(alias = "id", alias = "name")] @@ -166,10 +173,11 @@ pub type ListDockerNetworksResponse = Vec; /// Inspect a docker network on the server. Response: [InspectDockerNetworkResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerNetworkResponse)] +#[error(serror::Error)] pub struct InspectDockerNetwork { /// Id or name #[serde(alias = "id", alias = "name")] @@ -187,10 +195,11 @@ pub type InspectDockerNetworkResponse = Network; /// Response: [ListDockerImagesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerImagesResponse)] +#[error(serror::Error)] pub struct ListDockerImages { /// Id or name #[serde(alias = "id", alias = "name")] @@ -205,10 +214,11 @@ pub type ListDockerImagesResponse = Vec; /// Inspect a docker image on the server. Response: [Image]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerImageResponse)] +#[error(serror::Error)] pub struct InspectDockerImage { /// Id or name #[serde(alias = "id", alias = "name")] @@ -225,10 +235,11 @@ pub type InspectDockerImageResponse = Image; /// Get image history from the server. Response: [ListDockerImageHistoryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerImageHistoryResponse)] +#[error(serror::Error)] pub struct ListDockerImageHistory { /// Id or name #[serde(alias = "id", alias = "name")] @@ -247,10 +258,11 @@ pub type ListDockerImageHistoryResponse = /// Response: [ListDockerContainersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerContainersResponse)] +#[error(serror::Error)] pub struct ListDockerContainers { /// Id or name #[serde(alias = "id", alias = "name")] @@ -266,10 +278,11 @@ pub type ListDockerContainersResponse = Vec; /// Response: [ListDockerContainersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAllDockerContainersResponse)] +#[error(serror::Error)] pub struct ListAllDockerContainers { /// Filter by server id or name. #[serde(default)] @@ -284,10 +297,11 @@ pub type ListAllDockerContainersResponse = Vec; /// Inspect a docker container on the server. Response: [Container]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerContainerResponse)] +#[error(serror::Error)] pub struct InspectDockerContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -307,10 +321,11 @@ pub type InspectDockerContainerResponse = Container; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetContainerLogResponse)] +#[error(serror::Error)] pub struct GetContainerLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -342,10 +357,11 @@ pub type GetContainerLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(SearchContainerLogResponse)] +#[error(serror::Error)] pub struct SearchContainerLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -376,10 +392,11 @@ pub type SearchContainerLogResponse = Log; /// Find the attached resource for a container. Either Deployment or Stack. Response: [GetResourceMatchingContainerResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceMatchingContainerResponse)] +#[error(serror::Error)] pub struct GetResourceMatchingContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -401,10 +418,11 @@ pub struct GetResourceMatchingContainerResponse { /// Response: [ListDockerVolumesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerVolumesResponse)] +#[error(serror::Error)] pub struct ListDockerVolumes { /// Id or name #[serde(alias = "id", alias = "name")] @@ -419,10 +437,11 @@ pub type ListDockerVolumesResponse = Vec; /// Inspect a docker volume on the server. Response: [Volume]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerVolumeResponse)] +#[error(serror::Error)] pub struct InspectDockerVolume { /// Id or name #[serde(alias = "id", alias = "name")] @@ -440,10 +459,11 @@ pub type InspectDockerVolumeResponse = Volume; /// Response: [ListComposeProjectsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListComposeProjectsResponse)] +#[error(serror::Error)] pub struct ListComposeProjects { /// Id or name #[serde(alias = "id", alias = "name")] @@ -459,10 +479,11 @@ pub type ListComposeProjectsResponse = Vec; /// Response: [SystemInformation]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSystemInformationResponse)] +#[error(serror::Error)] pub struct GetSystemInformation { /// Id or name #[serde(alias = "id", alias = "name")] @@ -481,10 +502,11 @@ pub type GetSystemInformationResponse = SystemInformation; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSystemStatsResponse)] +#[error(serror::Error)] pub struct GetSystemStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -504,10 +526,11 @@ pub type GetSystemStatsResponse = SystemStats; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListSystemProcessesResponse)] +#[error(serror::Error)] pub struct ListSystemProcesses { /// Id or name #[serde(alias = "id", alias = "name")] @@ -523,10 +546,11 @@ pub type ListSystemProcessesResponse = Vec; /// Response: [GetHistoricalServerStatsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetHistoricalServerStatsResponse)] +#[error(serror::Error)] pub struct GetHistoricalServerStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -555,10 +579,11 @@ pub struct GetHistoricalServerStatsResponse { /// Response: [GetServersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServersSummaryResponse)] +#[error(serror::Error)] pub struct GetServersSummary {} /// Response for [GetServersSummary]. diff --git a/client/core/rs/src/api/read/server_template.rs b/client/core/rs/src/api/read/server_template.rs index 2a52d1ff5..cd0be5b32 100644 --- a/client/core/rs/src/api/read/server_template.rs +++ b/client/core/rs/src/api/read/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific server template by id or name. Response: [ServerTemplate]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerTemplateResponse)] +#[error(serror::Error)] pub struct GetServerTemplate { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetServerTemplateResponse = ServerTemplate; /// List server templates matching structured query. Response: [ListServerTemplatesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListServerTemplatesResponse)] +#[error(serror::Error)] pub struct ListServerTemplates { #[serde(default)] pub query: ServerTemplateQuery, @@ -49,10 +51,11 @@ pub type ListServerTemplatesResponse = Vec; /// List server templates matching structured query. Response: [ListFullServerTemplatesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullServerTemplatesResponse)] +#[error(serror::Error)] pub struct ListFullServerTemplates { #[serde(default)] pub query: ServerTemplateQuery, @@ -67,10 +70,11 @@ pub type ListFullServerTemplatesResponse = Vec; /// Response: [GetServerTemplatesSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerTemplatesSummaryResponse)] +#[error(serror::Error)] pub struct GetServerTemplatesSummary {} /// Response for [GetServerTemplatesSummary]. diff --git a/client/core/rs/src/api/read/stack.rs b/client/core/rs/src/api/read/stack.rs index 3c3be1c6d..1a49beb7a 100644 --- a/client/core/rs/src/api/read/stack.rs +++ b/client/core/rs/src/api/read/stack.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -18,10 +18,11 @@ use super::KomodoReadRequest; /// Get a specific stack. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackResponse)] +#[error(serror::Error)] pub struct GetStack { /// Id or name #[serde(alias = "id", alias = "name")] @@ -36,10 +37,11 @@ pub type GetStackResponse = Stack; /// Lists a specific stacks services (the containers). Response: [ListStackServicesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListStackServicesResponse)] +#[error(serror::Error)] pub struct ListStackServices { /// Id or name #[serde(alias = "id", alias = "name")] @@ -54,10 +56,11 @@ pub type ListStackServicesResponse = Vec; /// Get a stack service's log. Response: [GetStackServiceLogResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackServiceLogResponse)] +#[error(serror::Error)] pub struct GetStackServiceLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -89,10 +92,11 @@ pub type GetStackServiceLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(SearchStackServiceLogResponse)] +#[error(serror::Error)] pub struct SearchStackServiceLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -124,10 +128,11 @@ pub type SearchStackServiceLogResponse = Log; /// Useful to offer suggestions. Response: [ListCommonStackExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonStackExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonStackExtraArgs { /// optional structured query to filter stacks. #[serde(default)] @@ -143,10 +148,11 @@ pub type ListCommonStackExtraArgsResponse = Vec; /// Useful to offer suggestions. Response: [ListCommonStackBuildExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonStackBuildExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonStackBuildExtraArgs { /// optional structured query to filter stacks. #[serde(default)] @@ -161,10 +167,11 @@ pub type ListCommonStackBuildExtraArgsResponse = Vec; /// List stacks matching optional query. Response: [ListStacksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListStacksResponse)] +#[error(serror::Error)] pub struct ListStacks { /// optional structured query to filter syncs. #[serde(default)] @@ -179,10 +186,11 @@ pub type ListStacksResponse = Vec; /// List stacks matching optional query. Response: [ListFullStacksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullStacksResponse)] +#[error(serror::Error)] pub struct ListFullStacks { /// optional structured query to filter stacks. #[serde(default)] @@ -197,10 +205,11 @@ pub type ListFullStacksResponse = Vec; /// Get current action state for the stack. Response: [StackActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackActionStateResponse)] +#[error(serror::Error)] pub struct GetStackActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -216,10 +225,11 @@ pub type GetStackActionStateResponse = StackActionState; /// Response: [GetStacksSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStacksSummaryResponse)] +#[error(serror::Error)] pub struct GetStacksSummary {} /// Response for [GetStacksSummary] @@ -245,10 +255,11 @@ pub struct GetStacksSummaryResponse { /// Get a target stack's configured webhooks. Response: [GetStackWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetStackWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/sync.rs b/client/core/rs/src/api/read/sync.rs index a49f3bd17..54623c887 100644 --- a/client/core/rs/src/api/read/sync.rs +++ b/client/core/rs/src/api/read/sync.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoReadRequest; /// Get a specific sync. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct GetResourceSync { /// Id or name #[serde(alias = "id", alias = "name")] @@ -33,10 +34,11 @@ pub type GetResourceSyncResponse = ResourceSync; /// List syncs matching optional query. Response: [ListResourceSyncsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListResourceSyncsResponse)] +#[error(serror::Error)] pub struct ListResourceSyncs { /// optional structured query to filter syncs. #[serde(default)] @@ -51,10 +53,11 @@ pub type ListResourceSyncsResponse = Vec; /// List syncs matching optional query. Response: [ListFullResourceSyncsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullResourceSyncsResponse)] +#[error(serror::Error)] pub struct ListFullResourceSyncs { /// optional structured query to filter syncs. #[serde(default)] @@ -69,10 +72,11 @@ pub type ListFullResourceSyncsResponse = Vec; /// Get current action state for the sync. Response: [ResourceSyncActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceSyncActionStateResponse)] +#[error(serror::Error)] pub struct GetResourceSyncActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -88,10 +92,11 @@ pub type GetResourceSyncActionStateResponse = ResourceSyncActionState; /// Response: [GetResourceSyncsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceSyncsSummaryResponse)] +#[error(serror::Error)] pub struct GetResourceSyncsSummary {} /// Response for [GetResourceSyncsSummary] @@ -117,10 +122,11 @@ pub struct GetResourceSyncsSummaryResponse { /// Get a target Sync's configured webhooks. Response: [GetSyncWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSyncWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetSyncWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/tag.rs b/client/core/rs/src/api/read/tag.rs index 169b703c2..f90a101a4 100644 --- a/client/core/rs/src/api/read/tag.rs +++ b/client/core/rs/src/api/read/tag.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -12,10 +12,11 @@ use super::KomodoReadRequest; /// Get data for a specific tag. Response [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetTagResponse)] +#[error(serror::Error)] pub struct GetTag { /// Id or name #[serde(alias = "id", alias = "name")] @@ -31,10 +32,11 @@ pub type GetTagResponse = Tag; /// Response: [ListTagsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListTagsResponse)] +#[error(serror::Error)] pub struct ListTags { pub query: Option, } diff --git a/client/core/rs/src/api/read/toml.rs b/client/core/rs/src/api/read/toml.rs index afcef36b3..16f037a6c 100644 --- a/client/core/rs/src/api/read/toml.rs +++ b/client/core/rs/src/api/read/toml.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -21,10 +21,11 @@ pub struct TomlResponse { /// Response: [TomlResponse]. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ExportAllResourcesToTomlResponse)] +#[error(serror::Error)] pub struct ExportAllResourcesToToml { /// Tag name or id. Empty array will not filter by tag. #[serde(default)] @@ -40,10 +41,11 @@ pub type ExportAllResourcesToTomlResponse = TomlResponse; /// Response: [TomlResponse]. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ExportResourcesToTomlResponse)] +#[error(serror::Error)] pub struct ExportResourcesToToml { /// The targets to include in the export. #[serde(default)] diff --git a/client/core/rs/src/api/read/update.rs b/client/core/rs/src/api/read/update.rs index f153a871f..e1f9a0e05 100644 --- a/client/core/rs/src/api/read/update.rs +++ b/client/core/rs/src/api/read/update.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUpdateResponse)] +#[error(serror::Error)] pub struct GetUpdate { /// The update id. pub id: String, @@ -32,10 +33,11 @@ pub type GetUpdateResponse = Update; /// More recent updates will be returned first. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUpdatesResponse)] +#[error(serror::Error)] pub struct ListUpdates { /// An optional mongo query to filter the updates. pub query: Option, diff --git a/client/core/rs/src/api/read/user.rs b/client/core/rs/src/api/read/user.rs index dbf51b957..bd9f73154 100644 --- a/client/core/rs/src/api/read/user.rs +++ b/client/core/rs/src/api/read/user.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [ListApiKeysResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListApiKeysResponse)] +#[error(serror::Error)] pub struct ListApiKeys {} #[typeshare] @@ -28,10 +29,11 @@ pub type ListApiKeysResponse = Vec; /// Response: [ListApiKeysForServiceUserResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListApiKeysForServiceUserResponse)] +#[error(serror::Error)] pub struct ListApiKeysForServiceUser { /// Id or username #[serde(alias = "id", alias = "username")] @@ -48,10 +50,11 @@ pub type ListApiKeysForServiceUserResponse = Vec; /// Response: [FindUserResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(FindUserResponse)] +#[error(serror::Error)] pub struct FindUser { /// Id or username #[serde(alias = "id", alias = "username")] @@ -68,10 +71,11 @@ pub type FindUserResponse = User; /// Response: [ListUsersResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUsersResponse)] +#[error(serror::Error)] pub struct ListUsers {} #[typeshare] @@ -83,10 +87,11 @@ pub type ListUsersResponse = Vec; /// Response: [GetUsernameResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUsernameResponse)] +#[error(serror::Error)] pub struct GetUsername { /// The id of the user. pub user_id: String, diff --git a/client/core/rs/src/api/read/user_group.rs b/client/core/rs/src/api/read/user_group.rs index 76103fa3b..9188fba32 100644 --- a/client/core/rs/src/api/read/user_group.rs +++ b/client/core/rs/src/api/read/user_group.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [UserGroup]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUserGroupResponse)] +#[error(serror::Error)] pub struct GetUserGroup { /// Name or Id pub user_group: String, @@ -31,10 +32,11 @@ pub type GetUserGroupResponse = UserGroup; /// and users can see user groups to which they belong. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUserGroupsResponse)] +#[error(serror::Error)] pub struct ListUserGroups {} #[typeshare] diff --git a/client/core/rs/src/api/read/variable.rs b/client/core/rs/src/api/read/variable.rs index d034f073c..5ec082758 100644 --- a/client/core/rs/src/api/read/variable.rs +++ b/client/core/rs/src/api/read/variable.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// secret variables will have their values obscured. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetVariableResponse)] +#[error(serror::Error)] pub struct GetVariable { /// The name of the variable to get. pub name: String, @@ -35,10 +36,11 @@ pub type GetVariableResponse = Variable; /// secret variables will have their values obscured. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListVariablesResponse)] +#[error(serror::Error)] pub struct ListVariables {} #[typeshare] diff --git a/client/core/rs/src/api/user.rs b/client/core/rs/src/api/user.rs index 3c18e5de5..d130275a3 100644 --- a/client/core/rs/src/api/user.rs +++ b/client/core/rs/src/api/user.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,10 +13,11 @@ pub trait KomodoUserRequest: HasResponse {} /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(PushRecentlyViewedResponse)] +#[error(serror::Error)] pub struct PushRecentlyViewed { /// The target to push. pub resource: ResourceTarget, @@ -32,10 +33,11 @@ pub type PushRecentlyViewedResponse = NoData; /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(SetLastSeenUpdateResponse)] +#[error(serror::Error)] pub struct SetLastSeenUpdate {} #[typeshare] @@ -50,10 +52,11 @@ pub type SetLastSeenUpdateResponse = NoData; /// to get the secret later. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(CreateApiKeyResponse)] +#[error(serror::Error)] pub struct CreateApiKey { /// The name for the api key. pub name: String, @@ -84,10 +87,11 @@ pub struct CreateApiKeyResponse { /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(DeleteApiKeyResponse)] +#[error(serror::Error)] pub struct DeleteApiKey { /// The key which the user intends to delete. pub key: String, diff --git a/client/core/rs/src/api/write/action.rs b/client/core/rs/src/api/write/action.rs index 47768ad43..f10fdba38 100644 --- a/client/core/rs/src/api/write/action.rs +++ b/client/core/rs/src/api/write/action.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a action. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct CreateAction { /// The name given to newly created action. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateAction { /// of the action at the given `id`. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct CopyAction { /// The name of the new action. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyAction { /// Response: [Action] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct DeleteAction { /// The id or name of the action to delete. pub id: String, @@ -72,10 +75,11 @@ pub struct DeleteAction { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct UpdateAction { /// The id of the action to update. pub id: String, @@ -89,10 +93,12 @@ pub struct UpdateAction { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameAction { /// The id or name of the Action to rename. pub id: String, @@ -104,10 +110,11 @@ pub struct RenameAction { /// passed in request. Response: [CreateActionWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateActionWebhookResponse)] +#[error(serror::Error)] pub struct CreateActionWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -123,10 +130,11 @@ pub type CreateActionWebhookResponse = NoData; /// passed in request. Response: [DeleteActionWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteActionWebhookResponse)] +#[error(serror::Error)] pub struct DeleteActionWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/alerter.rs b/client/core/rs/src/api/write/alerter.rs index 7072f813b..e983c2754 100644 --- a/client/core/rs/src/api/write/alerter.rs +++ b/client/core/rs/src/api/write/alerter.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create an alerter. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct CreateAlerter { /// The name given to newly created alerter. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateAlerter { /// of the alerter at the given `id`. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct CopyAlerter { /// The name of the new alerter. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyAlerter { /// Response: [Alerter] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct DeleteAlerter { /// The id or name of the alerter to delete. pub id: String, @@ -69,10 +72,11 @@ pub struct DeleteAlerter { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct UpdateAlerter { /// The id of the alerter to update. pub id: String, @@ -86,10 +90,12 @@ pub struct UpdateAlerter { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameAlerter { /// The id or name of the Alerter to rename. pub id: String, diff --git a/client/core/rs/src/api/write/api_key.rs b/client/core/rs/src/api/write/api_key.rs index 95a016d20..0317a622f 100644 --- a/client/core/rs/src/api/write/api_key.rs +++ b/client/core/rs/src/api/write/api_key.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Response: [CreateApiKeyResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateApiKeyForServiceUserResponse)] +#[error(serror::Error)] pub struct CreateApiKeyForServiceUser { /// Must be service user pub user_id: String, @@ -40,10 +41,11 @@ pub type CreateApiKeyForServiceUserResponse = CreateApiKeyResponse; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteApiKeyForServiceUserResponse)] +#[error(serror::Error)] pub struct DeleteApiKeyForServiceUser { pub key: String, } diff --git a/client/core/rs/src/api/write/build.rs b/client/core/rs/src/api/write/build.rs index c293787b5..c81c08d7c 100644 --- a/client/core/rs/src/api/write/build.rs +++ b/client/core/rs/src/api/write/build.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a build. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct CreateBuild { /// The name given to newly created build. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateBuild { /// of the build at the given `id`. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct CopyBuild { /// The name of the new build. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyBuild { /// Response: [Build] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct DeleteBuild { /// The id or name of the build to delete. pub id: String, @@ -72,10 +75,11 @@ pub struct DeleteBuild { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct UpdateBuild { /// The id of the build to update. pub id: String, @@ -89,10 +93,12 @@ pub struct UpdateBuild { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameBuild { /// The id or name of the Build to rename. pub id: String, @@ -105,10 +111,11 @@ pub struct RenameBuild { /// Trigger a refresh of the cached latest hash and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshBuildCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -121,10 +128,11 @@ pub struct RefreshBuildCache { /// passed in request. Response: [CreateBuildWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateBuildWebhookResponse)] +#[error(serror::Error)] pub struct CreateBuildWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -140,10 +148,11 @@ pub type CreateBuildWebhookResponse = NoData; /// passed in request. Response: [CreateBuildWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteBuildWebhookResponse)] +#[error(serror::Error)] pub struct DeleteBuildWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/builder.rs b/client/core/rs/src/api/write/builder.rs index f929946a9..5b5e3a2b7 100644 --- a/client/core/rs/src/api/write/builder.rs +++ b/client/core/rs/src/api/write/builder.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a builder. Response: [Builder]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct CreateBuilder { /// The name given to newly created builder. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateBuilder { /// of the builder at the given `id`. Response: [Builder] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct CopyBuilder { /// The name of the new builder. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyBuilder { /// Response: [Builder] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct DeleteBuilder { /// The id or name of the builder to delete. pub id: String, @@ -71,10 +74,11 @@ pub struct DeleteBuilder { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct UpdateBuilder { /// The id of the builder to update. pub id: String, @@ -88,10 +92,12 @@ pub struct UpdateBuilder { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameBuilder { /// The id or name of the Builder to rename. pub id: String, diff --git a/client/core/rs/src/api/write/deployment.rs b/client/core/rs/src/api/write/deployment.rs index b7c6e6505..a62a1e3b6 100644 --- a/client/core/rs/src/api/write/deployment.rs +++ b/client/core/rs/src/api/write/deployment.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a deployment. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CreateDeployment { /// The name given to newly created deployment. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateDeployment { /// of the deployment at the given `id`. Response: [Deployment] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CopyDeployment { /// The name of the new deployment. pub name: String, @@ -49,10 +51,11 @@ pub struct CopyDeployment { /// Create a Deployment from an existing container. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CreateDeploymentFromContainer { /// The name or id of the existing container. pub name: String, @@ -69,10 +72,11 @@ pub struct CreateDeploymentFromContainer { /// the deployment clean up. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct DeleteDeployment { /// The id or name of the deployment to delete. pub id: String, @@ -93,10 +97,11 @@ pub struct DeleteDeployment { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct UpdateDeployment { /// The deployment id to update. pub id: String, @@ -112,10 +117,12 @@ pub struct UpdateDeployment { /// `docker rename ...`. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameDeployment { /// The id of the deployment to rename. pub id: String, diff --git a/client/core/rs/src/api/write/description.rs b/client/core/rs/src/api/write/description.rs index eb99542bd..7d88c27f5 100644 --- a/client/core/rs/src/api/write/description.rs +++ b/client/core/rs/src/api/write/description.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateDescription { /// The target resource to set description for. pub target: ResourceTarget, diff --git a/client/core/rs/src/api/write/permissions.rs b/client/core/rs/src/api/write/permissions.rs index 19dc28e00..5c710eda5 100644 --- a/client/core/rs/src/api/write/permissions.rs +++ b/client/core/rs/src/api/write/permissions.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdatePermissionOnTargetResponse)] +#[error(serror::Error)] pub struct UpdatePermissionOnTarget { /// Specify the user or user group. pub user_target: UserTarget, @@ -36,10 +37,11 @@ pub type UpdatePermissionOnTargetResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdatePermissionOnResourceTypeResponse)] +#[error(serror::Error)] pub struct UpdatePermissionOnResourceType { /// Specify the user or user group. pub user_target: UserTarget, @@ -58,10 +60,11 @@ pub type UpdatePermissionOnResourceTypeResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserBasePermissionsResponse)] +#[error(serror::Error)] pub struct UpdateUserBasePermissions { /// The target user. pub user_id: String, @@ -80,10 +83,11 @@ pub type UpdateUserBasePermissionsResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserAdminResponse)] +#[error(serror::Error)] pub struct UpdateUserAdmin { /// The target user. pub user_id: String, diff --git a/client/core/rs/src/api/write/procedure.rs b/client/core/rs/src/api/write/procedure.rs index e818c844a..7fe423eb9 100644 --- a/client/core/rs/src/api/write/procedure.rs +++ b/client/core/rs/src/api/write/procedure.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a procedure. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateProcedureResponse)] +#[error(serror::Error)] pub struct CreateProcedure { /// The name given to newly created build. pub name: String, @@ -36,10 +37,11 @@ pub type CreateProcedureResponse = Procedure; /// of the procedure at the given `id`. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CopyProcedureResponse)] +#[error(serror::Error)] pub struct CopyProcedure { /// The name of the new procedure. pub name: String, @@ -56,10 +58,11 @@ pub type CopyProcedureResponse = Procedure; /// Response: [Procedure] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteProcedureResponse)] +#[error(serror::Error)] pub struct DeleteProcedure { /// The id or name of the procedure to delete. pub id: String, @@ -80,10 +83,11 @@ pub type DeleteProcedureResponse = Procedure; /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateProcedureResponse)] +#[error(serror::Error)] pub struct UpdateProcedure { /// The id of the procedure to update. pub id: String, @@ -100,10 +104,12 @@ pub type UpdateProcedureResponse = Procedure; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameProcedure { /// The id or name of the Procedure to rename. pub id: String, diff --git a/client/core/rs/src/api/write/provider.rs b/client/core/rs/src/api/write/provider.rs index f3e99a6ad..415618195 100644 --- a/client/core/rs/src/api/write/provider.rs +++ b/client/core/rs/src/api/write/provider.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoWriteRequest; /// Response: [GitProviderAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateGitProviderAccountResponse)] +#[error(serror::Error)] pub struct CreateGitProviderAccount { /// The initial account config. Anything in the _id field will be ignored, /// as this is generated on creation. @@ -30,10 +31,11 @@ pub type CreateGitProviderAccountResponse = GitProviderAccount; /// Response: [GitProviderAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateGitProviderAccountResponse)] +#[error(serror::Error)] pub struct UpdateGitProviderAccount { /// The id of the git provider account to update. pub id: String, @@ -50,10 +52,11 @@ pub type UpdateGitProviderAccountResponse = GitProviderAccount; /// Response: [DeleteGitProviderAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteGitProviderAccountResponse)] +#[error(serror::Error)] pub struct DeleteGitProviderAccount { /// The id of the git provider to delete pub id: String, @@ -68,10 +71,11 @@ pub type DeleteGitProviderAccountResponse = GitProviderAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct CreateDockerRegistryAccount { pub account: _PartialDockerRegistryAccount, } @@ -85,10 +89,11 @@ pub type CreateDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct UpdateDockerRegistryAccount { /// The id of the docker registry to update pub id: String, @@ -105,10 +110,11 @@ pub type UpdateDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct DeleteDockerRegistryAccount { /// The id of the docker registry account to delete pub id: String, diff --git a/client/core/rs/src/api/write/repo.rs b/client/core/rs/src/api/write/repo.rs index 3008fc9f6..ec08c43a0 100644 --- a/client/core/rs/src/api/write/repo.rs +++ b/client/core/rs/src/api/write/repo.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a repo. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct CreateRepo { /// The name given to newly created repo. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateRepo { /// of the repo at the given `id`. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct CopyRepo { /// The name of the new repo. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyRepo { /// Response: [Repo] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct DeleteRepo { /// The id or name of the repo to delete. pub id: String, @@ -75,10 +78,11 @@ pub struct DeleteRepo { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct UpdateRepo { /// The id of the repo to update. pub id: String, @@ -92,10 +96,12 @@ pub struct UpdateRepo { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameRepo { /// The id or name of the Repo to rename. pub id: String, @@ -108,10 +114,11 @@ pub struct RenameRepo { /// Trigger a refresh of the cached latest hash and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshRepoCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -132,10 +139,11 @@ pub enum RepoWebhookAction { /// passed in request. Response: [CreateRepoWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateRepoWebhookResponse)] +#[error(serror::Error)] pub struct CreateRepoWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -153,10 +161,11 @@ pub type CreateRepoWebhookResponse = NoData; /// passed in request. Response: [DeleteRepoWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteRepoWebhookResponse)] +#[error(serror::Error)] pub struct DeleteRepoWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/server.rs b/client/core/rs/src/api/write/server.rs index f4dce6ece..d36727be2 100644 --- a/client/core/rs/src/api/write/server.rs +++ b/client/core/rs/src/api/write/server.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a server. Response: [Server]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct CreateServer { /// The name given to newly created server. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateServer { /// Response: [Server] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct DeleteServer { /// The id or name of the server to delete. pub id: String, @@ -54,10 +56,11 @@ pub struct DeleteServer { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct UpdateServer { /// The id or name of the server to update. pub id: String, @@ -71,10 +74,12 @@ pub struct UpdateServer { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameServer { /// The id or name of the Server to rename. pub id: String, @@ -90,10 +95,12 @@ pub struct RenameServer { /// `docker network create {name}` #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct CreateNetwork { /// Server Id or name pub server: String, diff --git a/client/core/rs/src/api/write/server_template.rs b/client/core/rs/src/api/write/server_template.rs index 5ddc4b21f..59e8e2346 100644 --- a/client/core/rs/src/api/write/server_template.rs +++ b/client/core/rs/src/api/write/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a server template. Response: [ServerTemplate]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct CreateServerTemplate { /// The name given to newly created server template. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateServerTemplate { /// of the server template at the given `id`. Response: [ServerTemplate] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct CopyServerTemplate { /// The name of the new server template. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyServerTemplate { /// Response: [ServerTemplate] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct DeleteServerTemplate { /// The id or name of the server template to delete. pub id: String, @@ -71,10 +74,11 @@ pub struct DeleteServerTemplate { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct UpdateServerTemplate { /// The id of the server template to update. pub id: String, @@ -88,10 +92,12 @@ pub struct UpdateServerTemplate { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameServerTemplate { /// The id or name of the ServerTemplate to rename. pub id: String, diff --git a/client/core/rs/src/api/write/stack.rs b/client/core/rs/src/api/write/stack.rs index 8cbd0e1ca..587c3b36e 100644 --- a/client/core/rs/src/api/write/stack.rs +++ b/client/core/rs/src/api/write/stack.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a stack. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct CreateStack { /// The name given to newly created stack. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateStack { /// of the stack at the given `id`. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct CopyStack { /// The name of the new stack. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyStack { /// Response: [Stack] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct DeleteStack { /// The id or name of the stack to delete. pub id: String, @@ -75,10 +78,11 @@ pub struct DeleteStack { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct UpdateStack { /// The id of the Stack to update. pub id: String, @@ -91,10 +95,12 @@ pub struct UpdateStack { /// Rename the stack at id to the given name. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameStack { /// The id of the stack to rename. pub id: String, @@ -107,10 +113,12 @@ pub struct RenameStack { /// Update file contents in Files on Server or Git Repo mode. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct WriteStackFileContents { /// The name or id of the target Stack. #[serde(alias = "id", alias = "name")] @@ -130,10 +138,11 @@ pub struct WriteStackFileContents { /// - The latest json, and for repos, the remote contents, hash, and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshStackCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -153,10 +162,11 @@ pub enum StackWebhookAction { /// passed in request. Response: [CreateStackWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateStackWebhookResponse)] +#[error(serror::Error)] pub struct CreateStackWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -174,10 +184,11 @@ pub type CreateStackWebhookResponse = NoData; /// passed in request. Response: [DeleteStackWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteStackWebhookResponse)] +#[error(serror::Error)] pub struct DeleteStackWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/sync.rs b/client/core/rs/src/api/write/sync.rs index 04816e7bc..2389f1b64 100644 --- a/client/core/rs/src/api/write/sync.rs +++ b/client/core/rs/src/api/write/sync.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -17,10 +17,11 @@ use super::KomodoWriteRequest; /// Create a sync. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct CreateResourceSync { /// The name given to newly created sync. pub name: String, @@ -35,10 +36,11 @@ pub struct CreateResourceSync { /// of the sync at the given `id`. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct CopyResourceSync { /// The name of the new sync. pub name: String, @@ -52,10 +54,11 @@ pub struct CopyResourceSync { /// Response: [ResourceSync] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct DeleteResourceSync { /// The id or name of the sync to delete. pub id: String, @@ -73,10 +76,11 @@ pub struct DeleteResourceSync { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct UpdateResourceSync { /// The id of the sync to update. pub id: String, @@ -90,10 +94,12 @@ pub struct UpdateResourceSync { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameResourceSync { /// The id or name of the ResourceSync to rename. pub id: String, @@ -106,10 +112,11 @@ pub struct RenameResourceSync { /// Trigger a refresh of the computed diff logs for view. Response: [ResourceSync] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct RefreshResourceSyncPending { /// Id or name #[serde(alias = "id", alias = "name")] @@ -121,10 +128,12 @@ pub struct RefreshResourceSyncPending { /// Rename the stack at id to the given name. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct WriteSyncFileContents { /// The name or id of the target Sync. #[serde(alias = "id", alias = "name")] @@ -150,12 +159,14 @@ pub struct WriteSyncFileContents { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct CommitSync { /// Id or name #[serde(alias = "id", alias = "name")] @@ -175,10 +186,11 @@ pub enum SyncWebhookAction { /// passed in request. Response: [CreateSyncWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateSyncWebhookResponse)] +#[error(serror::Error)] pub struct CreateSyncWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -196,10 +208,11 @@ pub type CreateSyncWebhookResponse = NoData; /// passed in request. Response: [DeleteSyncWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteSyncWebhookResponse)] +#[error(serror::Error)] pub struct DeleteSyncWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/tags.rs b/client/core/rs/src/api/write/tags.rs index 2e5baa4d5..fcefd57cc 100644 --- a/client/core/rs/src/api/write/tags.rs +++ b/client/core/rs/src/api/write/tags.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -12,10 +12,11 @@ use super::KomodoWriteRequest; /// Create a tag. Response: [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct CreateTag { /// The name of the tag. pub name: String, @@ -28,10 +29,11 @@ pub struct CreateTag { /// Note. Will also remove this tag from all attached resources. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct DeleteTag { /// The id of the tag to delete. pub id: String, @@ -42,10 +44,11 @@ pub struct DeleteTag { /// Rename a tag at id. Response: [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct RenameTag { /// The id of the tag to rename. pub id: String, @@ -59,10 +62,11 @@ pub struct RenameTag { /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateTagsOnResourceResponse)] +#[error(serror::Error)] pub struct UpdateTagsOnResource { pub target: ResourceTarget, /// Tag Ids diff --git a/client/core/rs/src/api/write/user.rs b/client/core/rs/src/api/write/user.rs index 00e647e41..442e25960 100644 --- a/client/core/rs/src/api/write/user.rs +++ b/client/core/rs/src/api/write/user.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,10 +13,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserUsernameResponse)] +#[error(serror::Error)] pub struct UpdateUserUsername { pub username: String, } @@ -30,10 +31,11 @@ pub type UpdateUserUsernameResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserPasswordResponse)] +#[error(serror::Error)] pub struct UpdateUserPassword { pub password: String, } @@ -51,10 +53,11 @@ pub type UpdateUserPasswordResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteUserResponse)] +#[error(serror::Error)] pub struct DeleteUser { /// User id or username #[serde(alias = "username", alias = "id")] @@ -70,10 +73,11 @@ pub type DeleteUserResponse = User; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateServiceUserResponse)] +#[error(serror::Error)] pub struct CreateServiceUser { /// The username for the service user. pub username: String, @@ -90,10 +94,11 @@ pub type CreateServiceUserResponse = User; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateServiceUserDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateServiceUserDescription { /// The service user's username pub username: String, diff --git a/client/core/rs/src/api/write/user_group.rs b/client/core/rs/src/api/write/user_group.rs index dbb5f722a..b7e8b37ca 100644 --- a/client/core/rs/src/api/write/user_group.rs +++ b/client/core/rs/src/api/write/user_group.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -10,10 +10,11 @@ use super::KomodoWriteRequest; /// **Admin only.** Create a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct CreateUserGroup { /// The name to assign to the new UserGroup pub name: String, @@ -24,10 +25,11 @@ pub struct CreateUserGroup { /// **Admin only.** Rename a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct RenameUserGroup { /// The id of the UserGroup pub id: String, @@ -40,10 +42,11 @@ pub struct RenameUserGroup { /// **Admin only.** Delete a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct DeleteUserGroup { /// The id of the UserGroup pub id: String, @@ -54,10 +57,11 @@ pub struct DeleteUserGroup { /// **Admin only.** Add a user to a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct AddUserToUserGroup { /// The name or id of UserGroup that user should be added to. pub user_group: String, @@ -70,10 +74,11 @@ pub struct AddUserToUserGroup { /// **Admin only.** Remove a user from a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct RemoveUserFromUserGroup { /// The name or id of UserGroup that user should be removed from. pub user_group: String, @@ -87,10 +92,11 @@ pub struct RemoveUserFromUserGroup { /// Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct SetUsersInUserGroup { /// Id or name. pub user_group: String, diff --git a/client/core/rs/src/api/write/variable.rs b/client/core/rs/src/api/write/variable.rs index 2f55f637e..ebd45e4ec 100644 --- a/client/core/rs/src/api/write/variable.rs +++ b/client/core/rs/src/api/write/variable.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -10,10 +10,11 @@ use super::KomodoWriteRequest; /// **Admin only.** Create variable. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateVariableResponse)] +#[error(serror::Error)] pub struct CreateVariable { /// The name of the variable to create. pub name: String, @@ -36,10 +37,11 @@ pub type CreateVariableResponse = Variable; /// **Admin only.** Update variable value. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableValueResponse)] +#[error(serror::Error)] pub struct UpdateVariableValue { /// The name of the variable to update. pub name: String, @@ -55,10 +57,11 @@ pub type UpdateVariableValueResponse = Variable; /// **Admin only.** Update variable description. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateVariableDescription { /// The name of the variable to update. pub name: String, @@ -74,10 +77,11 @@ pub type UpdateVariableDescriptionResponse = Variable; /// **Admin only.** Update whether variable is secret. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableIsSecretResponse)] +#[error(serror::Error)] pub struct UpdateVariableIsSecret { /// The name of the variable to update. pub name: String, @@ -93,10 +97,11 @@ pub type UpdateVariableIsSecretResponse = Variable; /// **Admin only.** Delete a variable. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteVariableResponse)] +#[error(serror::Error)] pub struct DeleteVariable { pub name: String, } diff --git a/client/core/rs/src/request.rs b/client/core/rs/src/request.rs index ed7d497d1..5cf752255 100644 --- a/client/core/rs/src/request.rs +++ b/client/core/rs/src/request.rs @@ -15,10 +15,14 @@ use crate::{ impl KomodoClient { #[cfg(not(feature = "blocking"))] - pub async fn auth( + pub async fn auth( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoAuthRequest, + T::Response: DeserializeOwned, + { self .post( "/auth", @@ -31,10 +35,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn auth( - &self, - request: T, - ) -> anyhow::Result { + pub fn auth(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoAuthRequest, + T::Response: DeserializeOwned, + { self.post( "/auth", json!({ @@ -45,10 +50,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn user( + pub async fn user( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoUserRequest, + T::Response: DeserializeOwned, + { self .post( "/auth", @@ -61,10 +70,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn user( - &self, - request: T, - ) -> anyhow::Result { + pub fn user(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoUserRequest, + T::Response: DeserializeOwned, + { self.post( "/auth", json!({ @@ -75,10 +85,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn read( + pub async fn read( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoReadRequest, + T::Response: DeserializeOwned, + { self .post( "/read", @@ -91,10 +105,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn read( - &self, - request: T, - ) -> anyhow::Result { + pub fn read(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoReadRequest, + T::Response: DeserializeOwned, + { self.post( "/read", json!({ @@ -105,10 +120,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn write( + pub async fn write( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoWriteRequest, + T::Response: DeserializeOwned, + { self .post( "/write", @@ -121,10 +140,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn write( - &self, - request: T, - ) -> anyhow::Result { + pub fn write(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoWriteRequest, + T::Response: DeserializeOwned, + { self.post( "/write", json!({ @@ -135,10 +155,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn execute( + pub async fn execute( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoExecuteRequest, + T::Response: DeserializeOwned, + { self .post( "/execute", @@ -151,10 +175,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn execute( - &self, - request: T, - ) -> anyhow::Result { + pub fn execute(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoExecuteRequest, + T::Response: DeserializeOwned, + { self.post( "/execute", json!({ diff --git a/client/core/ts/package.json b/client/core/ts/package.json index 4626d1f35..ed83c59a6 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,6 +1,6 @@ { "name": "komodo_client", - "version": "1.16.12", + "version": "1.16.13", "description": "Komodo client package", "homepage": "https://komo.do", "main": "dist/lib.js", diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index d1a1092e5..558408cdf 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -36,9 +36,6 @@ export type ReadResponses = { GetUserGroup: Types.GetUserGroupResponse; ListUserGroups: Types.ListUserGroupsResponse; - // ==== SEARCH ==== - FindResources: Types.FindResourcesResponse; - // ==== PROCEDURE ==== GetProceduresSummary: Types.GetProceduresSummaryResponse; GetProcedure: Types.GetProcedureResponse; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 724bc5fe8..fca539c59 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -4747,28 +4747,6 @@ export interface ExportResourcesToToml { include_variables?: boolean; } -/** Find resources matching a common query. Response: [FindResourcesResponse]. */ -export interface FindResources { - /** The mongo query as JSON */ - query?: MongoDocument; - /** The resource variants to include in the response. */ - resources?: ResourceTarget["type"][]; -} - -/** Response for [FindResources]. */ -export interface FindResourcesResponse { - /** The matching servers. */ - servers: ServerListItem[]; - /** The matching deployments. */ - deployments: DeploymentListItem[]; - /** The matching builds. */ - builds: BuildListItem[]; - /** The matching repos. */ - repos: RepoListItem[]; - /** The matching procedures. */ - procedures: ProcedureListItem[]; -} - /** * **Admin only.** * Find a user. @@ -7459,7 +7437,6 @@ export type ReadRequest = | { type: "ListUserTargetPermissions", params: ListUserTargetPermissions } | { type: "GetUserGroup", params: GetUserGroup } | { type: "ListUserGroups", params: ListUserGroups } - | { type: "FindResources", params: FindResources } | { type: "GetProceduresSummary", params: GetProceduresSummary } | { type: "GetProcedure", params: GetProcedure } | { type: "GetProcedureActionState", params: GetProcedureActionState } diff --git a/client/periphery/rs/src/api/build.rs b/client/periphery/rs/src/api/build.rs index 219b4370d..2e45f2df3 100644 --- a/client/periphery/rs/src/api/build.rs +++ b/client/periphery/rs/src/api/build.rs @@ -1,9 +1,10 @@ use komodo_client::entities::update::Log; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(BuildResponse)] +#[error(serror::Error)] pub struct Build { pub build: komodo_client::entities::build::Build, /// Override registry token with one sent from core. @@ -20,12 +21,14 @@ pub type BuildResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneBuilders {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneBuildx {} diff --git a/client/periphery/rs/src/api/compose.rs b/client/periphery/rs/src/api/compose.rs index 770e18043..0758a404b 100644 --- a/client/periphery/rs/src/api/compose.rs +++ b/client/periphery/rs/src/api/compose.rs @@ -3,7 +3,7 @@ use komodo_client::entities::{ update::Log, FileContents, SearchCombinator, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use super::git::RepoActionResponse; @@ -13,16 +13,18 @@ use super::git::RepoActionResponse; /// /// Incoming from docker like: /// [{"Name":"project_name","Status":"running(1)","ConfigFiles":"/root/compose/compose.yaml,/root/compose/compose2.yaml"}] -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ListComposeProjects {} // /// Get the compose contents on the host, for stacks using /// `files_on_host`. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(GetComposeContentsOnHostResponse)] +#[error(serror::Error)] pub struct GetComposeContentsOnHost { /// The name of the stack pub name: String, @@ -39,8 +41,9 @@ pub struct GetComposeContentsOnHostResponse { // /// The stack folder must already exist for this to work -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetComposeServiceLog { /// The name of the project pub project: String, @@ -61,8 +64,9 @@ fn default_tail() -> u64 { // /// The stack folder must already exist for this to work -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetComposeServiceLogSearch { /// The name of the project pub project: String, @@ -86,8 +90,9 @@ pub struct GetComposeServiceLogSearch { /// Write the compose contents to the file on the host, for stacks using /// `files_on_host`. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct WriteComposeContentsToHost { /// The name of the stack pub name: String, @@ -104,8 +109,9 @@ pub struct WriteComposeContentsToHost { /// Write and commit compose contents. /// Only works with git repo based stacks. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct WriteCommitComposeContents { /// The stack to write to. pub stack: Stack, @@ -123,8 +129,9 @@ pub struct WriteCommitComposeContents { /// Rewrites the compose directory, pulls any images, takes down existing containers, /// and runs docker compose up. Response: [ComposePullResponse] -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(ComposePullResponse)] +#[error(serror::Error)] pub struct ComposePull { /// The stack to deploy pub stack: Stack, @@ -145,8 +152,9 @@ pub struct ComposePullResponse { // /// docker compose up. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(ComposeUpResponse)] +#[error(serror::Error)] pub struct ComposeUp { /// The stack to deploy pub stack: Stack, @@ -187,8 +195,9 @@ pub struct ComposeUpResponse { // /// General compose command runner -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct ComposeExecution { /// The compose project name to run the execution on. /// Usually its he name of the stack / folder under the `stack_dir`. diff --git a/client/periphery/rs/src/api/container.rs b/client/periphery/rs/src/api/container.rs index cead5e7f8..658e3ccfe 100644 --- a/client/periphery/rs/src/api/container.rs +++ b/client/periphery/rs/src/api/container.rs @@ -4,21 +4,23 @@ use komodo_client::entities::{ update::Log, SearchCombinator, TerminationSignal, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Container)] +#[error(serror::Error)] pub struct InspectContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetContainerLog { pub name: String, #[serde(default = "default_tail")] @@ -34,8 +36,9 @@ fn default_tail() -> u64 { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetContainerLogSearch { pub name: String, pub terms: Vec, @@ -50,16 +53,18 @@ pub struct GetContainerLogSearch { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ContainerStats)] +#[error(serror::Error)] pub struct GetContainerStats { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct GetContainerStatsList {} // @@ -68,8 +73,9 @@ pub struct GetContainerStatsList {} // ACTIONS // ======= -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct Deploy { pub deployment: Deployment, pub stop_signal: Option, @@ -83,40 +89,45 @@ pub struct Deploy { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct StartContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RestartContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PauseContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct UnpauseContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct StopContainer { pub name: String, pub signal: Option, @@ -125,8 +136,9 @@ pub struct StopContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RemoveContainer { pub name: String, pub signal: Option, @@ -135,8 +147,9 @@ pub struct RemoveContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RenameContainer { pub curr_name: String, pub new_name: String, @@ -144,36 +157,42 @@ pub struct RenameContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct StartAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct RestartAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct PauseAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct UnpauseAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct StopAllContainers {} diff --git a/client/periphery/rs/src/api/git.rs b/client/periphery/rs/src/api/git.rs index 3302da1c1..6eb29f19e 100644 --- a/client/periphery/rs/src/api/git.rs +++ b/client/periphery/rs/src/api/git.rs @@ -3,17 +3,19 @@ use std::path::PathBuf; use komodo_client::entities::{ update::Log, CloneArgs, EnvironmentVar, LatestCommit, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(LatestCommit)] +#[error(serror::Error)] pub struct GetLatestCommit { pub name: String, } -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct CloneRepo { pub args: CloneArgs, #[serde(default)] @@ -35,8 +37,9 @@ fn default_env_file_path() -> String { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct PullRepo { pub args: CloneArgs, #[serde(default)] @@ -55,8 +58,9 @@ pub struct PullRepo { // /// Either pull or clone depending on whether it exists. -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct PullOrCloneRepo { pub args: CloneArgs, #[serde(default)] @@ -86,8 +90,9 @@ pub struct RepoActionResponse { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RenameRepo { pub curr_name: String, pub new_name: String, @@ -95,8 +100,9 @@ pub struct RenameRepo { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteRepo { pub name: String, } diff --git a/client/periphery/rs/src/api/image.rs b/client/periphery/rs/src/api/image.rs index 08c1b6da7..f5a1c2123 100644 --- a/client/periphery/rs/src/api/image.rs +++ b/client/periphery/rs/src/api/image.rs @@ -2,29 +2,32 @@ use komodo_client::entities::{ docker::image::{Image, ImageHistoryResponseItem}, update::Log, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Image)] +#[error(serror::Error)] pub struct InspectImage { pub name: String, } // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ImageHistory { pub name: String, } // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PullImage { /// The name of the image. pub name: String, @@ -36,8 +39,9 @@ pub struct PullImage { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteImage { /// Id or name pub name: String, @@ -45,6 +49,7 @@ pub struct DeleteImage { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneImages {} diff --git a/client/periphery/rs/src/api/mod.rs b/client/periphery/rs/src/api/mod.rs index dade628f6..ed9616ab3 100644 --- a/client/periphery/rs/src/api/mod.rs +++ b/client/periphery/rs/src/api/mod.rs @@ -8,7 +8,7 @@ use komodo_client::entities::{ update::Log, SystemCommand, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use serror::Serror; @@ -23,8 +23,9 @@ pub mod volume; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetHealthResponse)] +#[error(serror::Error)] pub struct GetHealth {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -32,8 +33,9 @@ pub struct GetHealthResponse {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetVersionResponse)] +#[error(serror::Error)] pub struct GetVersion {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -42,8 +44,9 @@ pub struct GetVersionResponse { } /// Returns all containers, networks, images, compose projects -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetDockerListsResponse)] +#[error(serror::Error)] pub struct GetDockerLists {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -57,36 +60,41 @@ pub struct GetDockerListsResponse { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ListGitProvidersResponse)] +#[error(serror::Error)] pub struct ListGitProviders {} pub type ListGitProvidersResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ListDockerRegistriesResponse)] +#[error(serror::Error)] pub struct ListDockerRegistries {} pub type ListDockerRegistriesResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ListSecrets {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneSystem {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RunCommand { pub command: SystemCommand, } diff --git a/client/periphery/rs/src/api/network.rs b/client/periphery/rs/src/api/network.rs index 67b76547e..21e2de3b4 100644 --- a/client/periphery/rs/src/api/network.rs +++ b/client/periphery/rs/src/api/network.rs @@ -1,21 +1,23 @@ use komodo_client::entities::{ docker::network::Network, update::Log, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Network)] +#[error(serror::Error)] pub struct InspectNetwork { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct CreateNetwork { pub name: String, pub driver: Option, @@ -23,8 +25,9 @@ pub struct CreateNetwork { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteNetwork { /// Id or name pub name: String, @@ -32,6 +35,7 @@ pub struct DeleteNetwork { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneNetworks {} diff --git a/client/periphery/rs/src/api/stats.rs b/client/periphery/rs/src/api/stats.rs index 06fa5a4fe..fcaf072f9 100644 --- a/client/periphery/rs/src/api/stats.rs +++ b/client/periphery/rs/src/api/stats.rs @@ -1,23 +1,28 @@ use komodo_client::entities::stats::{ SystemInformation, SystemProcess, SystemStats, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(SystemInformation)] +#[error(serror::Error)] pub struct GetSystemInformation {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(SystemStats)] +#[error(serror::Error)] pub struct GetSystemStats {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] -pub struct GetSystemProcesses {} \ No newline at end of file +#[error(serror::Error)] +pub struct GetSystemProcesses {} + +// diff --git a/client/periphery/rs/src/api/volume.rs b/client/periphery/rs/src/api/volume.rs index 39ac64084..462b1806d 100644 --- a/client/periphery/rs/src/api/volume.rs +++ b/client/periphery/rs/src/api/volume.rs @@ -1,19 +1,21 @@ use komodo_client::entities::{docker::volume::Volume, update::Log}; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Volume)] +#[error(serror::Error)] pub struct InspectVolume { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteVolume { /// Id or name pub name: String, @@ -21,6 +23,7 @@ pub struct DeleteVolume { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneVolumes {} diff --git a/client/periphery/rs/src/lib.rs b/client/periphery/rs/src/lib.rs index 8480491f3..8fc127771 100644 --- a/client/periphery/rs/src/lib.rs +++ b/client/periphery/rs/src/lib.rs @@ -3,8 +3,8 @@ use std::{sync::OnceLock, time::Duration}; use anyhow::Context; use reqwest::StatusCode; use resolver_api::HasResponse; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::json; -use serror::deserialize_error; pub mod api; @@ -45,10 +45,14 @@ impl PeripheryClient { level = "debug", skip(self) )] - pub async fn request( + pub async fn request( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: std::fmt::Debug + Serialize + HasResponse, + T::Response: DeserializeOwned, + { tracing::debug!("running health check"); self.health_check().await?; tracing::debug!("health check passed. running inner request"); @@ -64,11 +68,15 @@ impl PeripheryClient { } #[tracing::instrument(level = "debug", skip(self))] - async fn request_inner( + async fn request_inner( &self, request: T, timeout: Option, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: std::fmt::Debug + Serialize + HasResponse, + T::Response: DeserializeOwned, + { let req_type = T::req_type(); tracing::trace!( "sending request | type: {req_type} | body: {request:?}" @@ -104,8 +112,7 @@ impl PeripheryClient { tracing::debug!("got response text, deserializing error"); - let error = deserialize_error(text) - .context(format!("request to periphery failed | {status}")); + let error = serror::deserialize_error(text).context(status); Err(error) } diff --git a/frontend/public/client/responses.d.ts b/frontend/public/client/responses.d.ts index da8f01725..fcece45ea 100644 --- a/frontend/public/client/responses.d.ts +++ b/frontend/public/client/responses.d.ts @@ -28,7 +28,6 @@ export type ReadResponses = { ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse; GetUserGroup: Types.GetUserGroupResponse; ListUserGroups: Types.ListUserGroupsResponse; - FindResources: Types.FindResourcesResponse; GetProceduresSummary: Types.GetProceduresSummaryResponse; GetProcedure: Types.GetProcedureResponse; GetProcedureActionState: Types.GetProcedureActionStateResponse; diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 191dba903..7dbf49d53 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -4529,26 +4529,6 @@ export interface ExportResourcesToToml { /** Whether to include variables */ include_variables?: boolean; } -/** Find resources matching a common query. Response: [FindResourcesResponse]. */ -export interface FindResources { - /** The mongo query as JSON */ - query?: MongoDocument; - /** The resource variants to include in the response. */ - resources?: ResourceTarget["type"][]; -} -/** Response for [FindResources]. */ -export interface FindResourcesResponse { - /** The matching servers. */ - servers: ServerListItem[]; - /** The matching deployments. */ - deployments: DeploymentListItem[]; - /** The matching builds. */ - builds: BuildListItem[]; - /** The matching repos. */ - repos: RepoListItem[]; - /** The matching procedures. */ - procedures: ProcedureListItem[]; -} /** * **Admin only.** * Find a user. @@ -7146,9 +7126,6 @@ export type ReadRequest = { } | { type: "ListUserGroups"; params: ListUserGroups; -} | { - type: "FindResources"; - params: FindResources; } | { type: "GetProceduresSummary"; params: GetProceduresSummary; diff --git a/lib/cache/README.md b/lib/cache/README.md new file mode 100644 index 000000000..653591d92 --- /dev/null +++ b/lib/cache/README.md @@ -0,0 +1,5 @@ +# Cache module + +Contains a thread-safe async timeout cache implementation. +Used to cache outputs in memory for a limited time period. +Can be used to avoid re-running the underlying process which generated an output in too short a timeframe. \ No newline at end of file diff --git a/lib/command/README.md b/lib/command/README.md new file mode 100644 index 000000000..e69b14595 --- /dev/null +++ b/lib/command/README.md @@ -0,0 +1,3 @@ +# Command module + +Helpers to run shell commands as child processes, and collect the outputs. \ No newline at end of file diff --git a/lib/environment_file/README.md b/lib/environment_file/README.md new file mode 100644 index 000000000..dd01f956d --- /dev/null +++ b/lib/environment_file/README.md @@ -0,0 +1,8 @@ +# Environment file module + +Helpers for parsing variables from file contents. + +Used to parse secrets from the files specified in env variable ending in `_FILE`. + +Compatible with docker compose secrets, +see [https://docs.docker.com/compose/how-tos/use-secrets/](https://docs.docker.com/compose/how-tos/use-secrets/). \ No newline at end of file diff --git a/lib/formatting/README.md b/lib/formatting/README.md new file mode 100644 index 000000000..81804cb9a --- /dev/null +++ b/lib/formatting/README.md @@ -0,0 +1,3 @@ +# Formatting module + +Used to pretty-format logs using HTML for better display from the UI. \ No newline at end of file diff --git a/lib/git/README.md b/lib/git/README.md new file mode 100644 index 000000000..eaca0afaf --- /dev/null +++ b/lib/git/README.md @@ -0,0 +1,3 @@ +# Git module + +Helpers for cloning, pulling, and committing to git repos. \ No newline at end of file diff --git a/lib/logger/README.md b/lib/logger/README.md new file mode 100644 index 000000000..fadc2a455 --- /dev/null +++ b/lib/logger/README.md @@ -0,0 +1,3 @@ +# Logger module + +Helpers to configure standardized application logging / opentelemetry output. \ No newline at end of file diff --git a/lib/logger/src/otel.rs b/lib/logger/src/otel.rs index 91a4eff23..a9b756cc3 100644 --- a/lib/logger/src/otel.rs +++ b/lib/logger/src/otel.rs @@ -23,11 +23,8 @@ fn resource(service_name: String) -> Resource { pub fn tracer(endpoint: &str, service_name: String) -> Tracer { let provider = opentelemetry_sdk::trace::TracerProvider::builder() - .with_config( - opentelemetry_sdk::trace::Config::default() - .with_sampler(Sampler::AlwaysOn) - .with_resource(resource(service_name.clone())), - ) + .with_resource(resource(service_name.clone())) + .with_sampler(Sampler::AlwaysOn) .with_batch_exporter( opentelemetry_otlp::SpanExporter::builder() .with_tonic() diff --git a/lib/response/Cargo.toml b/lib/response/Cargo.toml new file mode 100644 index 000000000..da71475d8 --- /dev/null +++ b/lib/response/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "response" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] +serde_json.workspace = true +serror.workspace = true +anyhow.workspace = true +serde.workspace = true +axum.workspace = true \ No newline at end of file diff --git a/lib/response/src/lib.rs b/lib/response/src/lib.rs new file mode 100644 index 000000000..a2fb05fd7 --- /dev/null +++ b/lib/response/src/lib.rs @@ -0,0 +1,76 @@ +use anyhow::Context; +use axum::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; +use serde::Serialize; +use serror::serialize_error; + +pub struct Response(pub axum::response::Response); + +impl From for Response +where + T: Serialize, +{ + fn from(value: T) -> Response { + let res = match serde_json::to_string(&value) + .context("failed to serialize response body") + { + Ok(body) => axum::response::Response::builder() + .header( + CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ) + .body(axum::body::Body::from(body)) + .unwrap(), + Err(e) => axum::response::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header( + CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ) + .body(axum::body::Body::from(serialize_error(&e))) + .unwrap(), + }; + Response(res) + } +} + +pub enum JsonString { + Ok(String), + Err(serde_json::Error), +} + +impl From for JsonString +where + T: Serialize, +{ + fn from(value: T) -> JsonString { + match serde_json::to_string(&value) { + Ok(body) => JsonString::Ok(body), + Err(e) => JsonString::Err(e), + } + } +} + +impl JsonString { + pub fn into_response(self) -> axum::response::Response { + match self { + JsonString::Ok(body) => axum::response::Response::builder() + .header( + CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ) + .body(axum::body::Body::from(body)) + .unwrap(), + JsonString::Err(error) => axum::response::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header( + CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ) + .body(axum::body::Body::from(serialize_error( + &anyhow::Error::from(error) + .context("failed to serialize response body"), + ))) + .unwrap(), + } + } +} diff --git a/runfile.toml b/runfile.toml index b511b49a2..2c90caf72 100644 --- a/runfile.toml +++ b/runfile.toml @@ -46,12 +46,12 @@ cmd = """ docker compose -p komodo-dev -f test.compose.yaml build""" [test-core] -description = "runs core --release pointing to .komodo/core.config.toml" -cmd = "KOMODO_CONFIG_PATH=.komodo/core.config.toml cargo run -p komodo_core --release" +description = "runs core --release pointing to .dev/core.config.toml" +cmd = "KOMODO_CONFIG_PATH=.dev/core.config.toml cargo run -p komodo_core --release" [test-periphery] -description = "runs periphery --release pointing to test.periphery.config.toml" -cmd = "PERIPHERY_CONFIG_PATH=test.periphery.config.toml cargo run -p komodo_periphery --release" +description = "runs periphery --release pointing to .dev/periphery.config.toml" +cmd = "PERIPHERY_CONFIG_PATH=.dev/periphery.config.toml cargo run -p komodo_periphery --release" [create-multiarch-builder] cmd = "docker buildx create --name builder --use --bootstrap" diff --git a/test.core.config.toml b/test.core.config.toml deleted file mode 100644 index c7271bcef..000000000 --- a/test.core.config.toml +++ /dev/null @@ -1,43 +0,0 @@ -############################ -# 🦎 KOMODO CORE CONFIG 🦎 # -############################ -title = "Komodo Test" -host = "http://localhost:9121" -port = 9121 -passkey = "a_random_passkey" -sync_directory = ".komodo/syncs" -repo_directory = ".komodo/core-repos" -action_directory = ".komodo/actions" -frontend_path = "frontend/dist" -first_server = "http://localhost:8121" - -disable_confirm_dialog = true -enable_new_users = true - -# database.address = "localhost:27017" -database.address = "test-ferretdb.orb.local" -# database.username = "admin" -# database.password = "admin" -# database.uri = "mongodb://admin:admin@localhost:27017/komodo" # For ferretdb-postgres - -local_auth = true -jwt_secret = "your_random_secret" -jwt_ttl = "2-wk" - -oidc_enabled = false -# oidc_provider = "http://server.authentik2.orb.local:9000/application/o/komodo" -# oidc_client_id = "komodo" -# oidc_client_secret = "komodo" - -webhook_secret = "a_random_webhook_secret" - -monitoring_interval = "5-sec" -resource_poll_interval = "1-min" - -########### -# LOGGING # -########### -logging.level = "info" -logging.stdio = "standard" -# logging.otlp_endpoint = "http://grafana.orb.local:4317" -logging.opentelemetry_service_name = "Komodo" diff --git a/test.periphery.config.toml b/test.periphery.config.toml deleted file mode 100644 index c31a74340..000000000 --- a/test.periphery.config.toml +++ /dev/null @@ -1,24 +0,0 @@ -################################# -# 🦎 KOMODO PERIPHERY CONFIG 🦎 # -################################# -port = 8121 -repo_dir = ".komodo/repos" -stack_dir = ".komodo/stacks" -stats_polling_rate = "1-sec" -legacy_compose_cli = false -include_disk_mounts = [] - -############ -# Security # -############ -ssl_enabled = true -ssl_key_file = ".komodo/ssl/key.pem" -ssl_cert_file = ".komodo/ssl/cert.pem" - -########### -# LOGGING # -########### -logging.level = "info" -logging.stdio = "standard" -# logging.otlp_endpoint = "http://grafana.orb.local:4317" -logging.opentelemetry_service_name = "Periphery" \ No newline at end of file