* ferretdb v2 now that they support arm64

* remove ignored for sqlite

* tweak

* mongo copier

* 1.17.6

* primary name is ferretdb option

* give doc counts

* fmt

* print document count

* komodo util versioned seperately

* add copy startup sleep

* FerretDB v2 upgrade guide

* tweak docs

* tweak

* tweak

* add link to upgrade guide for ferretdb v1 users

* fix copy batch size

* multi arch util setup

* util use workspace version

* clarify behavior re root_directory

* finished copying database log

* update to rust:1.87.0

* fix: reset rename editor on navigate

* loosen naming restrictions for most resource types

* added support for ntfy email forwarding (#493)

* fix alerter email option docs

* remove logging directive in example compose - can be done at user discretion

* more granular permissions

* fix initial fe type errors

* fix the new perm typing

* add dedicated ws routes to connect to deployment / stack terminal, using the permissioning on those entities

* frontend should convey / respect the perms

* use IndexSet for SpecificPermission

* finish IndexSet

* match regex or wildcard resource  name pattern

* gen ts client

* implement new terminal components which use the container / deployment / stack specific permissioned endpoints

* user group backend "everyone" support

* bump to 1.18.0 for significant permissioning changes

* ts 1.18.0

* permissions FE in prog

* FE permissions assignment working

* user group all map uses ordered IndexMap for consistency

* improve user group toml and fix execute bug

* URL encode names in webhook urls

* UI support configure 'everyone' User Group

* sync handle toggling user group everyone

* user group table show everyone enabled

* sync will update user group "everyone"

* Inspect Deployment / Stack containers directly

* fix InspectStackContainer container name

* Deployment / stack service inspect

* Stack / Deployment inherit Logs, Inspect and Terminal from their attached server for user

* fix compose down not capitalized

* don't use tabs

* more descriptive permission table titles

* different localstorage for permissions show all

* network / image / volume inspect don't require inspect perms

* fix container inspect

* fix list container undefined error

* prcesses list gated UI

* remove localstorage on permission table expansion

* fix ug sync handling of all zero permissions

* pretty log startup config

* implement actually pretty logging initial config

* fix user permissions when api returns string

* fix container info table

* util based on bullseye-slim

* permission toml specific skip_serializing_if = "IndexSet::is_empty"

* container tab permissions reversed

* reorder pretty logging stuff to be together

* update docs with permissioning info

* tweak docs

* update roadmap

---------

Co-authored-by: FelixBreitweiser <felix.breitweiser@uni-siegen.de>
This commit is contained in:
Maxwell Becker
2025-05-30 12:52:58 -07:00
committed by GitHub
parent a43e1f3f52
commit 31034e5b34
190 changed files with 5934 additions and 3395 deletions

141
Cargo.lock generated
View File

@@ -165,9 +165,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-config"
version = "1.6.2"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fcc63c9860579e4cb396239570e979376e70aab79e496621748a09913f8b36"
checksum = "02a18fd934af6ae7ca52410d4548b98eb895aab0f1ea417d168d85db1434a141"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -254,9 +254,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.124.0"
version = "1.134.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6746a315a5446304942f057e6a072347dad558d23bfbda64c42b9a236f824013"
checksum = "a9a84e95f739e79d157409fa00e41008dabd181022193dabfabc68ddccbd6055"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -271,16 +271,15 @@ dependencies = [
"aws-types",
"fastrand",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sso"
version = "1.65.0"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8efec445fb78df585327094fcef4cad895b154b58711e504db7a93c41aa27151"
checksum = "83447efb7179d8e2ad2afb15ceb9c113debbc2ecdf109150e338e2e28b86190b"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -294,16 +293,15 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-ssooidc"
version = "1.66.0"
version = "1.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e49cca619c10e7b002dc8e66928ceed66ab7f56c1a3be86c5437bf2d8d89bba"
checksum = "c5f9bfbbda5e2b9fe330de098f14558ee8b38346408efe9f2e9cee82dc1636a4"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -317,16 +315,15 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sts"
version = "1.66.0"
version = "1.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7420479eac0a53f776cc8f0d493841ffe58ad9d9783f3947be7265784471b47a"
checksum = "e17b984a66491ec08b4f4097af8911251db79296b3e4a763060b45805746264f"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -341,7 +338,6 @@ dependencies = [
"aws-types",
"fastrand",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
@@ -419,7 +415,7 @@ dependencies = [
"hyper-util",
"pin-project-lite",
"rustls 0.21.12",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -651,7 +647,7 @@ dependencies = [
"hyper 1.6.0",
"hyper-util",
"pin-project-lite",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"tokio",
@@ -795,9 +791,9 @@ dependencies = [
[[package]]
name = "bollard"
version = "0.18.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30"
checksum = "af706e9dc793491dd382c99c22fde6e9934433d4cc0d6a4b34eb2cdc57a5c917"
dependencies = [
"base64 0.22.1",
"bollard-stubs",
@@ -828,20 +824,21 @@ dependencies = [
[[package]]
name = "bollard-stubs"
version = "1.47.1-rc.27.3.1"
version = "1.48.2-rc.28.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da"
checksum = "79cdf0fccd5341b38ae0be74b74410bdd5eceeea8876dc149a13edfe57e3b259"
dependencies = [
"serde",
"serde_json",
"serde_repr",
"serde_with",
]
[[package]]
name = "bson"
version = "2.14.0"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8113ff51309e2779e8785a246c10fb783e8c2452f134d6257fd71cc03ccd6c"
checksum = "7969a9ba84b0ff843813e7249eed1678d9b6607ce5a3b8f0a47af3fcf7978e6e"
dependencies = [
"ahash",
"base64 0.22.1",
@@ -893,7 +890,7 @@ dependencies = [
[[package]]
name = "cache"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"tokio",
@@ -996,9 +993,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.37"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1006,9 +1003,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.37"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
dependencies = [
"anstream",
"anstyle",
@@ -1060,7 +1057,7 @@ dependencies = [
[[package]]
name = "command"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"formatting",
@@ -1523,9 +1520,9 @@ dependencies = [
[[package]]
name = "english-to-cron"
version = "0.1.4"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a13a7d5e0ab3872c3ee478366eae624d89ab953d30276b0eee08169774ceb73"
checksum = "e26fb7377cbec9a94f60428e6e6afbe10c699a14639b4d3d4b67b25c0bbe0806"
dependencies = [
"regex",
]
@@ -1544,7 +1541,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"thiserror 2.0.12",
]
@@ -1624,7 +1621,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"serror",
]
@@ -1786,7 +1783,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"cache",
@@ -2160,7 +2157,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.6.0",
"hyper-util",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -2523,7 +2520,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"clap",
@@ -2539,7 +2536,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2551,6 +2548,7 @@ dependencies = [
"derive_variants",
"envy",
"futures",
"indexmap 2.9.0",
"mongo_indexed",
"partial_derive2",
"reqwest",
@@ -2570,7 +2568,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -2599,6 +2597,7 @@ dependencies = [
"git",
"hex",
"hmac",
"indexmap 2.9.0",
"jsonwebtoken",
"komodo_client",
"logger",
@@ -2608,7 +2607,6 @@ dependencies = [
"nom_pem",
"octorust",
"openidconnect",
"ordered_hash_map",
"partial_derive2",
"periphery_client",
"rand 0.9.1",
@@ -2616,7 +2614,7 @@ dependencies = [
"reqwest",
"resolver_api",
"response",
"rustls 0.23.26",
"rustls 0.23.27",
"serde",
"serde_json",
"serde_yaml",
@@ -2639,7 +2637,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2667,7 +2665,7 @@ dependencies = [
"resolver_api",
"response",
"run_command",
"rustls 0.23.26",
"rustls 0.23.27",
"serde",
"serde_json",
"serde_yaml",
@@ -2681,6 +2679,21 @@ dependencies = [
"uuid",
]
[[package]]
name = "komodo_util"
version = "1.18.0"
dependencies = [
"anyhow",
"dotenvy",
"envy",
"futures-util",
"mungos",
"serde",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -2757,7 +2770,7 @@ dependencies = [
[[package]]
name = "logger"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"komodo_client",
@@ -3512,13 +3525,13 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "periphery_client"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"komodo_client",
"reqwest",
"resolver_api",
"rustls 0.23.26",
"rustls 0.23.27",
"serde",
"serde_json",
"serde_qs",
@@ -3718,7 +3731,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.1",
"rustls 0.23.26",
"rustls 0.23.27",
"socket2",
"thiserror 2.0.12",
"tokio",
@@ -3737,7 +3750,7 @@ dependencies = [
"rand 0.9.1",
"ring",
"rustc-hash 2.1.1",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
@@ -3921,7 +3934,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-native-certs 0.8.1",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
@@ -4040,7 +4053,7 @@ dependencies = [
[[package]]
name = "response"
version = "1.17.5"
version = "1.18.0"
dependencies = [
"anyhow",
"axum",
@@ -4175,16 +4188,16 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.1",
"rustls-webpki 0.103.3",
"subtle",
"zeroize",
]
@@ -4252,9 +4265,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.1"
version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"aws-lc-rs",
"ring",
@@ -4854,9 +4867,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.35.0"
version = "0.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422"
checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a"
dependencies = [
"libc",
"memchr",
@@ -5016,9 +5029,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"bytes",
@@ -5059,7 +5072,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls 0.23.26",
"rustls 0.23.27",
"tokio",
]
@@ -5083,7 +5096,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -5225,9 +5238,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"bitflags 2.9.0",
"bytes",
@@ -5367,7 +5380,7 @@ dependencies = [
"httparse",
"log",
"rand 0.9.1",
"rustls 0.23.26",
"rustls 0.23.27",
"rustls-pki-types",
"sha1",
"thiserror 2.0.12",
@@ -5502,9 +5515,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"getrandom 0.3.2",
"js-sys",

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "1.17.5"
version = "1.18.0"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -45,7 +45,7 @@ svi = "1.0.1"
# ASYNC
reqwest = { version = "0.12.15", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
tokio = { version = "1.44.2", features = ["full"] }
tokio = { version = "1.45.1", features = ["full"] }
tokio-util = { version = "0.7.15", features = ["io", "codec"] }
tokio-stream = { version = "0.1.17", features = ["sync"] }
pin-project-lite = "0.2.16"
@@ -56,12 +56,12 @@ arc-swap = "1.7.1"
# SERVER
tokio-tungstenite = { version = "0.26.2", features = ["rustls-tls-native-roots"] }
axum-extra = { version = "0.10.1", features = ["typed-header"] }
tower-http = { version = "0.6.2", features = ["fs", "cors"] }
tower-http = { version = "0.6.4", features = ["fs", "cors"] }
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
axum = { version = "0.8.4", features = ["ws", "json", "macros"] }
# SER/DE
ordered_hash_map = { version = "0.4.0", features = ["serde"] }
indexmap = { version = "2.9.0", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive"] }
strum = { version = "0.27.1", features = ["derive"] }
serde_json = "1.0.140"
@@ -83,19 +83,19 @@ opentelemetry = "0.29.1"
tracing = "0.1.41"
# CONFIG
clap = { version = "4.5.37", features = ["derive"] }
clap = { version = "4.5.38", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
# CRYPTO / AUTH
uuid = { version = "1.16.0", features = ["v4", "fast-rng", "serde"] }
uuid = { version = "1.17.0", features = ["v4", "fast-rng", "serde"] }
jsonwebtoken = { version = "9.3.1", default-features = false }
openidconnect = "4.0.0"
urlencoding = "2.1.3"
nom_pem = "4.0.0"
bcrypt = "0.17.0"
base64 = "0.22.1"
rustls = "0.23.26"
rustls = "0.23.27"
hmac = "0.12.1"
sha2 = "0.10.9"
rand = "0.9.1"
@@ -103,16 +103,16 @@ hex = "0.4.3"
# SYSTEM
portable-pty = "0.9.0"
bollard = "0.18.1"
sysinfo = "0.35.0"
bollard = "0.19.0"
sysinfo = "0.35.1"
# CLOUD
aws-config = "1.6.2"
aws-sdk-ec2 = "1.124.0"
aws-config = "1.6.3"
aws-sdk-ec2 = "1.134.0"
aws-credential-types = "1.2.3"
## CRON
english-to-cron = "0.1.4"
english-to-cron = "0.1.6"
chrono-tz = "0.10.3"
chrono = "0.4.41"
croner = "2.1.0"
@@ -126,4 +126,4 @@ wildcard = "0.3.0"
colored = "3.0.0"
regex = "1.11.1"
bytes = "1.10.1"
bson = "2.14.0"
bson = "2.15.0"

View File

@@ -1,7 +1,7 @@
## Builds the Komodo Core and Periphery binaries
## Builds the Komodo Core, Periphery, and Util binaries
## for a specific architecture.
FROM rust:1.86.0-bullseye AS builder
FROM rust:1.87.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
@@ -10,17 +10,20 @@ COPY ./client/core/rs ./client/core/rs
COPY ./client/periphery ./client/periphery
COPY ./bin/core ./bin/core
COPY ./bin/periphery ./bin/periphery
COPY ./bin/util ./bin/util
# Compile bin
RUN \
cargo build -p komodo_core --release && \
cargo build -p komodo_periphery --release
cargo build -p komodo_periphery --release && \
cargo build -p komodo_util --release
# Copy just the binaries to scratch image
FROM scratch
COPY --from=builder /builder/target/release/core /core
COPY --from=builder /builder/target/release/periphery /periphery
COPY --from=builder /builder/target/release/util /util
LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo
LABEL org.opencontainers.image.description="Komodo Binaries"

View File

@@ -39,7 +39,6 @@ svi.workspace = true
# external
aws-credential-types.workspace = true
tokio-tungstenite.workspace = true
ordered_hash_map.workspace = true
english-to-cron.workspace = true
openidconnect.workspace = true
jsonwebtoken.workspace = true
@@ -54,6 +53,7 @@ serde_json.workspace = true
serde_yaml.workspace = true
typeshare.workspace = true
chrono-tz.workspace = true
indexmap.workspace = true
octorust.workspace = true
wildcard.workspace = true
arc-swap.workspace = true

View File

@@ -1,7 +1,7 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
# Build Core
FROM rust:1.86.0-bullseye AS core-builder
FROM rust:1.87.0-bullseye AS core-builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./

View File

@@ -130,13 +130,15 @@ pub async fn send_alert_to_alerter(
)
})
}
AlerterEndpoint::Ntfy(NtfyAlerterEndpoint { url }) => {
ntfy::send_alert(url, alert).await.with_context(|| {
format!(
"Failed to send alert to ntfy Alerter {}",
alerter.name
)
})
AlerterEndpoint::Ntfy(NtfyAlerterEndpoint { url, email }) => {
ntfy::send_alert(url, email.as_deref(), alert)
.await
.with_context(|| {
format!(
"Failed to send alert to ntfy Alerter {}",
alerter.name
)
})
}
AlerterEndpoint::Pushover(PushoverAlerterEndpoint { url }) => {
pushover::send_alert(url, alert).await.with_context(|| {

View File

@@ -5,6 +5,7 @@ use super::*;
#[instrument(level = "debug")]
pub async fn send_alert(
url: &str,
email: Option<&str>,
alert: &Alert,
) -> anyhow::Result<()> {
let level = fmt_level(alert.level);
@@ -224,22 +225,27 @@ pub async fn send_alert(
};
if !content.is_empty() {
send_message(url, content).await?;
send_message(url, email, content).await?;
}
Ok(())
}
async fn send_message(
url: &str,
email: Option<&str>,
content: String,
) -> anyhow::Result<()> {
let response = http_client()
let mut request = http_client()
.post(url)
.header("Title", "ntfy Alert")
.body(content)
.send()
.await
.context("Failed to send message")?;
.body(content);
if let Some(email) = email {
request = request.header("X-Email", email);
}
let response =
request.send().await.context("Failed to send message")?;
let status = response.status();
if status.is_success() {

View File

@@ -39,7 +39,8 @@ use crate::{
random_string,
update::update_update,
},
resource::{self, refresh_action_state_cache},
permission::get_check_permissions,
resource::refresh_action_state_cache,
state::{action_states, db_client},
};
@@ -71,10 +72,10 @@ impl Resolve<ExecuteArgs> for RunAction {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let mut action = resource::get_check_permissions::<Action>(
let mut action = get_check_permissions::<Action>(
&self.action,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -12,7 +12,7 @@ use resolver_api::Resolve;
use crate::{
alert::send_alert_to_alerter, helpers::update::update_update,
resource::get_check_permissions,
permission::get_check_permissions,
};
use super::ExecuteArgs;
@@ -26,7 +26,7 @@ impl Resolve<ExecuteArgs> for TestAlerter {
let alerter = get_check_permissions::<Alerter>(
&self.alerter,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -48,6 +48,7 @@ use crate::{
registry_token,
update::{init_execution_update, update_update},
},
permission::get_check_permissions,
resource::{self, refresh_build_state_cache},
state::{action_states, db_client},
};
@@ -80,10 +81,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let mut build = resource::get_check_permissions::<Build>(
let mut build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -513,10 +514,10 @@ impl Resolve<ExecuteArgs> for CancelBuild {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -34,6 +34,7 @@ use crate::{
update::update_update,
},
monitor::update_cache_for_server,
permission::get_check_permissions,
resource,
state::action_states,
};
@@ -68,10 +69,10 @@ async fn setup_deployment_execution(
deployment: &str,
user: &User,
) -> anyhow::Result<(Deployment, Server)> {
let deployment = resource::get_check_permissions::<Deployment>(
let deployment = get_check_permissions::<Deployment>(
deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -12,6 +12,7 @@ use komodo_client::{
api::execute::*,
entities::{
Operation,
permission::PermissionLevel,
update::{Log, Update},
user::User,
},
@@ -86,18 +87,6 @@ pub enum ExecuteRequest {
PruneBuildx(PruneBuildx),
PruneSystem(PruneSystem),
// ==== DEPLOYMENT ====
Deploy(Deploy),
BatchDeploy(BatchDeploy),
PullDeployment(PullDeployment),
StartDeployment(StartDeployment),
RestartDeployment(RestartDeployment),
PauseDeployment(PauseDeployment),
UnpauseDeployment(UnpauseDeployment),
StopDeployment(StopDeployment),
DestroyDeployment(DestroyDeployment),
BatchDestroyDeployment(BatchDestroyDeployment),
// ==== STACK ====
DeployStack(DeployStack),
BatchDeployStack(BatchDeployStack),
@@ -113,6 +102,18 @@ pub enum ExecuteRequest {
DestroyStack(DestroyStack),
BatchDestroyStack(BatchDestroyStack),
// ==== DEPLOYMENT ====
Deploy(Deploy),
BatchDeploy(BatchDeploy),
PullDeployment(PullDeployment),
StartDeployment(StartDeployment),
RestartDeployment(RestartDeployment),
PauseDeployment(PauseDeployment),
UnpauseDeployment(UnpauseDeployment),
StopDeployment(StopDeployment),
DestroyDeployment(DestroyDeployment),
BatchDestroyDeployment(BatchDestroyDeployment),
// ==== BUILD ====
RunBuild(RunBuild),
BatchRunBuild(BatchRunBuild),
@@ -298,6 +299,7 @@ async fn batch_execute<E: BatchExecute>(
pattern,
Default::default(),
user,
PermissionLevel::Execute.into(),
&[],
)
.await?;

View File

@@ -21,7 +21,8 @@ use tokio::sync::Mutex;
use crate::{
alert::send_alerts,
helpers::{procedure::execute_procedure, update::update_update},
resource::{self, refresh_procedure_state_cache},
permission::get_check_permissions,
resource::refresh_procedure_state_cache,
state::{action_states, db_client},
};
@@ -70,10 +71,10 @@ fn resolve_inner(
>,
> {
Box::pin(async move {
let procedure = resource::get_check_permissions::<Procedure>(
let procedure = get_check_permissions::<Procedure>(
&procedure,
&user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -41,6 +41,7 @@ use crate::{
query::get_variables_and_secrets,
update::update_update,
},
permission::get_check_permissions,
resource::{self, refresh_repo_state_cache},
state::{action_states, db_client},
};
@@ -73,10 +74,10 @@ impl Resolve<ExecuteArgs> for CloneRepo {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let mut repo = resource::get_check_permissions::<Repo>(
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -185,10 +186,10 @@ impl Resolve<ExecuteArgs> for PullRepo {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let mut repo = resource::get_check_permissions::<Repo>(
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -340,10 +341,10 @@ impl Resolve<ExecuteArgs> for BuildRepo {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let mut repo = resource::get_check_permissions::<Repo>(
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -651,10 +652,10 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -15,7 +15,7 @@ use resolver_api::Resolve;
use crate::{
helpers::{periphery_client, update::update_update},
monitor::update_cache_for_server,
resource,
permission::get_check_permissions,
state::action_states,
};
@@ -27,10 +27,10 @@ impl Resolve<ExecuteArgs> for StartContainer {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -81,10 +81,10 @@ impl Resolve<ExecuteArgs> for RestartContainer {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -137,10 +137,10 @@ impl Resolve<ExecuteArgs> for PauseContainer {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -191,10 +191,10 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -247,10 +247,10 @@ impl Resolve<ExecuteArgs> for StopContainer {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -309,10 +309,10 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
signal,
time,
} = self;
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -365,10 +365,10 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -415,10 +415,10 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -467,10 +467,10 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -517,10 +517,10 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -569,10 +569,10 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -619,10 +619,10 @@ impl Resolve<ExecuteArgs> for PruneContainers {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -675,10 +675,10 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -726,10 +726,10 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -780,10 +780,10 @@ impl Resolve<ExecuteArgs> for DeleteImage {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -828,10 +828,10 @@ impl Resolve<ExecuteArgs> for PruneImages {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -880,10 +880,10 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -931,10 +931,10 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -983,10 +983,10 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -1035,10 +1035,10 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -1087,10 +1087,10 @@ impl Resolve<ExecuteArgs> for PruneSystem {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;

View File

@@ -29,6 +29,7 @@ use crate::{
update::{add_update_without_send, update_update},
},
monitor::update_cache_for_server,
permission::get_check_permissions,
resource,
stack::{execute::execute_compose, get_stack_and_server},
state::{action_states, db_client},
@@ -69,7 +70,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
let (mut stack, server) = get_stack_and_server(
&self.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
true,
)
.await?;
@@ -320,10 +321,10 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> serror::Result<Update> {
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
RefreshStackCache {
@@ -402,11 +403,8 @@ impl Resolve<ExecuteArgs> for BatchPullStack {
ExecuteArgs { user, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchPullStack>(
&self.pattern,
user,
)
.await?,
super::batch_execute::<BatchPullStack>(&self.pattern, user)
.await?,
)
}
}
@@ -498,7 +496,7 @@ impl Resolve<ExecuteArgs> for PullStack {
let (stack, server) = get_stack_and_server(
&self.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
true,
)
.await?;

View File

@@ -29,7 +29,7 @@ use resolver_api::Resolve;
use crate::{
api::write::WriteArgs,
helpers::{query::get_id_to_tags, update::update_update},
resource,
permission::get_check_permissions,
state::{action_states, db_client},
sync::{
AllResourcesById, ResourceSyncTrait,
@@ -54,9 +54,11 @@ impl Resolve<ExecuteArgs> for RunSync {
resource_type: match_resource_type,
resources: match_resources,
} = self;
let sync = resource::get_check_permissions::<
entities::sync::ResourceSync,
>(&sync, user, PermissionLevel::Execute)
let sync = get_check_permissions::<entities::sync::ResourceSync>(
&sync,
user,
PermissionLevel::Execute.into(),
)
.await?;
// get the action state for the sync (or insert default).

View File

@@ -12,6 +12,7 @@ use resolver_api::Resolve;
use crate::{
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_state_cache, action_states},
};
@@ -24,10 +25,10 @@ impl Resolve<ReadArgs> for GetAction {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Action> {
Ok(
resource::get_check_permissions::<Action>(
get_check_permissions::<Action>(
&self.action,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -45,8 +46,13 @@ impl Resolve<ReadArgs> for ListActions {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Action>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Action>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -63,7 +69,10 @@ impl Resolve<ReadArgs> for ListFullActions {
};
Ok(
resource::list_full_for_user::<Action>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -75,10 +84,10 @@ impl Resolve<ReadArgs> for GetActionActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ActionActionState> {
let action = resource::get_check_permissions::<Action>(
let action = get_check_permissions::<Action>(
&self.action,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -99,6 +108,7 @@ impl Resolve<ReadArgs> for GetActionsSummary {
let actions = resource::list_full_for_user::<Action>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await

View File

@@ -16,7 +16,7 @@ use mungos::{
use resolver_api::Resolve;
use crate::{
config::core_config, resource::get_resource_ids_for_user,
config::core_config, permission::get_resource_ids_for_user,
state::db_client,
};

View File

@@ -11,7 +11,8 @@ use mungos::mongodb::bson::doc;
use resolver_api::Resolve;
use crate::{
helpers::query::get_all_tags, resource, state::db_client,
helpers::query::get_all_tags, permission::get_check_permissions,
resource, state::db_client,
};
use super::ReadArgs;
@@ -22,10 +23,10 @@ impl Resolve<ReadArgs> for GetAlerter {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Alerter> {
Ok(
resource::get_check_permissions::<Alerter>(
get_check_permissions::<Alerter>(
&self.alerter,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -43,8 +44,13 @@ impl Resolve<ReadArgs> for ListAlerters {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Alerter>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Alerter>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -61,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullAlerters {
};
Ok(
resource::list_full_for_user::<Alerter>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)

View File

@@ -22,6 +22,7 @@ use resolver_api::Resolve;
use crate::{
config::core_config,
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{
action_states, build_state_cache, db_client, github_client,
@@ -36,10 +37,10 @@ impl Resolve<ReadArgs> for GetBuild {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Build> {
Ok(
resource::get_check_permissions::<Build>(
get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -57,8 +58,13 @@ impl Resolve<ReadArgs> for ListBuilds {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Build>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Build>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -75,7 +81,10 @@ impl Resolve<ReadArgs> for ListFullBuilds {
};
Ok(
resource::list_full_for_user::<Build>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -87,10 +96,10 @@ impl Resolve<ReadArgs> for GetBuildActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<BuildActionState> {
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -111,6 +120,7 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
let builds = resource::list_full_for_user::<Build>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -218,10 +228,10 @@ impl Resolve<ReadArgs> for ListBuildVersions {
patch,
limit,
} = self;
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&build,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
@@ -274,7 +284,10 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
get_all_tags(None).await?
};
let builds = resource::list_full_for_user::<Build>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await
.context("failed to get resources matching query")?;
@@ -306,10 +319,10 @@ impl Resolve<ReadArgs> for GetBuildWebhookEnabled {
});
};
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;

View File

@@ -11,7 +11,8 @@ use mungos::mongodb::bson::doc;
use resolver_api::Resolve;
use crate::{
helpers::query::get_all_tags, resource, state::db_client,
helpers::query::get_all_tags, permission::get_check_permissions,
resource, state::db_client,
};
use super::ReadArgs;
@@ -22,10 +23,10 @@ impl Resolve<ReadArgs> for GetBuilder {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Builder> {
Ok(
resource::get_check_permissions::<Builder>(
get_check_permissions::<Builder>(
&self.builder,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -43,8 +44,13 @@ impl Resolve<ReadArgs> for ListBuilders {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Builder>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Builder>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -61,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullBuilders {
};
Ok(
resource::list_full_for_user::<Builder>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)

View File

@@ -8,19 +8,22 @@ use komodo_client::{
Deployment, DeploymentActionState, DeploymentConfig,
DeploymentListItem, DeploymentState,
},
docker::container::ContainerStats,
docker::container::{Container, ContainerStats},
permission::PermissionLevel,
server::Server,
server::{Server, ServerState},
update::Log,
},
};
use periphery_client::api;
use periphery_client::api::{self, container::InspectContainer};
use resolver_api::Resolve;
use crate::{
helpers::{periphery_client, query::get_all_tags},
permission::get_check_permissions,
resource,
state::{action_states, deployment_status_cache},
state::{
action_states, deployment_status_cache, server_status_cache,
},
};
use super::ReadArgs;
@@ -31,10 +34,10 @@ impl Resolve<ReadArgs> for GetDeployment {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Deployment> {
Ok(
resource::get_check_permissions::<Deployment>(
get_check_permissions::<Deployment>(
&self.deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -53,7 +56,10 @@ impl Resolve<ReadArgs> for ListDeployments {
};
let only_update_available = self.query.specific.update_available;
let deployments = resource::list_for_user::<Deployment>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?;
let deployments = if only_update_available {
@@ -80,7 +86,10 @@ impl Resolve<ReadArgs> for ListFullDeployments {
};
Ok(
resource::list_full_for_user::<Deployment>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -92,10 +101,10 @@ impl Resolve<ReadArgs> for GetDeploymentContainer {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetDeploymentContainerResponse> {
let deployment = resource::get_check_permissions::<Deployment>(
let deployment = get_check_permissions::<Deployment>(
&self.deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let status = deployment_status_cache()
@@ -126,10 +135,10 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
name,
config: DeploymentConfig { server_id, .. },
..
} = resource::get_check_permissions::<Deployment>(
} = get_check_permissions::<Deployment>(
&deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.logs(),
)
.await?;
if server_id.is_empty() {
@@ -164,10 +173,10 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
name,
config: DeploymentConfig { server_id, .. },
..
} = resource::get_check_permissions::<Deployment>(
} = get_check_permissions::<Deployment>(
&deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.logs(),
)
.await?;
if server_id.is_empty() {
@@ -188,6 +197,50 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
}
}
impl Resolve<ReadArgs> for InspectDeploymentContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
let InspectDeploymentContainer { deployment } = self;
let Deployment {
name,
config: DeploymentConfig { server_id, .. },
..
} = get_check_permissions::<Deployment>(
&deployment,
user,
PermissionLevel::Read.inspect(),
)
.await?;
if server_id.is_empty() {
return Err(
anyhow!(
"Cannot inspect deployment, not attached to any server"
)
.into(),
);
}
let server = resource::get::<Server>(&server_id).await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect container: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)?
.request(InspectContainer { name })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for GetDeploymentStats {
async fn resolve(
self,
@@ -197,10 +250,10 @@ impl Resolve<ReadArgs> for GetDeploymentStats {
name,
config: DeploymentConfig { server_id, .. },
..
} = resource::get_check_permissions::<Deployment>(
} = get_check_permissions::<Deployment>(
&self.deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
if server_id.is_empty() {
@@ -222,10 +275,10 @@ impl Resolve<ReadArgs> for GetDeploymentActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<DeploymentActionState> {
let deployment = resource::get_check_permissions::<Deployment>(
let deployment = get_check_permissions::<Deployment>(
&self.deployment,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -246,6 +299,7 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
let deployments = resource::list_full_for_user::<Deployment>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -289,7 +343,10 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
get_all_tags(None).await?
};
let deployments = resource::list_full_for_user::<Deployment>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await
.context("failed to get resources matching query")?;

View File

@@ -11,6 +11,7 @@ use komodo_client::{
build::Build,
builder::{Builder, BuilderConfig},
config::{DockerRegistry, GitProvider},
permission::PermissionLevel,
repo::Repo,
server::Server,
sync::ResourceSync,
@@ -71,7 +72,7 @@ enum ReadRequest {
// ==== USER ====
GetUsername(GetUsername),
GetPermissionLevel(GetPermissionLevel),
GetPermission(GetPermission),
FindUser(FindUser),
ListUsers(ListUsers),
ListApiKeys(ListApiKeys),
@@ -123,6 +124,25 @@ enum ReadRequest {
ListComposeProjects(ListComposeProjects),
ListTerminals(ListTerminals),
// ==== SERVER STATS ====
GetSystemInformation(GetSystemInformation),
GetSystemStats(GetSystemStats),
ListSystemProcesses(ListSystemProcesses),
// ==== STACK ====
GetStacksSummary(GetStacksSummary),
GetStack(GetStack),
GetStackActionState(GetStackActionState),
GetStackWebhooksEnabled(GetStackWebhooksEnabled),
GetStackLog(GetStackLog),
SearchStackLog(SearchStackLog),
InspectStackContainer(InspectStackContainer),
ListStacks(ListStacks),
ListFullStacks(ListFullStacks),
ListStackServices(ListStackServices),
ListCommonStackExtraArgs(ListCommonStackExtraArgs),
ListCommonStackBuildExtraArgs(ListCommonStackBuildExtraArgs),
// ==== DEPLOYMENT ====
GetDeploymentsSummary(GetDeploymentsSummary),
GetDeployment(GetDeployment),
@@ -131,6 +151,7 @@ enum ReadRequest {
GetDeploymentStats(GetDeploymentStats),
GetDeploymentLog(GetDeploymentLog),
SearchDeploymentLog(SearchDeploymentLog),
InspectDeploymentContainer(InspectDeploymentContainer),
ListDeployments(ListDeployments),
ListFullDeployments(ListFullDeployments),
ListCommonDeploymentExtraArgs(ListCommonDeploymentExtraArgs),
@@ -162,19 +183,6 @@ enum ReadRequest {
ListResourceSyncs(ListResourceSyncs),
ListFullResourceSyncs(ListFullResourceSyncs),
// ==== STACK ====
GetStacksSummary(GetStacksSummary),
GetStack(GetStack),
GetStackActionState(GetStackActionState),
GetStackWebhooksEnabled(GetStackWebhooksEnabled),
GetStackLog(GetStackLog),
SearchStackLog(SearchStackLog),
ListStacks(ListStacks),
ListFullStacks(ListFullStacks),
ListStackServices(ListStackServices),
ListCommonStackExtraArgs(ListCommonStackExtraArgs),
ListCommonStackBuildExtraArgs(ListCommonStackBuildExtraArgs),
// ==== BUILDER ====
GetBuildersSummary(GetBuildersSummary),
GetBuilder(GetBuilder),
@@ -203,11 +211,6 @@ enum ReadRequest {
ListAlerts(ListAlerts),
GetAlert(GetAlert),
// ==== SERVER STATS ====
GetSystemInformation(GetSystemInformation),
GetSystemStats(GetSystemStats),
ListSystemProcesses(ListSystemProcesses),
// ==== VARIABLE ====
GetVariable(GetVariable),
ListVariables(ListVariables),
@@ -396,16 +399,19 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
resource::list_full_for_user::<Build>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[]
),
resource::list_full_for_user::<Repo>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[]
),
resource::list_full_for_user::<ResourceSync>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[]
),
)?;

View File

@@ -1,7 +1,7 @@
use anyhow::{Context, anyhow};
use komodo_client::{
api::read::{
GetPermissionLevel, GetPermissionLevelResponse, ListPermissions,
GetPermission, GetPermissionResponse, ListPermissions,
ListPermissionsResponse, ListUserTargetPermissions,
ListUserTargetPermissionsResponse,
},
@@ -35,13 +35,13 @@ impl Resolve<ReadArgs> for ListPermissions {
}
}
impl Resolve<ReadArgs> for GetPermissionLevel {
impl Resolve<ReadArgs> for GetPermission {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetPermissionLevelResponse> {
) -> serror::Result<GetPermissionResponse> {
if user.admin {
return Ok(PermissionLevel::Write);
return Ok(PermissionLevel::Write.all());
}
Ok(get_user_permission_on_target(user, &self.target).await?)
}

View File

@@ -10,6 +10,7 @@ use resolver_api::Resolve;
use crate::{
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_states, procedure_state_cache},
};
@@ -22,10 +23,10 @@ impl Resolve<ReadArgs> for GetProcedure {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetProcedureResponse> {
Ok(
resource::get_check_permissions::<Procedure>(
get_check_permissions::<Procedure>(
&self.procedure,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -44,7 +45,10 @@ impl Resolve<ReadArgs> for ListProcedures {
};
Ok(
resource::list_for_user::<Procedure>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -63,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullProcedures {
};
Ok(
resource::list_full_for_user::<Procedure>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -78,6 +85,7 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
let procedures = resource::list_full_for_user::<Procedure>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -120,10 +128,10 @@ impl Resolve<ReadArgs> for GetProcedureActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetProcedureActionStateResponse> {
let procedure = resource::get_check_permissions::<Procedure>(
let procedure = get_check_permissions::<Procedure>(
&self.procedure,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()

View File

@@ -12,6 +12,7 @@ use resolver_api::Resolve;
use crate::{
config::core_config,
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_states, github_client, repo_state_cache},
};
@@ -24,10 +25,10 @@ impl Resolve<ReadArgs> for GetRepo {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Repo> {
Ok(
resource::get_check_permissions::<Repo>(
get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -45,8 +46,13 @@ impl Resolve<ReadArgs> for ListRepos {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Repo>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Repo>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -63,7 +69,10 @@ impl Resolve<ReadArgs> for ListFullRepos {
};
Ok(
resource::list_full_for_user::<Repo>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -75,10 +84,10 @@ impl Resolve<ReadArgs> for GetRepoActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<RepoActionState> {
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -99,6 +108,7 @@ impl Resolve<ReadArgs> for GetReposSummary {
let repos = resource::list_full_for_user::<Repo>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -160,10 +170,10 @@ impl Resolve<ReadArgs> for GetRepoWebhooksEnabled {
});
};
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;

View File

@@ -51,6 +51,7 @@ use crate::{
periphery_client,
query::{get_all_tags, get_system_info},
},
permission::get_check_permissions,
resource,
stack::compose_container_match_regex,
state::{action_states, db_client, server_status_cache},
@@ -66,6 +67,7 @@ impl Resolve<ReadArgs> for GetServersSummary {
let servers = resource::list_for_user::<Server>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await?;
@@ -93,10 +95,10 @@ impl Resolve<ReadArgs> for GetPeripheryVersion {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetPeripheryVersionResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let version = server_status_cache()
@@ -114,10 +116,10 @@ impl Resolve<ReadArgs> for GetServer {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Server> {
Ok(
resource::get_check_permissions::<Server>(
get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -135,8 +137,13 @@ impl Resolve<ReadArgs> for ListServers {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Server>(self.query, user, &all_tags)
.await?,
resource::list_for_user::<Server>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
@@ -153,7 +160,10 @@ impl Resolve<ReadArgs> for ListFullServers {
};
Ok(
resource::list_full_for_user::<Server>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -165,10 +175,10 @@ impl Resolve<ReadArgs> for GetServerState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetServerStateResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let status = server_status_cache()
@@ -187,10 +197,10 @@ impl Resolve<ReadArgs> for GetServerActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ServerActionState> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -208,10 +218,10 @@ impl Resolve<ReadArgs> for GetSystemInformation {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SystemInformation> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
get_system_info(&server).await.map_err(Into::into)
@@ -223,10 +233,10 @@ impl Resolve<ReadArgs> for GetSystemStats {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSystemStatsResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let status =
@@ -255,10 +265,10 @@ impl Resolve<ReadArgs> for ListSystemProcesses {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSystemProcessesResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.processes(),
)
.await?;
let mut lock = processes_cache().lock().await;
@@ -294,10 +304,10 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
granularity,
page,
} = self;
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let granularity =
@@ -342,10 +352,10 @@ impl Resolve<ReadArgs> for ListDockerContainers {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerContainersResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -367,6 +377,7 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
let servers = resource::list_for_user::<Server>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await?
@@ -400,6 +411,7 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
let servers = resource::list_full_for_user::<Server>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -436,10 +448,10 @@ impl Resolve<ReadArgs> for InspectDockerContainer {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.inspect(),
)
.await?;
let cache = server_status_cache()
@@ -476,10 +488,10 @@ impl Resolve<ReadArgs> for GetContainerLog {
tail,
timestamps,
} = self;
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)?
@@ -507,10 +519,10 @@ impl Resolve<ReadArgs> for SearchContainerLog {
invert,
timestamps,
} = self;
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)?
@@ -532,10 +544,10 @@ impl Resolve<ReadArgs> for GetResourceMatchingContainer {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetResourceMatchingContainerResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
// first check deployments
@@ -593,10 +605,10 @@ impl Resolve<ReadArgs> for ListDockerNetworks {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerNetworksResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -615,10 +627,10 @@ impl Resolve<ReadArgs> for InspectDockerNetwork {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Network> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -645,10 +657,10 @@ impl Resolve<ReadArgs> for ListDockerImages {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerImagesResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -667,10 +679,10 @@ impl Resolve<ReadArgs> for InspectDockerImage {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Image> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -694,10 +706,10 @@ impl Resolve<ReadArgs> for ListDockerImageHistory {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<ImageHistoryResponseItem>> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -724,10 +736,10 @@ impl Resolve<ReadArgs> for ListDockerVolumes {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerVolumesResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -746,10 +758,10 @@ impl Resolve<ReadArgs> for InspectDockerVolume {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Volume> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -773,10 +785,10 @@ impl Resolve<ReadArgs> for ListComposeProjects {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListComposeProjectsResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
@@ -832,10 +844,10 @@ impl Resolve<ReadArgs> for ListTerminals {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListTerminalsResponse> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read,
PermissionLevel::Read.terminal(),
)
.await?;
let cache = terminals_cache().get_or_insert(server.id.clone());

View File

@@ -1,25 +1,32 @@
use std::collections::HashSet;
use anyhow::Context;
use anyhow::{Context, anyhow};
use komodo_client::{
api::read::*,
entities::{
config::core::CoreConfig,
docker::container::Container,
permission::PermissionLevel,
server::{Server, ServerState},
stack::{Stack, StackActionState, StackListItem, StackState},
},
};
use periphery_client::api::compose::{
GetComposeLog, GetComposeLogSearch,
use periphery_client::api::{
compose::{GetComposeLog, GetComposeLogSearch},
container::InspectContainer,
};
use resolver_api::Resolve;
use crate::{
config::core_config,
helpers::{periphery_client, query::get_all_tags},
permission::get_check_permissions,
resource,
stack::get_stack_and_server,
state::{action_states, github_client, stack_status_cache},
state::{
action_states, github_client, server_status_cache,
stack_status_cache,
},
};
use super::ReadArgs;
@@ -30,10 +37,10 @@ impl Resolve<ReadArgs> for GetStack {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Stack> {
Ok(
resource::get_check_permissions::<Stack>(
get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -45,10 +52,10 @@ impl Resolve<ReadArgs> for ListStackServices {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListStackServicesResponse> {
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
@@ -75,9 +82,13 @@ impl Resolve<ReadArgs> for GetStackLog {
tail,
timestamps,
} = self;
let (stack, server) =
get_stack_and_server(&stack, user, PermissionLevel::Read, true)
.await?;
let (stack, server) = get_stack_and_server(
&stack,
user,
PermissionLevel::Read.logs(),
true,
)
.await?;
let res = periphery_client(&server)?
.request(GetComposeLog {
project: stack.project_name(false),
@@ -104,9 +115,13 @@ impl Resolve<ReadArgs> for SearchStackLog {
invert,
timestamps,
} = self;
let (stack, server) =
get_stack_and_server(&stack, user, PermissionLevel::Read, true)
.await?;
let (stack, server) = get_stack_and_server(
&stack,
user,
PermissionLevel::Read.logs(),
true,
)
.await?;
let res = periphery_client(&server)?
.request(GetComposeLogSearch {
project: stack.project_name(false),
@@ -122,6 +137,60 @@ impl Resolve<ReadArgs> for SearchStackLog {
}
}
impl Resolve<ReadArgs> for InspectStackContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
let InspectStackContainer { stack, service } = self;
let stack = get_check_permissions::<Stack>(
&stack,
user,
PermissionLevel::Read.inspect(),
)
.await?;
if stack.config.server_id.is_empty() {
return Err(
anyhow!("Cannot inspect stack, not attached to any server")
.into(),
);
}
let server =
resource::get::<Server>(&stack.config.server_id).await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect container: server is {:?}",
cache.state
)
.into(),
);
}
let services = &stack_status_cache()
.get(&stack.id)
.await
.unwrap_or_default()
.curr
.services;
let Some(name) = services
.into_iter()
.find(|s| s.service == service)
.and_then(|s| s.container.as_ref().map(|c| c.name.clone()))
else {
return Err(anyhow!(
"No service found matching '{service}'. Was the stack last deployed manually?"
).into());
};
let res = periphery_client(&server)?
.request(InspectContainer { name })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
async fn resolve(
self,
@@ -133,7 +202,10 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
get_all_tags(None).await?
};
let stacks = resource::list_full_for_user::<Stack>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await
.context("failed to get resources matching query")?;
@@ -164,7 +236,10 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
get_all_tags(None).await?
};
let stacks = resource::list_full_for_user::<Stack>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await
.context("failed to get resources matching query")?;
@@ -195,9 +270,13 @@ impl Resolve<ReadArgs> for ListStacks {
get_all_tags(None).await?
};
let only_update_available = self.query.specific.update_available;
let stacks =
resource::list_for_user::<Stack>(self.query, user, &all_tags)
.await?;
let stacks = resource::list_for_user::<Stack>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?;
let stacks = if only_update_available {
stacks
.into_iter()
@@ -228,7 +307,10 @@ impl Resolve<ReadArgs> for ListFullStacks {
};
Ok(
resource::list_full_for_user::<Stack>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -240,10 +322,10 @@ impl Resolve<ReadArgs> for GetStackActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<StackActionState> {
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -264,6 +346,7 @@ impl Resolve<ReadArgs> for GetStacksSummary {
let stacks = resource::list_full_for_user::<Stack>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -302,10 +385,10 @@ impl Resolve<ReadArgs> for GetStackWebhooksEnabled {
});
};
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;

View File

@@ -14,6 +14,7 @@ use resolver_api::Resolve;
use crate::{
config::core_config,
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_states, github_client},
};
@@ -26,10 +27,10 @@ impl Resolve<ReadArgs> for GetResourceSync {
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ResourceSync> {
Ok(
resource::get_check_permissions::<ResourceSync>(
get_check_permissions::<ResourceSync>(
&self.sync,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?,
)
@@ -48,7 +49,10 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
};
Ok(
resource::list_for_user::<ResourceSync>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -67,7 +71,10 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
};
Ok(
resource::list_full_for_user::<ResourceSync>(
self.query, user, &all_tags,
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
@@ -79,10 +86,10 @@ impl Resolve<ReadArgs> for GetResourceSyncActionState {
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ResourceSyncActionState> {
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
@@ -104,6 +111,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
resource::list_full_for_user::<ResourceSync>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
@@ -160,10 +168,10 @@ impl Resolve<ReadArgs> for GetSyncWebhooksEnabled {
});
};
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;

View File

@@ -20,12 +20,14 @@ use crate::{
helpers::query::{
get_all_tags, get_id_to_tags, get_user_user_group_ids,
},
permission::get_check_permissions,
resource,
state::db_client,
sync::{
AllResourcesById,
toml::{TOML_PRETTY_OPTIONS, ToToml, convert_resource},
user_groups::convert_user_groups,
toml::{ToToml, convert_resource},
user_groups::{convert_user_groups, user_group_to_toml},
variables::variable_to_toml,
},
};
@@ -45,6 +47,7 @@ async fn get_all_targets(
resource::list_for_user::<Alerter>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -55,6 +58,7 @@ async fn get_all_targets(
resource::list_for_user::<Builder>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -65,6 +69,7 @@ async fn get_all_targets(
resource::list_for_user::<Server>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -75,6 +80,7 @@ async fn get_all_targets(
resource::list_for_user::<Stack>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -85,6 +91,7 @@ async fn get_all_targets(
resource::list_for_user::<Deployment>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -95,6 +102,7 @@ async fn get_all_targets(
resource::list_for_user::<Build>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -105,6 +113,7 @@ async fn get_all_targets(
resource::list_for_user::<Repo>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -115,6 +124,7 @@ async fn get_all_targets(
resource::list_for_user::<Procedure>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -125,6 +135,7 @@ async fn get_all_targets(
resource::list_for_user::<Action>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -135,6 +146,7 @@ async fn get_all_targets(
resource::list_full_for_user::<ResourceSync>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
@@ -198,10 +210,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
for target in targets {
match target {
ResourceTarget::Alerter(id) => {
let alerter = resource::get_check_permissions::<Alerter>(
let alerter = get_check_permissions::<Alerter>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
res.alerters.push(convert_resource::<Alerter>(
@@ -212,10 +224,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::ResourceSync(id) => {
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
if sync.config.file_contents.is_empty()
@@ -231,10 +243,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
}
}
ResourceTarget::Server(id) => {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
res.servers.push(convert_resource::<Server>(
@@ -245,13 +257,12 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Builder(id) => {
let mut builder =
resource::get_check_permissions::<Builder>(
&id,
user,
PermissionLevel::Read,
)
.await?;
let mut builder = get_check_permissions::<Builder>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Builder::replace_ids(&mut builder, &all);
res.builders.push(convert_resource::<Builder>(
builder,
@@ -261,10 +272,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Build(id) => {
let mut build = resource::get_check_permissions::<Build>(
let mut build = get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
Build::replace_ids(&mut build, &all);
@@ -276,10 +287,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Deployment(id) => {
let mut deployment = resource::get_check_permissions::<
Deployment,
>(
&id, user, PermissionLevel::Read
let mut deployment = get_check_permissions::<Deployment>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Deployment::replace_ids(&mut deployment, &all);
@@ -291,10 +302,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Repo(id) => {
let mut repo = resource::get_check_permissions::<Repo>(
let mut repo = get_check_permissions::<Repo>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
Repo::replace_ids(&mut repo, &all);
@@ -306,10 +317,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Stack(id) => {
let mut stack = resource::get_check_permissions::<Stack>(
let mut stack = get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
Stack::replace_ids(&mut stack, &all);
@@ -321,10 +332,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
ResourceTarget::Procedure(id) => {
let mut procedure = resource::get_check_permissions::<
Procedure,
>(
&id, user, PermissionLevel::Read
let mut procedure = get_check_permissions::<Procedure>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Procedure::replace_ids(&mut procedure, &all);
@@ -336,10 +347,10 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
));
}
ResourceTarget::Action(id) => {
let mut action = resource::get_check_permissions::<Action>(
let mut action = get_check_permissions::<Action>(
&id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
Action::replace_ids(&mut action, &all);
@@ -490,22 +501,14 @@ fn serialize_resources_toml(
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[variable]]\n");
toml.push_str(
&toml_pretty::to_string(variable, TOML_PRETTY_OPTIONS)
.context("failed to serialize variables to toml")?,
);
toml.push_str(&variable_to_toml(variable)?);
}
for user_group in &resources.user_groups {
for user_group in resources.user_groups {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[user_group]]\n");
toml.push_str(
&toml_pretty::to_string(user_group, TOML_PRETTY_OPTIONS)
.context("failed to serialize user_groups to toml")?,
);
toml.push_str(&user_group_to_toml(user_group)?);
}
Ok(toml)

View File

@@ -27,7 +27,11 @@ use mungos::{
};
use resolver_api::Resolve;
use crate::{config::core_config, resource, state::db_client};
use crate::{
config::core_config,
permission::{get_check_permissions, get_resource_ids_for_user},
state::db_client,
};
use super::ReadArgs;
@@ -41,18 +45,17 @@ impl Resolve<ReadArgs> for ListUpdates {
let query = if user.admin || core_config().transparent_mode {
self.query
} else {
let server_query =
resource::get_resource_ids_for_user::<Server>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Server", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Server" });
let server_query = get_resource_ids_for_user::<Server>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Server", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Server" });
let deployment_query =
resource::get_resource_ids_for_user::<Deployment>(user)
get_resource_ids_for_user::<Deployment>(user)
.await?
.map(|ids| {
doc! {
@@ -61,38 +64,35 @@ impl Resolve<ReadArgs> for ListUpdates {
})
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
let stack_query =
resource::get_resource_ids_for_user::<Stack>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Stack", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Stack" });
let stack_query = get_resource_ids_for_user::<Stack>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Stack", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Stack" });
let build_query =
resource::get_resource_ids_for_user::<Build>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Build", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Build" });
let build_query = get_resource_ids_for_user::<Build>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Build", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Build" });
let repo_query =
resource::get_resource_ids_for_user::<Repo>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Repo", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Repo" });
let repo_query = get_resource_ids_for_user::<Repo>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Repo", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Repo" });
let procedure_query =
resource::get_resource_ids_for_user::<Procedure>(user)
get_resource_ids_for_user::<Procedure>(user)
.await?
.map(|ids| {
doc! {
@@ -101,47 +101,43 @@ impl Resolve<ReadArgs> for ListUpdates {
})
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
let action_query =
resource::get_resource_ids_for_user::<Action>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Action", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Action" });
let builder_query =
resource::get_resource_ids_for_user::<Builder>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Builder", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Builder" });
let alerter_query =
resource::get_resource_ids_for_user::<Alerter>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Alerter", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
let resource_sync_query =
resource::get_resource_ids_for_user::<ResourceSync>(
user,
)
let action_query = get_resource_ids_for_user::<Action>(user)
.await?
.map(|ids| {
doc! {
"target.type": "ResourceSync", "target.id": { "$in": ids }
"target.type": "Action", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
.unwrap_or_else(|| doc! { "target.type": "Action" });
let builder_query = get_resource_ids_for_user::<Builder>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Builder", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Builder" });
let alerter_query = get_resource_ids_for_user::<Alerter>(user)
.await?
.map(|ids| {
doc! {
"target.type": "Alerter", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
let resource_sync_query = get_resource_ids_for_user::<
ResourceSync,
>(user)
.await?
.map(|ids| {
doc! {
"target.type": "ResourceSync", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
let mut query = self.query.unwrap_or_default();
query.extend(doc! {
@@ -233,82 +229,82 @@ impl Resolve<ReadArgs> for GetUpdate {
);
}
ResourceTarget::Server(id) => {
resource::get_check_permissions::<Server>(
get_check_permissions::<Server>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Deployment(id) => {
resource::get_check_permissions::<Deployment>(
get_check_permissions::<Deployment>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Build(id) => {
resource::get_check_permissions::<Build>(
get_check_permissions::<Build>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Repo(id) => {
resource::get_check_permissions::<Repo>(
get_check_permissions::<Repo>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Builder(id) => {
resource::get_check_permissions::<Builder>(
get_check_permissions::<Builder>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Alerter(id) => {
resource::get_check_permissions::<Alerter>(
get_check_permissions::<Alerter>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Procedure(id) => {
resource::get_check_permissions::<Procedure>(
get_check_permissions::<Procedure>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Action(id) => {
resource::get_check_permissions::<Action>(
get_check_permissions::<Action>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::ResourceSync(id) => {
resource::get_check_permissions::<ResourceSync>(
get_check_permissions::<ResourceSync>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Stack(id) => {
resource::get_check_permissions::<Stack>(
get_check_permissions::<Stack>(
id,
user,
PermissionLevel::Read,
PermissionLevel::Read.into(),
)
.await?;
}

View File

@@ -10,7 +10,8 @@ use serror::Json;
use uuid::Uuid;
use crate::{
auth::auth_request, helpers::periphery_client, resource,
auth::auth_request, helpers::periphery_client,
permission::get_check_permissions,
};
pub fn router() -> Router {
@@ -45,10 +46,10 @@ async fn execute_inner(
info!("/terminal request | user: {}", user.username);
let res = async {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Write,
PermissionLevel::Read.terminal(),
)
.await?;

View File

@@ -6,7 +6,7 @@ use komodo_client::{
};
use resolver_api::Resolve;
use crate::resource;
use crate::{permission::get_check_permissions, resource};
use super::WriteArgs;
@@ -29,13 +29,12 @@ impl Resolve<WriteArgs> for CopyAction {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Action> {
let Action { config, .. } =
resource::get_check_permissions::<Action>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Action { config, .. } = get_check_permissions::<Action>(
&self.id,
user,
PermissionLevel::Write.into(),
)
.await?;
Ok(
resource::create::<Action>(&self.name, config.into(), user)
.await?,

View File

@@ -6,7 +6,7 @@ use komodo_client::{
};
use resolver_api::Resolve;
use crate::resource;
use crate::{permission::get_check_permissions, resource};
use super::WriteArgs;
@@ -29,13 +29,12 @@ impl Resolve<WriteArgs> for CopyAlerter {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Alerter> {
let Alerter { config, .. } =
resource::get_check_permissions::<Alerter>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Alerter { config, .. } = get_check_permissions::<Alerter>(
&self.id,
user,
PermissionLevel::Write.into(),
)
.await?;
Ok(
resource::create::<Alerter>(&self.name, config.into(), user)
.await?,

View File

@@ -36,6 +36,7 @@ use crate::{
query::get_server_with_state,
update::{add_update, make_update},
},
permission::get_check_permissions,
resource,
state::{db_client, github_client},
};
@@ -61,13 +62,12 @@ impl Resolve<WriteArgs> for CopyBuild {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Build> {
let Build { mut config, .. } =
resource::get_check_permissions::<Build>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Build { mut config, .. } = get_check_permissions::<Build>(
&self.id,
user,
PermissionLevel::Read.into(),
)
.await?;
// reset version to 0.0.0
config.version = Default::default();
Ok(
@@ -107,10 +107,10 @@ impl Resolve<WriteArgs> for RenameBuild {
impl Resolve<WriteArgs> for WriteBuildFileContents {
#[instrument(name = "WriteBuildFileContents", skip(args))]
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
&args.user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -294,10 +294,10 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
) -> serror::Result<NoData> {
// 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>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -493,10 +493,10 @@ impl Resolve<WriteArgs> for CreateBuildWebhook {
let WriteArgs { user } = args;
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -606,10 +606,10 @@ impl Resolve<WriteArgs> for DeleteBuildWebhook {
);
};
let build = resource::get_check_permissions::<Build>(
let build = get_check_permissions::<Build>(
&self.build,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;

View File

@@ -6,7 +6,7 @@ use komodo_client::{
};
use resolver_api::Resolve;
use crate::resource;
use crate::{permission::get_check_permissions, resource};
use super::WriteArgs;
@@ -29,13 +29,12 @@ impl Resolve<WriteArgs> for CopyBuilder {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Builder> {
let Builder { config, .. } =
resource::get_check_permissions::<Builder>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Builder { config, .. } = get_check_permissions::<Builder>(
&self.id,
user,
PermissionLevel::Write.into(),
)
.await?;
Ok(
resource::create::<Builder>(&self.name, config.into(), user)
.await?,

View File

@@ -11,7 +11,7 @@ use komodo_client::{
komodo_timestamp,
permission::PermissionLevel,
server::{Server, ServerState},
to_komodo_name,
to_docker_compatible_name,
update::Update,
},
};
@@ -25,6 +25,7 @@ use crate::{
query::get_deployment_state,
update::{add_update, make_update},
},
permission::get_check_permissions,
resource,
state::{action_states, db_client, server_status_cache},
};
@@ -51,10 +52,10 @@ impl Resolve<WriteArgs> for CopyDeployment {
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
let Deployment { config, .. } =
resource::get_check_permissions::<Deployment>(
get_check_permissions::<Deployment>(
&self.id,
user,
PermissionLevel::Write,
PermissionLevel::Read.into(),
)
.await?;
Ok(
@@ -70,10 +71,10 @@ impl Resolve<WriteArgs> for CreateDeploymentFromContainer {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Write,
PermissionLevel::Read.inspect().attach(),
)
.await?;
let cache = server_status_cache()
@@ -188,10 +189,10 @@ impl Resolve<WriteArgs> for RenameDeployment {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
let deployment = resource::get_check_permissions::<Deployment>(
let deployment = get_check_permissions::<Deployment>(
&self.id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -206,7 +207,7 @@ impl Resolve<WriteArgs> for RenameDeployment {
let _action_guard =
action_state.update(|state| state.renaming = true)?;
let name = to_komodo_name(&self.name);
let name = to_docker_compatible_name(&self.name);
let container_state = get_deployment_state(&deployment).await?;

View File

@@ -69,6 +69,7 @@ pub enum WriteRequest {
AddUserToUserGroup(AddUserToUserGroup),
RemoveUserFromUserGroup(RemoveUserFromUserGroup),
SetUsersInUserGroup(SetUsersInUserGroup),
SetEveryoneUserGroup(SetEveryoneUserGroup),
// ==== PERMISSIONS ====
UpdateUserAdmin(UpdateUserAdmin),
@@ -89,6 +90,17 @@ pub enum WriteRequest {
DeleteTerminal(DeleteTerminal),
DeleteAllTerminals(DeleteAllTerminals),
// ==== STACK ====
CreateStack(CreateStack),
CopyStack(CopyStack),
DeleteStack(DeleteStack),
UpdateStack(UpdateStack),
RenameStack(RenameStack),
WriteStackFileContents(WriteStackFileContents),
RefreshStackCache(RefreshStackCache),
CreateStackWebhook(CreateStackWebhook),
DeleteStackWebhook(DeleteStackWebhook),
// ==== DEPLOYMENT ====
CreateDeployment(CreateDeployment),
CopyDeployment(CopyDeployment),
@@ -158,17 +170,6 @@ pub enum WriteRequest {
CreateSyncWebhook(CreateSyncWebhook),
DeleteSyncWebhook(DeleteSyncWebhook),
// ==== STACK ====
CreateStack(CreateStack),
CopyStack(CopyStack),
DeleteStack(DeleteStack),
UpdateStack(UpdateStack),
RenameStack(RenameStack),
WriteStackFileContents(WriteStackFileContents),
RefreshStackCache(RefreshStackCache),
CreateStackWebhook(CreateStackWebhook),
DeleteStackWebhook(DeleteStackWebhook),
// ==== TAG ====
CreateTag(CreateTag),
DeleteTag(DeleteTag),

View File

@@ -11,7 +11,7 @@ use komodo_client::{
use mungos::{
by_id::{find_one_by_id, update_one_by_id},
mongodb::{
bson::{Document, doc, oid::ObjectId},
bson::{Document, doc, oid::ObjectId, to_bson},
options::UpdateOptions,
},
};
@@ -65,6 +65,10 @@ impl Resolve<WriteArgs> for UpdateUserBasePermissions {
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdateUserBasePermissionsResponse> {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
let UpdateUserBasePermissions {
user_id,
enabled,
@@ -72,10 +76,6 @@ impl Resolve<WriteArgs> for UpdateUserBasePermissions {
create_builds,
} = self;
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
let user = find_one_by_id(&db_client().users, &user_id)
.await
.context("failed to query mongo for user")?
@@ -122,16 +122,16 @@ impl Resolve<WriteArgs> for UpdatePermissionOnResourceType {
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdatePermissionOnResourceTypeResponse> {
let UpdatePermissionOnResourceType {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
let Self {
user_target,
resource_type,
permission,
} = self;
if !admin.admin {
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?;
@@ -153,9 +153,11 @@ impl Resolve<WriteArgs> for UpdatePermissionOnResourceType {
let id = ObjectId::from_str(&user_target_id)
.context("id is not ObjectId")?;
let field = format!("all.{resource_type}");
let filter = doc! { "_id": id };
let update = doc! { "$set": { &field: permission.as_ref() } };
let field = format!("all.{resource_type}");
let set =
to_bson(&permission).context("permission is not Bson")?;
let update = doc! { "$set": { &field: &set } };
match user_target_variant {
UserTargetVariant::User => {
@@ -164,7 +166,7 @@ impl Resolve<WriteArgs> for UpdatePermissionOnResourceType {
.update_one(filter, update)
.await
.with_context(|| {
format!("failed to set {field}: {permission} on db")
format!("failed to set {field}: {set} on db")
})?;
}
UserTargetVariant::UserGroup => {
@@ -173,7 +175,7 @@ impl Resolve<WriteArgs> for UpdatePermissionOnResourceType {
.update_one(filter, update)
.await
.with_context(|| {
format!("failed to set {field}: {permission} on db")
format!("failed to set {field}: {set} on db")
})?;
}
}
@@ -188,19 +190,22 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdatePermissionOnTargetResponse> {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
let UpdatePermissionOnTarget {
user_target,
resource_target,
permission,
} = self;
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
// Some extra checks if user target is an actual User
// Some extra checks relevant if user target is an actual User
if let UserTarget::User(user_id) = &user_target {
let user = get_user(user_id).await?;
if !user.enabled {
return Err(anyhow!("user not enabled").into());
}
if user.admin {
return Err(
anyhow!(
@@ -209,9 +214,6 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
.into(),
);
}
if !user.enabled {
return Err(anyhow!("user not enabled").into());
}
}
let (user_target_variant, user_target_id) =
@@ -223,6 +225,9 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
let (user_target_variant, resource_variant) =
(user_target_variant.as_ref(), resource_variant.as_ref());
let specific = to_bson(&permission.specific)
.context("permission.specific is not valid Bson")?;
db_client()
.permissions
.update_one(
@@ -238,7 +243,8 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
"user_target.id": user_target_id,
"resource_target.type": resource_variant,
"resource_target.id": resource_id,
"level": permission.as_ref(),
"level": permission.level.as_ref(),
"specific": specific
}
},
)

View File

@@ -6,7 +6,7 @@ use komodo_client::{
};
use resolver_api::Resolve;
use crate::resource;
use crate::{permission::get_check_permissions, resource};
use super::WriteArgs;
@@ -30,10 +30,10 @@ impl Resolve<WriteArgs> for CopyProcedure {
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CopyProcedureResponse> {
let Procedure { config, .. } =
resource::get_check_permissions::<Procedure>(
get_check_permissions::<Procedure>(
&self.id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
Ok(

View File

@@ -10,7 +10,7 @@ use komodo_client::{
permission::PermissionLevel,
repo::{PartialRepoConfig, Repo, RepoInfo},
server::Server,
to_komodo_name,
to_path_compatible_name,
update::{Log, Update},
},
};
@@ -28,6 +28,7 @@ use crate::{
git_token, periphery_client,
update::{add_update, make_update},
},
permission::get_check_permissions,
resource,
state::{action_states, db_client, github_client},
};
@@ -50,13 +51,12 @@ impl Resolve<WriteArgs> for CopyRepo {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Repo> {
let Repo { config, .. } =
resource::get_check_permissions::<Repo>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Repo { config, .. } = get_check_permissions::<Repo>(
&self.id,
user,
PermissionLevel::Read.into(),
)
.await?;
Ok(
resource::create::<Repo>(&self.name, config.into(), user)
.await?,
@@ -87,10 +87,10 @@ impl Resolve<WriteArgs> for RenameRepo {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -111,7 +111,7 @@ impl Resolve<WriteArgs> for RenameRepo {
let _action_guard =
action_state.update(|state| state.renaming = true)?;
let name = to_komodo_name(&self.name);
let name = to_path_compatible_name(&self.name);
let mut update = make_update(&repo, Operation::RenameRepo, user);
@@ -131,7 +131,7 @@ impl Resolve<WriteArgs> for RenameRepo {
let log = match periphery_client(&server)?
.request(api::git::RenameRepo {
curr_name: to_komodo_name(&repo.name),
curr_name: to_path_compatible_name(&repo.name),
new_name: name.clone(),
})
.await
@@ -169,10 +169,10 @@ impl Resolve<WriteArgs> for RefreshRepoCache {
) -> serror::Result<NoData> {
// 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>(
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -257,10 +257,10 @@ impl Resolve<WriteArgs> for CreateRepoWebhook {
);
};
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.repo,
&args.user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -380,10 +380,10 @@ impl Resolve<WriteArgs> for DeleteRepoWebhook {
);
};
let repo = resource::get_check_permissions::<Repo>(
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;

View File

@@ -6,6 +6,7 @@ use komodo_client::{
NoData, Operation,
permission::PermissionLevel,
server::Server,
to_docker_compatible_name,
update::{Update, UpdateStatus},
},
};
@@ -17,6 +18,7 @@ use crate::{
periphery_client,
update::{add_update, make_update, update_update},
},
permission::get_check_permissions,
resource,
};
@@ -68,10 +70,10 @@ impl Resolve<WriteArgs> for CreateNetwork {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -84,7 +86,7 @@ impl Resolve<WriteArgs> for CreateNetwork {
match periphery
.request(api::network::CreateNetwork {
name: self.name,
name: to_docker_compatible_name(&self.name),
driver: None,
})
.await
@@ -109,10 +111,10 @@ impl Resolve<WriteArgs> for CreateTerminal {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Write,
PermissionLevel::Write.terminal(),
)
.await?;
@@ -137,10 +139,10 @@ impl Resolve<WriteArgs> for DeleteTerminal {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Write,
PermissionLevel::Write.terminal(),
)
.await?;
@@ -163,10 +165,10 @@ impl Resolve<WriteArgs> for DeleteAllTerminals {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
let server = resource::get_check_permissions::<Server>(
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Write,
PermissionLevel::Write.terminal(),
)
.await?;

View File

@@ -30,6 +30,7 @@ use crate::{
query::get_server_with_state,
update::{add_update, make_update},
},
permission::get_check_permissions,
resource,
stack::{
get_stack_and_server,
@@ -60,13 +61,12 @@ impl Resolve<WriteArgs> for CopyStack {
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Stack> {
let Stack { config, .. } =
resource::get_check_permissions::<Stack>(
&self.id,
user,
PermissionLevel::Write,
)
.await?;
let Stack { config, .. } = get_check_permissions::<Stack>(
&self.id,
user,
PermissionLevel::Read.into(),
)
.await?;
Ok(
resource::create::<Stack>(&self.name, config.into(), user)
.await?,
@@ -115,7 +115,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
let (mut stack, server) = get_stack_and_server(
&stack,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
true,
)
.await?;
@@ -229,10 +229,10 @@ impl Resolve<WriteArgs> for RefreshStackCache {
) -> serror::Result<NoData> {
// 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>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
@@ -432,10 +432,10 @@ impl Resolve<WriteArgs> for CreateStackWebhook {
);
};
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -552,10 +552,10 @@ impl Resolve<WriteArgs> for DeleteStackWebhook {
);
};
let stack = resource::get_check_permissions::<Stack>(
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;

View File

@@ -24,7 +24,7 @@ use komodo_client::{
PartialResourceSyncConfig, ResourceSync, ResourceSyncInfo,
SyncDeployUpdate,
},
to_komodo_name,
to_path_compatible_name,
update::{Log, Update},
user::sync_user,
},
@@ -48,6 +48,7 @@ use crate::{
query::get_id_to_tags,
update::{add_update, make_update, update_update},
},
permission::get_check_permissions,
resource,
state::{db_client, github_client},
sync::{
@@ -78,10 +79,10 @@ impl Resolve<WriteArgs> for CopyResourceSync {
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
let ResourceSync { config, .. } =
resource::get_check_permissions::<ResourceSync>(
get_check_permissions::<ResourceSync>(
&self.id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
Ok(
@@ -134,10 +135,10 @@ impl Resolve<WriteArgs> for RenameResourceSync {
impl Resolve<WriteArgs> for WriteSyncFileContents {
#[instrument(name = "WriteSyncFileContents", skip(args))]
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
&args.user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -178,7 +179,7 @@ async fn write_sync_file_contents_on_host(
let root = core_config()
.sync_directory
.join(to_komodo_name(&sync.name));
.join(to_path_compatible_name(&sync.name));
let file_path =
file_path.parse::<PathBuf>().context("Invalid file path")?;
let resource_path = resource_path
@@ -345,9 +346,11 @@ impl Resolve<WriteArgs> for CommitSync {
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
let WriteArgs { user } = args;
let sync = resource::get_check_permissions::<
entities::sync::ResourceSync,
>(&self.sync, user, PermissionLevel::Write)
let sync = get_check_permissions::<entities::sync::ResourceSync>(
&self.sync,
user,
PermissionLevel::Write.into(),
)
.await?;
let file_contents_empty = sync.config.file_contents_empty();
@@ -411,7 +414,7 @@ impl Resolve<WriteArgs> for CommitSync {
};
let file_path = core_config()
.sync_directory
.join(to_komodo_name(&sync.name))
.join(to_path_compatible_name(&sync.name))
.join(&resource_path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)
@@ -514,10 +517,13 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
) -> serror::Result<ResourceSync> {
// 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,
>(&self.sync, user, PermissionLevel::Execute)
.await?;
let mut sync =
get_check_permissions::<entities::sync::ResourceSync>(
&self.sync,
user,
PermissionLevel::Execute.into(),
)
.await?;
if !sync.config.managed
&& !sync.config.files_on_host
@@ -864,10 +870,10 @@ impl Resolve<WriteArgs> for CreateSyncWebhook {
);
};
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -984,10 +990,10 @@ impl Resolve<WriteArgs> for DeleteSyncWebhook {
);
};
let sync = resource::get_check_permissions::<ResourceSync>(
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;

View File

@@ -30,6 +30,7 @@ use resolver_api::Resolve;
use crate::{
helpers::query::{get_tag, get_tag_check_owner},
permission::get_check_permissions,
resource,
state::db_client,
};
@@ -150,94 +151,94 @@ impl Resolve<WriteArgs> for UpdateTagsOnResource {
return Err(anyhow!("Invalid target type: System").into());
}
ResourceTarget::Build(id) => {
resource::get_check_permissions::<Build>(
get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Build>(&id, self.tags, args).await?;
}
ResourceTarget::Builder(id) => {
resource::get_check_permissions::<Builder>(
get_check_permissions::<Builder>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Builder>(&id, self.tags, args).await?
}
ResourceTarget::Deployment(id) => {
resource::get_check_permissions::<Deployment>(
get_check_permissions::<Deployment>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Deployment>(&id, self.tags, args)
.await?
}
ResourceTarget::Server(id) => {
resource::get_check_permissions::<Server>(
get_check_permissions::<Server>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Server>(&id, self.tags, args).await?
}
ResourceTarget::Repo(id) => {
resource::get_check_permissions::<Repo>(
get_check_permissions::<Repo>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Repo>(&id, self.tags, args).await?
}
ResourceTarget::Alerter(id) => {
resource::get_check_permissions::<Alerter>(
get_check_permissions::<Alerter>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Alerter>(&id, self.tags, args).await?
}
ResourceTarget::Procedure(id) => {
resource::get_check_permissions::<Procedure>(
get_check_permissions::<Procedure>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Procedure>(&id, self.tags, args)
.await?
}
ResourceTarget::Action(id) => {
resource::get_check_permissions::<Action>(
get_check_permissions::<Action>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Action>(&id, self.tags, args).await?
}
ResourceTarget::ResourceSync(id) => {
resource::get_check_permissions::<ResourceSync>(
get_check_permissions::<ResourceSync>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<ResourceSync>(&id, self.tags, args)
.await?
}
ResourceTarget::Stack(id) => {
resource::get_check_permissions::<Stack>(
get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
resource::update_tags::<Stack>(&id, self.tags, args).await?

View File

@@ -2,10 +2,7 @@ use std::{collections::HashMap, str::FromStr};
use anyhow::{Context, anyhow};
use komodo_client::{
api::write::{
AddUserToUserGroup, CreateUserGroup, DeleteUserGroup,
RemoveUserFromUserGroup, RenameUserGroup, SetUsersInUserGroup,
},
api::write::*,
entities::{komodo_timestamp, user_group::UserGroup},
};
use mungos::{
@@ -20,6 +17,7 @@ use crate::state::db_client;
use super::WriteArgs;
impl Resolve<WriteArgs> for CreateUserGroup {
#[instrument(name = "CreateUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -28,11 +26,12 @@ impl Resolve<WriteArgs> for CreateUserGroup {
return Err(anyhow!("This call is admin-only").into());
}
let user_group = UserGroup {
name: self.name,
id: Default::default(),
everyone: Default::default(),
users: Default::default(),
all: Default::default(),
updated_at: komodo_timestamp(),
name: self.name,
};
let db = db_client();
let id = db
@@ -53,6 +52,7 @@ impl Resolve<WriteArgs> for CreateUserGroup {
}
impl Resolve<WriteArgs> for RenameUserGroup {
#[instrument(name = "RenameUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -78,6 +78,7 @@ impl Resolve<WriteArgs> for RenameUserGroup {
}
impl Resolve<WriteArgs> for DeleteUserGroup {
#[instrument(name = "DeleteUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -110,6 +111,7 @@ impl Resolve<WriteArgs> for DeleteUserGroup {
}
impl Resolve<WriteArgs> for AddUserToUserGroup {
#[instrument(name = "AddUserToUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -153,6 +155,7 @@ impl Resolve<WriteArgs> for AddUserToUserGroup {
}
impl Resolve<WriteArgs> for RemoveUserFromUserGroup {
#[instrument(name = "RemoveUserFromUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -196,6 +199,7 @@ impl Resolve<WriteArgs> for RemoveUserFromUserGroup {
}
impl Resolve<WriteArgs> for SetUsersInUserGroup {
#[instrument(name = "SetUsersInUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
@@ -240,3 +244,33 @@ impl Resolve<WriteArgs> for SetUsersInUserGroup {
Ok(res)
}
}
impl Resolve<WriteArgs> for SetEveryoneUserGroup {
#[instrument(name = "SetEveryoneUserGroup", skip(admin), fields(admin = admin.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UserGroup> {
if !admin.admin {
return Err(anyhow!("This call is admin-only").into());
}
let db = db_client();
let filter = match ObjectId::from_str(&self.user_group) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": &self.user_group },
};
db.user_groups
.update_one(filter.clone(), doc! { "$set": { "everyone": self.everyone } })
.await
.context("failed to set everyone on user group")?;
let res = db
.user_groups
.find_one(filter)
.await
.context("failed to query db for UserGroups")?
.context("no user group with given id")?;
Ok(res)
}
}

View File

@@ -199,6 +199,7 @@ pub fn core_config() -> &'static CoreConfig {
.komodo_logging_opentelemetry_service_name
.unwrap_or(config.logging.opentelemetry_service_name),
},
pretty_startup_config: env.komodo_pretty_startup_config.unwrap_or(config.pretty_startup_config),
ssl_enabled: env.komodo_ssl_enabled.unwrap_or(config.ssl_enabled),
ssl_key_file: env.komodo_ssl_key_file.unwrap_or(config.ssl_key_file),
ssl_cert_file: env.komodo_ssl_cert_file.unwrap_or(config.ssl_cert_file),

View File

@@ -0,0 +1,32 @@
use anyhow::Context;
pub enum Matcher<'a> {
Wildcard(wildcard::Wildcard<'a>),
Regex(regex::Regex),
}
impl<'a> Matcher<'a> {
pub fn new(pattern: &'a str) -> anyhow::Result<Self> {
if pattern.starts_with('\\') && pattern.ends_with('\\') {
let inner = &pattern[1..(pattern.len() - 1)];
let regex = regex::Regex::new(inner)
.with_context(|| format!("invalid regex. got: {inner}"))?;
Ok(Self::Regex(regex))
} else {
let wildcard = wildcard::Wildcard::new(pattern.as_bytes())
.with_context(|| {
format!("invalid wildcard. got: {pattern}")
})?;
Ok(Self::Wildcard(wildcard))
}
}
pub fn is_match(&self, source: &str) -> bool {
match self {
Matcher::Wildcard(wildcard) => {
wildcard.is_match(source.as_bytes())
}
Matcher::Regex(regex) => regex.is_match(source),
}
}
}

View File

@@ -1,9 +1,12 @@
use std::time::Duration;
use anyhow::{Context, anyhow};
use indexmap::IndexSet;
use komodo_client::entities::{
ResourceTarget,
permission::{Permission, PermissionLevel, UserTarget},
permission::{
Permission, PermissionLevel, SpecificPermission, UserTarget,
},
server::Server,
user::User,
};
@@ -19,6 +22,7 @@ pub mod builder;
pub mod cache;
pub mod channel;
pub mod interpolate;
pub mod matcher;
pub mod procedure;
pub mod prune;
pub mod query;
@@ -147,6 +151,7 @@ pub async fn create_permission<T>(
user: &User,
target: T,
level: PermissionLevel,
specific: IndexSet<SpecificPermission>,
) where
T: Into<ResourceTarget> + std::fmt::Debug,
{
@@ -162,6 +167,7 @@ pub async fn create_permission<T>(
user_target: UserTarget::User(user.id.clone()),
resource_target: target.clone(),
level,
specific,
})
.await
{

View File

@@ -9,6 +9,7 @@ use komodo_client::{
action::Action,
build::Build,
deployment::Deployment,
permission::PermissionLevel,
procedure::Procedure,
repo::Repo,
stack::Stack,
@@ -1189,6 +1190,7 @@ async fn extend_batch_exection<E: ExtendBatch>(
pattern,
Default::default(),
procedure_user(),
PermissionLevel::Read.into(),
&[],
)
.await?

View File

@@ -14,7 +14,7 @@ use komodo_client::entities::{
builder::Builder,
deployment::{Deployment, DeploymentState},
docker::container::{ContainerListItem, ContainerStateStatusEnum},
permission::PermissionLevel,
permission::{PermissionLevel, PermissionLevelAndSpecifics},
procedure::Procedure,
repo::Repo,
server::{Server, ServerState},
@@ -39,7 +39,8 @@ use tokio::sync::Mutex;
use crate::{
config::core_config,
resource::{self, get_user_permission_on_resource},
permission::get_user_permission_on_resource,
resource,
stack::compose_container_match_regex,
state::{db_client, deployment_status_cache, stack_status_cache},
};
@@ -238,7 +239,10 @@ pub async fn get_user_user_groups(
find_collect(
&db_client().user_groups,
doc! {
"users": user_id
"$or": [
{ "everyone": true },
{ "users": user_id },
]
},
None,
)
@@ -277,9 +281,9 @@ pub fn user_target_query(
pub async fn get_user_permission_on_target(
user: &User,
target: &ResourceTarget,
) -> anyhow::Result<PermissionLevel> {
) -> anyhow::Result<PermissionLevelAndSpecifics> {
match target {
ResourceTarget::System(_) => Ok(PermissionLevel::None),
ResourceTarget::System(_) => Ok(PermissionLevel::None.into()),
ResourceTarget::Build(id) => {
get_user_permission_on_resource::<Build>(user, id).await
}

View File

@@ -22,6 +22,7 @@ mod db;
mod helpers;
mod listener;
mod monitor;
mod permission;
mod resource;
mod schedule;
mod stack;
@@ -43,7 +44,12 @@ async fn app() -> anyhow::Result<()> {
};
info!("Komodo Core version: v{}", env!("CARGO_PKG_VERSION"));
info!("{:?}", config.sanitized());
if core_config().pretty_startup_config {
info!("{:#?}", config.sanitized());
} else {
info!("{:?}", config.sanitized());
}
// Init jwt client to crash on failure
state::jwt_client();
@@ -55,7 +61,7 @@ async fn app() -> anyhow::Result<()> {
);
// Run after db connection.
startup::on_startup().await;
// Spawn background tasks
monitor::spawn_monitor_loop();
resource::spawn_resource_refresh_loop();

View File

@@ -2,7 +2,8 @@ use std::collections::HashMap;
use anyhow::Context;
use komodo_client::entities::{
resource::ResourceQuery, server::Server, user::User,
permission::PermissionLevel, resource::ResourceQuery,
server::Server, user::User,
};
use crate::resource;
@@ -39,6 +40,7 @@ async fn get_all_servers_map()
admin: true,
..Default::default()
},
PermissionLevel::Read.into(),
&[],
)
.await

229
bin/core/src/permission.rs Normal file
View File

@@ -0,0 +1,229 @@
use std::collections::HashSet;
use anyhow::{Context, anyhow};
use futures::{FutureExt, future::BoxFuture};
use indexmap::IndexSet;
use komodo_client::{
api::read::GetPermission,
entities::{
permission::{PermissionLevel, PermissionLevelAndSpecifics},
resource::Resource,
user::User,
},
};
use mongo_indexed::doc;
use mungos::find::find_collect;
use resolver_api::Resolve;
use crate::{
api::read::ReadArgs,
config::core_config,
helpers::query::{get_user_user_groups, user_target_query},
resource::{KomodoResource, get},
state::db_client,
};
pub async fn get_check_permissions<T: KomodoResource>(
id_or_name: &str,
user: &User,
required_permissions: PermissionLevelAndSpecifics,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
let resource = get::<T>(id_or_name).await?;
// Allow all if admin
if user.admin {
return Ok(resource);
}
let user_permissions =
get_user_permission_on_resource::<T>(user, &resource.id).await?;
if (
// Allow if its just read or below, and transparent mode enabled
(required_permissions.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.level >= required_permissions.level
) && user_permissions
.fulfills_specific(&required_permissions.specific)
{
return Ok(resource);
}
if user_permissions.fulfills(&required_permissions) {
Ok(resource)
} else {
Err(anyhow!(
"User does not have required permissions on this {}. Must have at least {} permissions{}",
T::resource_type(),
required_permissions.level,
if required_permissions.specific.is_empty() {
String::new()
} else {
format!(
", as well as these specific permissions: [{}]",
required_permissions.specifics_for_log()
)
}
))
}
}
#[instrument(level = "debug")]
pub fn get_user_permission_on_resource<'a, T: KomodoResource>(
user: &'a User,
resource_id: &'a str,
) -> BoxFuture<'a, anyhow::Result<PermissionLevelAndSpecifics>> {
Box::pin(async {
// Admin returns early with max permissions
if user.admin {
return Ok(PermissionLevel::Write.all());
}
let resource_type = T::resource_type();
let resource = get::<T>(resource_id).await?;
let initial_specific = if let Some(additional_target) =
T::inherit_specific_permissions_from(&resource)
{
GetPermission {
target: additional_target,
}
.resolve(&ReadArgs { user: user.clone() })
.await
.map_err(|e| e.error)
.context("failed to get user permission on additional target")?
.specific
} else {
IndexSet::new()
};
let mut permission = PermissionLevelAndSpecifics {
level: if core_config().transparent_mode {
PermissionLevel::Read
} else {
PermissionLevel::None
},
specific: initial_specific,
};
// Add in the resource level global base permissions
if resource.base_permission.level > permission.level {
permission.level = resource.base_permission.level;
}
permission
.specific
.extend(resource.base_permission.specific);
// Overlay users base on resource variant
if let Some(user_permission) =
user.all.get(&resource_type).cloned()
{
if user_permission.level > permission.level {
permission.level = user_permission.level;
}
permission.specific.extend(user_permission.specific);
}
// Overlay any user groups base on resource variant
let groups = get_user_user_groups(&user.id).await?;
for group in &groups {
if let Some(group_permission) =
group.all.get(&resource_type).cloned()
{
if group_permission.level > permission.level {
permission.level = group_permission.level;
}
permission.specific.extend(group_permission.specific);
}
}
// 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 resource permission user has between personal / any user groups
.fold(permission, |mut permission, resource_permission| {
if resource_permission.level > permission.level {
permission.level = resource_permission.level
}
permission.specific.extend(resource_permission.specific);
permission
});
Ok(permission)
})
}
/// 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<T: KomodoResource>(
user: &User,
) -> anyhow::Result<Option<Vec<String>>> {
// Check admin or transparent mode
if user.admin || core_config().transparent_mode {
return Ok(None);
}
let resource_type = T::resource_type();
// Check user 'all' on variant
if let Some(permission) = user.all.get(&resource_type).cloned() {
if permission.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(permission) = group.all.get(&resource_type).cloned() {
if permission.level > PermissionLevel::None {
return Ok(None);
}
}
}
let (base, perms) = tokio::try_join!(
// Get any resources with non-none base permission,
find_collect(
T::coll(),
doc! { "$or": [
{ "base_permission": { "$in": ["Read", "Execute", "Write"] } },
{ "base_permission.level": { "$in": ["Read", "Execute", "Write"] } }
] },
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": { "$in": ["Read", "Execute", "Write"] }
},
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::<HashSet<_>>();
Ok(Some(ids.into_iter().collect()))
}

View File

@@ -15,6 +15,7 @@ use komodo_client::{
environment_vars_from_str, optional_string,
permission::PermissionLevel,
resource::Resource,
to_docker_compatible_name,
update::Update,
user::{User, build_user},
},
@@ -48,6 +49,10 @@ impl super::KomodoResource for Build {
ResourceTarget::Build(id.into())
}
fn validated_name(name: &str) -> String {
to_docker_compatible_name(name)
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().builds
@@ -214,7 +219,7 @@ async fn validate_config(
let builder = super::get_check_permissions::<Builder>(
builder_id,
user,
PermissionLevel::Read,
PermissionLevel::Read.attach(),
)
.await
.context("Cannot attach Build to this Builder")?;

View File

@@ -1,4 +1,5 @@
use anyhow::Context;
use indexmap::IndexSet;
use komodo_client::entities::{
MergePartial, Operation, ResourceTarget, ResourceTargetVariant,
builder::{
@@ -6,7 +7,7 @@ use komodo_client::entities::{
BuilderListItem, BuilderListItemInfo, BuilderQuerySpecifics,
PartialBuilderConfig, PartialServerBuilderConfig,
},
permission::PermissionLevel,
permission::{PermissionLevel, SpecificPermission},
resource::Resource,
server::Server,
update::Update,
@@ -35,6 +36,10 @@ impl super::KomodoResource for Builder {
ResourceTarget::Builder(id.into())
}
fn creator_specific_permissions() -> IndexSet<SpecificPermission> {
[SpecificPermission::Attach].into_iter().collect()
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().builders
@@ -180,7 +185,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
PermissionLevel::Read.attach(),
)
.await?;
*server_id = server.id;

View File

@@ -1,5 +1,6 @@
use anyhow::Context;
use formatting::format_serror;
use indexmap::IndexSet;
use komodo_client::entities::{
Operation, ResourceTarget, ResourceTargetVariant,
build::Build,
@@ -10,9 +11,10 @@ use komodo_client::entities::{
PartialDeploymentConfig, conversions_from_str,
},
environment_vars_from_str,
permission::PermissionLevel,
permission::{PermissionLevel, SpecificPermission},
resource::Resource,
server::Server,
to_docker_compatible_name,
update::Update,
user::User,
};
@@ -47,6 +49,26 @@ impl super::KomodoResource for Deployment {
ResourceTarget::Deployment(id.into())
}
fn validated_name(name: &str) -> String {
to_docker_compatible_name(name)
}
fn creator_specific_permissions() -> IndexSet<SpecificPermission> {
[
SpecificPermission::Inspect,
SpecificPermission::Logs,
SpecificPermission::Terminal,
]
.into_iter()
.collect()
}
fn inherit_specific_permissions_from(
_self: &Resource<Self::Config, Self::Info>,
) -> Option<ResourceTarget> {
ResourceTarget::Server(_self.config.server_id.clone()).into()
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().deployments
@@ -284,7 +306,7 @@ async fn validate_config(
let server = get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
PermissionLevel::Read.attach(),
)
.await
.context("Cannot attach Deployment to this Server")?;
@@ -298,7 +320,7 @@ async fn validate_config(
let build = get_check_permissions::<Build>(
build_id,
user,
PermissionLevel::Read,
PermissionLevel::Read.attach(),
)
.await
.context(

View File

@@ -5,16 +5,20 @@ use std::{
use anyhow::{Context, anyhow};
use formatting::format_serror;
use futures::{FutureExt, future::join_all};
use futures::future::join_all;
use indexmap::IndexSet;
use komodo_client::{
api::{read::ExportResourcesToToml, write::CreateTag},
entities::{
Operation, ResourceTarget, ResourceTargetVariant,
komodo_timestamp,
permission::PermissionLevel,
permission::{
PermissionLevel, PermissionLevelAndSpecifics,
SpecificPermission,
},
resource::{AddFilters, Resource, ResourceQuery},
tag::Tag,
to_komodo_name,
to_general_name,
update::Update,
user::{User, system_user},
},
@@ -35,15 +39,12 @@ use serde::{Serialize, de::DeserializeOwned};
use crate::{
api::{read::ReadArgs, write::WriteArgs},
config::core_config,
helpers::{
create_permission, flatten_document,
query::{
get_tag, get_user_user_groups, id_or_name_filter,
user_target_query,
},
query::{get_tag, id_or_name_filter},
update::{add_update, make_update},
},
permission::{get_check_permissions, get_resource_ids_for_user},
state::db_client,
};
@@ -117,6 +118,28 @@ pub trait KomodoResource {
#[allow(clippy::ptr_arg)]
async fn busy(id: &String) -> anyhow::Result<bool>;
/// Some resource types have restrictions on the allowed formatting for names.
/// Stacks, Builds, and Deployments all require names to be "docker compatible",
/// which means all lowercase, and no spaces or dots.
fn validated_name(name: &str) -> String {
to_general_name(name)
}
/// These permissions go to the creator of the resource,
/// and include full access to the resource.
fn creator_specific_permissions() -> IndexSet<SpecificPermission> {
IndexSet::new()
}
/// For Stacks / Deployments, they should inherit specific
/// permissions like `Logs`, `Inspect`, and `Terminal`
/// from their attached Server.
fn inherit_specific_permissions_from(
_self: &Resource<Self::Config, Self::Info>,
) -> Option<ResourceTarget> {
None
}
// =======
// CREATE
// =======
@@ -213,106 +236,6 @@ pub async fn get<T: KomodoResource>(
})
}
pub async fn get_check_permissions<T: KomodoResource>(
id_or_name: &str,
user: &User,
permission_level: PermissionLevel,
) -> anyhow::Result<Resource<T::Config, T::Info>> {
let resource = get::<T>(id_or_name).await?;
if user.admin
// 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::<T>(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",
T::resource_type()
))
}
}
#[instrument(level = "debug")]
pub async fn get_user_permission_on_resource<T: KomodoResource>(
user: &User,
resource_id: &str,
) -> anyhow::Result<PermissionLevel> {
if user.admin {
return Ok(PermissionLevel::Write);
}
let resource_type = T::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::<T>(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
// ======
@@ -332,80 +255,17 @@ pub async fn get_resource_object_ids_for_user<T: KomodoResource>(
})
}
/// 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<T: KomodoResource>(
user: &User,
) -> anyhow::Result<Option<Vec<String>>> {
// Check admin or transparent mode
if user.admin || core_config().transparent_mode {
return Ok(None);
}
let resource_type = T::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(
T::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::<HashSet<_>>();
Ok(Some(ids.into_iter().collect()))
}
#[instrument(level = "debug")]
pub async fn list_for_user<T: KomodoResource>(
mut query: ResourceQuery<T::QuerySpecifics>,
user: &User,
permissions: PermissionLevelAndSpecifics,
all_tags: &[Tag],
) -> anyhow::Result<Vec<T::ListItem>> {
validate_resource_query_tags(&mut query, all_tags)?;
let mut filters = Document::new();
query.add_filters(&mut filters);
list_for_user_using_document::<T>(filters, user).await
list_for_user_using_document::<T>(filters, user, permissions).await
}
#[instrument(level = "debug")]
@@ -413,10 +273,15 @@ pub async fn list_for_user_using_pattern<T: KomodoResource>(
pattern: &str,
query: ResourceQuery<T::QuerySpecifics>,
user: &User,
permissions: PermissionLevelAndSpecifics,
all_tags: &[Tag],
) -> anyhow::Result<Vec<T::ListItem>> {
let list = list_full_for_user_using_pattern::<T>(
pattern, query, user, all_tags,
pattern,
query,
user,
permissions,
all_tags,
)
.await?
.into_iter()
@@ -428,6 +293,7 @@ pub async fn list_for_user_using_pattern<T: KomodoResource>(
pub async fn list_for_user_using_document<T: KomodoResource>(
filters: Document,
user: &User,
permissions: PermissionLevelAndSpecifics,
) -> anyhow::Result<Vec<T::ListItem>> {
let list = list_full_for_user_using_document::<T>(filters, user)
.await?
@@ -449,10 +315,12 @@ pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
pattern: &str,
query: ResourceQuery<T::QuerySpecifics>,
user: &User,
permissions: PermissionLevelAndSpecifics,
all_tags: &[Tag],
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
let resources =
list_full_for_user::<T>(query, user, all_tags).await?;
list_full_for_user::<T>(query, user, permissions, all_tags)
.await?;
let patterns = parse_string_list(pattern);
let mut names = HashSet::<String>::new();
@@ -489,6 +357,7 @@ pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
pub async fn list_full_for_user<T: KomodoResource>(
mut query: ResourceQuery<T::QuerySpecifics>,
user: &User,
permissions: PermissionLevelAndSpecifics,
all_tags: &[Tag],
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
validate_resource_query_tags(&mut query, all_tags)?;
@@ -590,7 +459,7 @@ pub async fn create<T: KomodoResource>(
return Err(anyhow!("Must provide non-empty name for resource."));
}
let name = to_komodo_name(name);
let name = T::validated_name(name);
if ObjectId::from_str(&name).is_ok() {
return Err(anyhow!("valid ObjectIds cannot be used as names."));
@@ -598,11 +467,16 @@ pub async fn create<T: KomodoResource>(
// 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 list_full_for_user::<T>(Default::default(), system_user(), &[])
.await
.context("Failed to list all resources for duplicate name check")?
.into_iter()
.any(|r| r.name == name)
if list_full_for_user::<T>(
Default::default(),
system_user(),
PermissionLevel::Read.into(),
&[],
)
.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."));
}
@@ -619,7 +493,7 @@ pub async fn create<T: KomodoResource>(
tags: Default::default(),
config: config.into(),
info: T::default_info().await?,
base_permission: PermissionLevel::None,
base_permission: PermissionLevel::None.into(),
};
let resource_id = T::coll()
@@ -636,8 +510,13 @@ pub async fn create<T: KomodoResource>(
let resource = get::<T>(&resource_id).await?;
let target = resource_target::<T>(resource_id);
create_permission(user, target.clone(), PermissionLevel::Write)
.await;
create_permission(
user,
target.clone(),
PermissionLevel::Write,
T::creator_specific_permissions(),
)
.await;
let mut update = make_update(target, T::create_operation(), user);
update.start_ts = start_ts;
@@ -676,7 +555,7 @@ pub async fn update<T: KomodoResource>(
let resource = get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -788,7 +667,7 @@ pub async fn update_description<T: KomodoResource>(
get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
T::coll()
@@ -852,7 +731,7 @@ pub async fn rename<T: KomodoResource>(
let resource = get_check_permissions::<T>(
id_or_name,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
@@ -862,7 +741,7 @@ pub async fn rename<T: KomodoResource>(
user,
);
let name = to_komodo_name(name);
let name = T::validated_name(name);
update_one_by_id(
T::coll(),
@@ -906,7 +785,7 @@ pub async fn delete<T: KomodoResource>(
let resource = get_check_permissions::<T>(
id_or_name,
&args.user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;

View File

@@ -180,7 +180,7 @@ async fn validate_config(
let procedure = super::get_check_permissions::<Procedure>(
&params.procedure,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
match id {
@@ -204,7 +204,7 @@ async fn validate_config(
let action = super::get_check_permissions::<Action>(
&params.action,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.action = action.id;
@@ -220,7 +220,7 @@ async fn validate_config(
let build = super::get_check_permissions::<Build>(
&params.build,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.build = build.id;
@@ -236,7 +236,7 @@ async fn validate_config(
let build = super::get_check_permissions::<Build>(
&params.build,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.build = build.id;
@@ -246,7 +246,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -263,7 +263,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -273,7 +273,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -283,7 +283,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -293,7 +293,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -303,7 +303,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -313,7 +313,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -323,7 +323,7 @@ async fn validate_config(
super::get_check_permissions::<Deployment>(
&params.deployment,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.deployment = deployment.id;
@@ -339,7 +339,7 @@ async fn validate_config(
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.repo = repo.id;
@@ -355,7 +355,7 @@ async fn validate_config(
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.repo = repo.id;
@@ -371,7 +371,7 @@ async fn validate_config(
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.repo = repo.id;
@@ -387,7 +387,7 @@ async fn validate_config(
let repo = super::get_check_permissions::<Repo>(
&params.repo,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.repo = repo.id;
@@ -396,7 +396,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -405,7 +405,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -414,7 +414,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -423,7 +423,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -432,7 +432,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -441,7 +441,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -450,7 +450,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -459,7 +459,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -468,7 +468,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -477,7 +477,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -486,7 +486,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -495,7 +495,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -504,7 +504,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -513,7 +513,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -522,7 +522,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -531,7 +531,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -540,7 +540,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -549,7 +549,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -558,7 +558,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -567,7 +567,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -576,7 +576,7 @@ async fn validate_config(
let server = super::get_check_permissions::<Server>(
&params.server,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.server = server.id;
@@ -585,7 +585,7 @@ async fn validate_config(
let sync = super::get_check_permissions::<ResourceSync>(
&params.sync,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.sync = sync.id;
@@ -595,7 +595,7 @@ async fn validate_config(
let sync = super::get_check_permissions::<ResourceSync>(
&params.sync,
user,
PermissionLevel::Write,
PermissionLevel::Write.into(),
)
.await?;
params.sync = sync.id;
@@ -604,7 +604,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -620,7 +620,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -636,7 +636,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -652,7 +652,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -661,7 +661,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -670,7 +670,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -679,7 +679,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -688,7 +688,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -697,7 +697,7 @@ async fn validate_config(
let stack = super::get_check_permissions::<Stack>(
&params.stack,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.stack = stack.id;
@@ -713,7 +713,7 @@ async fn validate_config(
let alerter = super::get_check_permissions::<Alerter>(
&params.alerter,
user,
PermissionLevel::Execute,
PermissionLevel::Execute.into(),
)
.await?;
params.alerter = alerter.id;

View File

@@ -12,7 +12,7 @@ use komodo_client::entities::{
},
resource::Resource,
server::Server,
to_komodo_name,
to_path_compatible_name,
update::Update,
user::User,
};
@@ -48,6 +48,10 @@ impl super::KomodoResource for Repo {
ResourceTarget::Repo(id.into())
}
fn validated_name(name: &str) -> String {
to_path_compatible_name(name)
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().repos
@@ -170,7 +174,7 @@ impl super::KomodoResource for Repo {
match periphery
.request(DeleteRepo {
name: if repo.config.path.is_empty() {
to_komodo_name(&repo.name)
to_path_compatible_name(&repo.name)
} else {
repo.config.path.clone()
},
@@ -226,7 +230,7 @@ async fn validate_config(
let server = get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
PermissionLevel::Read.attach(),
)
.await
.context("Cannot attach Repo to this Server")?;
@@ -238,7 +242,7 @@ async fn validate_config(
let builder = super::get_check_permissions::<Builder>(
builder_id,
user,
PermissionLevel::Read,
PermissionLevel::Read.attach(),
)
.await
.context("Cannot attach Repo to this Builder")?;

View File

@@ -1,6 +1,8 @@
use anyhow::Context;
use indexmap::IndexSet;
use komodo_client::entities::{
Operation, ResourceTarget, ResourceTargetVariant, komodo_timestamp,
permission::SpecificPermission,
resource::Resource,
server::{
PartialServerConfig, Server, ServerConfig, ServerConfigDiff,
@@ -34,6 +36,18 @@ impl super::KomodoResource for Server {
ResourceTarget::Server(id.into())
}
fn creator_specific_permissions() -> IndexSet<SpecificPermission> {
[
SpecificPermission::Terminal,
SpecificPermission::Inspect,
SpecificPermission::Attach,
SpecificPermission::Logs,
SpecificPermission::Processes,
]
.into_iter()
.collect()
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().servers

View File

@@ -1,10 +1,11 @@
use anyhow::Context;
use formatting::format_serror;
use indexmap::IndexSet;
use komodo_client::{
api::write::RefreshStackCache,
entities::{
Operation, ResourceTarget, ResourceTargetVariant,
permission::PermissionLevel,
permission::{PermissionLevel, SpecificPermission},
resource::Resource,
server::Server,
stack::{
@@ -12,6 +13,7 @@ use komodo_client::{
StackInfo, StackListItem, StackListItemInfo,
StackQuerySpecifics, StackServiceWithUpdate, StackState,
},
to_docker_compatible_name,
update::Update,
user::{User, stack_user},
},
@@ -48,6 +50,26 @@ impl super::KomodoResource for Stack {
ResourceTarget::Stack(id.into())
}
fn validated_name(name: &str) -> String {
to_docker_compatible_name(name)
}
fn creator_specific_permissions() -> IndexSet<SpecificPermission> {
[
SpecificPermission::Inspect,
SpecificPermission::Logs,
SpecificPermission::Terminal,
]
.into_iter()
.collect()
}
fn inherit_specific_permissions_from(
_self: &Resource<Self::Config, Self::Info>,
) -> Option<ResourceTarget> {
ResourceTarget::Server(_self.config.server_id.clone()).into()
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().stacks
@@ -314,7 +336,7 @@ async fn validate_config(
let server = get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Write,
PermissionLevel::Read.attach(),
)
.await
.context("Cannot attach stack to this Server")?;

View File

@@ -36,9 +36,13 @@ pub async fn execute_compose<T: ExecuteCompose>(
mut update: Update,
extras: T::Extras,
) -> anyhow::Result<Update> {
let (stack, server) =
get_stack_and_server(stack, user, PermissionLevel::Execute, true)
.await?;
let (stack, server) = get_stack_and_server(
stack,
user,
PermissionLevel::Execute.into(),
true,
)
.await?;
// get the action state for the stack (or insert default).
let action_state =

View File

@@ -1,13 +1,16 @@
use anyhow::{Context, anyhow};
use komodo_client::entities::{
permission::PermissionLevel,
permission::PermissionLevelAndSpecifics,
server::{Server, ServerState},
stack::Stack,
user::User,
};
use regex::Regex;
use crate::{helpers::query::get_server_with_state, resource};
use crate::{
helpers::query::get_server_with_state,
permission::get_check_permissions,
};
pub mod execute;
pub mod remote;
@@ -16,15 +19,11 @@ pub mod services;
pub async fn get_stack_and_server(
stack: &str,
user: &User,
permission_level: PermissionLevel,
permissions: PermissionLevelAndSpecifics,
block_if_server_unreachable: bool,
) -> anyhow::Result<(Stack, Server)> {
let stack = resource::get_check_permissions::<Stack>(
stack,
user,
permission_level,
)
.await?;
let stack =
get_check_permissions::<Stack>(stack, user, permissions).await?;
if stack.config.server_id.is_empty() {
return Err(anyhow!("Stack has no server configured"));

View File

@@ -3,7 +3,7 @@ use git::GitRes;
use komodo_client::entities::{
CloneArgs,
sync::{ResourceSync, SyncFileContents},
to_komodo_name,
to_path_compatible_name,
toml::ResourcesToml,
update::Log,
};
@@ -31,7 +31,7 @@ pub async fn get_remote_resources(
// =============
let root_path = core_config()
.sync_directory
.join(to_komodo_name(&sync.name));
.join(to_path_compatible_name(&sync.name));
let (mut logs, mut files, mut file_errors) =
(Vec::new(), Vec::new(), Vec::new());
let resources = super::file::read_resources(

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap;
use anyhow::Context;
use indexmap::IndexMap;
use komodo_client::{
api::execute::Execution,
entities::{
@@ -19,7 +20,6 @@ use komodo_client::{
toml::ResourceToml,
},
};
use ordered_hash_map::OrderedHashMap;
use partial_derive2::{MaybeNone, PartialDiff};
use crate::resource::KomodoResource;
@@ -44,8 +44,8 @@ pub trait ToToml: KomodoResource {
fn edit_config_object(
_resource: &ResourceToml<Self::PartialConfig>,
config: OrderedHashMap<String, serde_json::Value>,
) -> anyhow::Result<OrderedHashMap<String, serde_json::Value>> {
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
Ok(config)
}
@@ -62,9 +62,9 @@ pub trait ToToml: KomodoResource {
resource.config =
Self::Config::default().minimize_partial(resource.config);
let mut resource_map: OrderedHashMap<String, serde_json::Value> =
let mut resource_map: IndexMap<String, serde_json::Value> =
serde_json::from_str(&serde_json::to_string(&resource)?)?;
resource_map.remove("config");
resource_map.shift_remove("config");
let config = serde_json::from_str(&serde_json::to_string(
&resource.config,
@@ -182,8 +182,8 @@ impl ToToml for Stack {
fn edit_config_object(
_resource: &ResourceToml<Self::PartialConfig>,
config: OrderedHashMap<String, serde_json::Value>,
) -> anyhow::Result<OrderedHashMap<String, serde_json::Value>> {
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
config
.into_iter()
.map(|(key, value)| {
@@ -225,8 +225,8 @@ impl ToToml for Deployment {
fn edit_config_object(
resource: &ResourceToml<Self::PartialConfig>,
config: OrderedHashMap<String, serde_json::Value>,
) -> anyhow::Result<OrderedHashMap<String, serde_json::Value>> {
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
config
.into_iter()
.map(|(key, mut value)| {
@@ -278,8 +278,8 @@ impl ToToml for Build {
fn edit_config_object(
resource: &ResourceToml<Self::PartialConfig>,
config: OrderedHashMap<String, serde_json::Value>,
) -> anyhow::Result<OrderedHashMap<String, serde_json::Value>> {
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
config
.into_iter()
.map(|(key, value)| match key.as_str() {
@@ -330,8 +330,8 @@ impl ToToml for Repo {
fn edit_config_object(
_resource: &ResourceToml<Self::PartialConfig>,
config: OrderedHashMap<String, serde_json::Value>,
) -> anyhow::Result<OrderedHashMap<String, serde_json::Value>> {
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
config
.into_iter()
.map(|(key, value)| {
@@ -791,7 +791,7 @@ impl ToToml for Procedure {
resource.config =
Self::Config::default().minimize_partial(resource.config);
let mut parsed: OrderedHashMap<String, serde_json::Value> =
let mut parsed: IndexMap<String, serde_json::Value> =
serde_json::from_str(&serde_json::to_string(&resource)?)?;
let config = parsed

View File

@@ -1,18 +1,25 @@
use std::{cmp::Ordering, collections::HashMap};
use std::{
cmp::Ordering, collections::HashMap, fmt::Write, sync::OnceLock,
};
use anyhow::Context;
use formatting::{Color, bold, colored, muted};
use indexmap::{IndexMap, IndexSet};
use komodo_client::{
api::{
read::ListUserTargetPermissions,
write::{
CreateUserGroup, DeleteUserGroup, SetUsersInUserGroup,
UpdatePermissionOnResourceType, UpdatePermissionOnTarget,
CreateUserGroup, DeleteUserGroup, SetEveryoneUserGroup,
SetUsersInUserGroup, UpdatePermissionOnResourceType,
UpdatePermissionOnTarget,
},
},
entities::{
ResourceTarget, ResourceTargetVariant,
permission::{PermissionLevel, UserTarget},
permission::{
PermissionLevel, PermissionLevelAndSpecifics,
SpecificPermission, UserTarget,
},
sync::DiffData,
toml::{PermissionToml, UserGroupToml},
update::Log,
@@ -21,20 +28,109 @@ use komodo_client::{
},
};
use mungos::find::find_collect;
use regex::Regex;
use resolver_api::Resolve;
use serde::Serialize;
use crate::{
api::{read::ReadArgs, write::WriteArgs},
helpers::matcher::Matcher,
state::db_client,
};
use super::{AllResourcesById, toml::TOML_PRETTY_OPTIONS};
/// Used to serialize user group
#[derive(Serialize)]
struct BasicUserGroupToml {
name: String,
#[serde(skip_serializing_if = "is_false")]
everyone: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
users: Vec<String>,
}
fn is_false(b: &bool) -> bool {
!b
}
/// Used to serialize user group
#[derive(Serialize)]
struct Permissions {
permissions: Vec<PermissionToml>,
}
pub fn user_group_to_toml(
user_group: UserGroupToml,
) -> anyhow::Result<String> {
// Start with the basic body
let basic = BasicUserGroupToml {
name: user_group.name,
everyone: user_group.everyone,
users: if user_group.everyone {
Vec::new()
} else {
user_group.users
},
};
let basic = toml_pretty::to_string(&basic, TOML_PRETTY_OPTIONS)
.context("failed to serialize user group to toml")?;
let mut res = format!("[[user_group]]\n{basic}");
// Add "all" permissions
for (variant, PermissionLevelAndSpecifics { level, specific }) in
user_group.all
{
// skip 'zero' all permissions
if level == PermissionLevel::None && specific.is_empty() {
continue;
}
write!(&mut res, "\nall.{variant} = ")
.context("failed to serialize user group 'all' to toml")?;
if specific.is_empty() {
res.push('"');
res.push_str(level.as_ref());
res.push('"');
} else {
let specific = serde_json::to_string(&specific)
.context(
"failed to serialize user group specifics to... json?",
)?
.replace(",", ", ");
write!(
&mut res,
"{{ level = \"{level}\", specific = {specific} }}"
)
.context(
"failed to serialize user group 'all' with specifics to toml",
)?;
}
}
// End with resource permissions array
if !user_group.permissions.is_empty() {
res.push('\n');
res.push_str(
&toml_pretty::to_string(
&Permissions {
permissions: user_group.permissions,
},
TOML_PRETTY_OPTIONS,
)
.context(
"failed to serialize user group permissions to toml",
)?,
);
}
Ok(res)
}
pub struct UpdateItem {
user_group: UserGroupToml,
update_users: bool,
all_diff: HashMap<ResourceTargetVariant, PermissionLevel>,
update_everyone: bool,
all_diff:
IndexMap<ResourceTargetVariant, PermissionLevelAndSpecifics>,
}
pub struct DeleteItem {
@@ -64,17 +160,17 @@ pub async fn get_updates_for_view(
for (_id, user_group) in map.values() {
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
diffs.push(DiffData::Delete {
current: format!(
"[[user_group]]\n{}",
toml_pretty::to_string(user_group, TOML_PRETTY_OPTIONS)
.context("failed to serialize user group to toml")?
),
current: user_group_to_toml(user_group.clone())?,
});
}
}
}
for mut user_group in user_groups {
if user_group.everyone {
user_group.users.clear();
}
user_group
.permissions
.retain(|p| p.level > PermissionLevel::None);
@@ -91,23 +187,17 @@ pub async fn get_updates_for_view(
)
})?;
let (_original_id, original) = match map
.get(&user_group.name)
.cloned()
{
Some(original) => original,
None => {
diffs.push(DiffData::Create {
name: user_group.name.clone(),
proposed: format!(
"[[user_group]]\n{}",
toml_pretty::to_string(&user_group, TOML_PRETTY_OPTIONS)
.context("failed to serialize user group to toml")?
),
});
continue;
}
};
let (_original_id, original) =
match map.get(&user_group.name).cloned() {
Some(original) => original,
None => {
diffs.push(DiffData::Create {
name: user_group.name.clone(),
proposed: user_group_to_toml(user_group.clone())?,
});
continue;
}
};
user_group.users.sort();
let all_diff = diff_group_all(&original.all, &user_group.all);
@@ -115,23 +205,20 @@ pub async fn get_updates_for_view(
user_group.permissions.sort_by(sort_permissions);
let update_users = user_group.users != original.users;
let update_everyone = user_group.everyone != original.everyone;
let update_all = !all_diff.is_empty();
let update_permissions =
user_group.permissions != original.permissions;
// only add log after diff detected
if update_users || update_all || update_permissions {
if update_users
|| update_everyone
|| update_all
|| update_permissions
{
diffs.push(DiffData::Update {
proposed: format!(
"[[user_group]]\n{}",
toml_pretty::to_string(&user_group, TOML_PRETTY_OPTIONS)
.context("failed to serialize user group to toml")?
),
current: format!(
"[[user_group]]\n{}",
toml_pretty::to_string(&original, TOML_PRETTY_OPTIONS)
.context("failed to serialize user group to toml")?
),
proposed: user_group_to_toml(user_group.clone())?,
current: user_group_to_toml(original.clone())?,
});
}
}
@@ -152,7 +239,15 @@ pub async fn get_updates_for_execution(
.await
.context("failed to query db for UserGroups")?
.into_iter()
.map(|ug| (ug.name.clone(), ug))
.map(|mut ug| {
if ug.everyone {
ug.users.clear();
}
ug.all.retain(|_, p| {
p.level > PermissionLevel::None || !p.specific.is_empty()
});
(ug.name.clone(), ug)
})
.collect::<HashMap<_, _>>();
let mut to_create = Vec::<UserGroupToml>::new();
@@ -182,6 +277,10 @@ pub async fn get_updates_for_execution(
.collect::<HashMap<_, _>>();
for mut user_group in user_groups {
if user_group.everyone {
user_group.users.clear();
}
user_group
.permissions
.retain(|p| p.level > PermissionLevel::None);
@@ -193,7 +292,7 @@ pub async fn get_updates_for_execution(
.await
.with_context(|| {
format!(
"failed to expand user group {} permissions",
"Failed to expand user group {} permissions",
user_group.name
)
})?;
@@ -303,6 +402,7 @@ pub async fn get_updates_for_execution(
PermissionToml {
target: p.resource_target,
level: p.level,
specific: p.specific,
}
})
.collect::<Vec<_>>();
@@ -316,8 +416,10 @@ pub async fn get_updates_for_execution(
original_permissions.sort_by(sort_permissions);
let update_users = user_group.users != original_users;
let update_everyone = user_group.everyone != original.everyone;
// Extend permissions with any existing that have no target in incoming
// This makes sure to set those permissions back to None.
let to_remove = original_permissions
.iter()
.filter(|permission| {
@@ -329,32 +431,37 @@ pub async fn get_updates_for_execution(
.map(|permission| PermissionToml {
target: permission.target.clone(),
level: PermissionLevel::None,
specific: IndexSet::new(),
})
.collect::<Vec<_>>();
user_group.permissions.extend(to_remove);
// remove any permissions that already exist on original
user_group.permissions.retain(|permission| {
let Some(level) = original_permissions
let Some(original_permission) = original_permissions
.iter()
.find(|p| p.target == permission.target)
.map(|p| p.level)
else {
// not in original, keep it
return true;
};
// keep it if level doesn't match
level != permission.level
original_permission.level != permission.level
|| !specific_equal(
&original_permission.specific,
&permission.specific,
)
});
// only push update after diff detected
if update_users
|| update_everyone
|| !all_diff.is_empty()
|| !user_group.permissions.is_empty()
{
to_update.push(UpdateItem {
user_group,
update_users,
update_everyone,
all_diff: all_diff
.into_iter()
.map(|(k, (_, v))| (k, v))
@@ -432,6 +539,13 @@ pub async fn run_updates(
&mut has_error,
)
.await;
set_everyone(
user_group.name.clone(),
user_group.everyone,
&mut log,
&mut has_error,
)
.await;
run_update_all(
user_group.name.clone(),
user_group.all,
@@ -452,6 +566,7 @@ pub async fn run_updates(
for UpdateItem {
user_group,
update_users,
update_everyone,
all_diff,
} in to_update
{
@@ -464,6 +579,15 @@ pub async fn run_updates(
)
.await;
}
if update_everyone {
set_everyone(
user_group.name.clone(),
user_group.everyone,
&mut log,
&mut has_error,
)
.await;
}
if !all_diff.is_empty() {
run_update_all(
user_group.name.clone(),
@@ -548,9 +672,44 @@ async fn set_users(
}
}
async fn set_everyone(
user_group: String,
everyone: bool,
log: &mut String,
has_error: &mut bool,
) {
if let Err(e) = (SetEveryoneUserGroup {
user_group: user_group.clone(),
everyone,
})
.resolve(&WriteArgs {
user: sync_user().to_owned(),
})
.await
{
*has_error = true;
log.push_str(&format!(
"\n{}: failed to set everyone for group {} | {:#}",
colored("ERROR", Color::Red),
bold(&user_group),
e.error
))
} else {
log.push_str(&format!(
"\n{}: {} user group '{}' everyone",
muted("INFO"),
colored("updated", Color::Blue),
bold(&user_group)
))
}
}
async fn run_update_all(
user_group: String,
all_diff: HashMap<ResourceTargetVariant, PermissionLevel>,
all_diff: IndexMap<
ResourceTargetVariant,
PermissionLevelAndSpecifics,
>,
log: &mut String,
has_error: &mut bool,
) {
@@ -589,11 +748,16 @@ async fn run_update_permissions(
log: &mut String,
has_error: &mut bool,
) {
for PermissionToml { target, level } in permissions {
for PermissionToml {
target,
level,
specific,
} in permissions
{
if let Err(e) = (UpdatePermissionOnTarget {
user_target: UserTarget::UserGroup(user_group.clone()),
resource_target: target.clone(),
permission: level,
permission: level.specifics(specific.clone()),
})
.resolve(&WriteArgs {
user: sync_user().to_owned(),
@@ -609,12 +773,14 @@ async fn run_update_permissions(
))
} else {
log.push_str(&format!(
"\n{}: {} user group '{}' permissions | {}: {target:?} | {}: {level}",
"\n{}: {} user group '{}' permissions | {}: {target:?} | {}: {level} | {}: {}",
muted("INFO"),
colored("updated", Color::Blue),
bold(&user_group),
muted("target"),
muted("level")
muted("level"),
muted("specific"),
specific.into_iter().map(|s| s.into()).collect::<Vec<&'static str>>().join(", ")
))
}
}
@@ -633,171 +799,217 @@ async fn expand_user_group_permissions(
if id.is_empty() {
continue;
}
if id.starts_with('\\') && id.ends_with('\\') {
let inner = &id[1..(id.len() - 1)];
let regex = Regex::new(inner)
.with_context(|| format!("invalid regex. got: {inner}"))?;
match variant {
ResourceTargetVariant::Build => {
let permissions = all_resources
.builds
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Build(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Builder => {
let permissions = all_resources
.builders
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Builder(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Deployment => {
let permissions = all_resources
.deployments
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Deployment(
resource.name.clone(),
),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Server => {
let permissions = all_resources
.servers
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Server(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Repo => {
let permissions = all_resources
.repos
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Repo(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Alerter => {
let permissions = all_resources
.alerters
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Alerter(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Procedure => {
let permissions = all_resources
.procedures
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Procedure(
resource.name.clone(),
),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Action => {
let permissions = all_resources
.actions
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Action(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::ResourceSync => {
let permissions = all_resources
.syncs
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::ResourceSync(
resource.name.clone(),
),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::Stack => {
let permissions = all_resources
.stacks
.values()
.filter(|resource| regex.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Stack(resource.name.clone()),
level: permission.level,
});
expanded.extend(permissions);
}
ResourceTargetVariant::System => {}
let matcher = Matcher::new(&id)?;
match variant {
ResourceTargetVariant::Build => {
let permissions = all_resources
.builds
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Build(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
} else {
// No regex
expanded.push(permission);
ResourceTargetVariant::Builder => {
let permissions = all_resources
.builders
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Builder(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Deployment => {
let permissions = all_resources
.deployments
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Deployment(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Server => {
let permissions = all_resources
.servers
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Server(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Repo => {
let permissions = all_resources
.repos
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Repo(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Alerter => {
let permissions = all_resources
.alerters
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Alerter(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Procedure => {
let permissions = all_resources
.procedures
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Procedure(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Action => {
let permissions = all_resources
.actions
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Action(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::ResourceSync => {
let permissions = all_resources
.syncs
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::ResourceSync(
resource.name.clone(),
),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Stack => {
let permissions = all_resources
.stacks
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Stack(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::System => {}
}
}
Ok(expanded)
}
type AllDiff =
HashMap<ResourceTargetVariant, (PermissionLevel, PermissionLevel)>;
type AllDiff = IndexMap<
ResourceTargetVariant,
(PermissionLevelAndSpecifics, PermissionLevelAndSpecifics),
>;
fn default_permission() -> &'static PermissionLevelAndSpecifics {
static DEFAULT_PERMISSION: OnceLock<PermissionLevelAndSpecifics> =
OnceLock::new();
DEFAULT_PERMISSION.get_or_init(Default::default)
}
/// diffs user_group.all
fn diff_group_all(
original: &HashMap<ResourceTargetVariant, PermissionLevel>,
incoming: &HashMap<ResourceTargetVariant, PermissionLevel>,
original: &IndexMap<
ResourceTargetVariant,
PermissionLevelAndSpecifics,
>,
incoming: &IndexMap<
ResourceTargetVariant,
PermissionLevelAndSpecifics,
>,
) -> AllDiff {
let mut to_update = HashMap::new();
let mut to_update = IndexMap::new();
// need to compare both forward and backward because either hashmap could be sparse.
// forward direction
for (variant, level) in incoming {
let original_level = original.get(variant).unwrap_or_default();
if level == original_level {
continue;
for (variant, permission) in incoming {
let original_permission =
original.get(variant).unwrap_or(default_permission());
if permission.level != original_permission.level
|| !specific_equal(
&original_permission.specific,
&permission.specific,
)
{
to_update.insert(
*variant,
(original_permission.clone(), permission.clone()),
);
}
to_update.insert(*variant, (*original_level, *level));
}
// backward direction
for (variant, level) in original {
let incoming_level = incoming.get(variant).unwrap_or_default();
if level == incoming_level {
continue;
for (variant, permission) in original {
let incoming_permission =
incoming.get(variant).unwrap_or(default_permission());
if permission.level != incoming_permission.level
|| !specific_equal(
&incoming_permission.specific,
&permission.specific,
)
{
to_update.insert(
*variant,
(permission.clone(), incoming_permission.clone()),
);
}
to_update.insert(*variant, (*level, *incoming_level));
}
to_update
}
fn specific_equal(
a: &IndexSet<SpecificPermission>,
b: &IndexSet<SpecificPermission>,
) -> bool {
for item in a {
if !b.contains(item) {
return false;
}
}
for item in b {
if !a.contains(item) {
return false;
}
}
true
}
pub async fn convert_user_groups(
user_groups: impl Iterator<Item = UserGroup>,
all: &AllResourcesById,
@@ -811,7 +1023,11 @@ pub async fn convert_user_groups(
.map(|user| (user.id, user.username))
.collect::<HashMap<_, _>>();
for user_group in user_groups {
for mut user_group in user_groups {
user_group.all.retain(|_, p| {
p.level > PermissionLevel::None || !p.specific.is_empty()
});
// this method is admin only, but we already know user can see user group if above does not return Err
let mut permissions = (ListUserTargetPermissions {
user_target: UserTarget::UserGroup(user_group.id.clone()),
@@ -825,6 +1041,7 @@ pub async fn convert_user_groups(
.await
.map_err(|e| e.error)?
.into_iter()
.filter(|permission| permission.level > PermissionLevel::None)
.map(|mut permission| {
match &mut permission.resource_target {
ResourceTarget::Build(id) => {
@@ -902,14 +1119,20 @@ pub async fn convert_user_groups(
PermissionToml {
target: permission.resource_target,
level: permission.level,
specific: permission.specific,
}
})
.collect::<Vec<_>>();
let mut users = user_group
.users
.into_iter()
.filter_map(|user_id| usernames.get(&user_id).cloned())
.collect::<Vec<_>>();
let mut users = if user_group.everyone {
Vec::new()
} else {
user_group
.users
.into_iter()
.filter_map(|user_id| usernames.get(&user_id).cloned())
.collect::<Vec<_>>()
};
permissions.sort_by(sort_permissions);
users.sort();
@@ -918,8 +1141,9 @@ pub async fn convert_user_groups(
user_group.id,
UserGroupToml {
name: user_group.name,
users,
everyone: user_group.everyone,
all: user_group.all,
users,
permissions,
},
));

View File

@@ -15,6 +15,14 @@ use crate::{api::write::WriteArgs, state::db_client};
use super::toml::TOML_PRETTY_OPTIONS;
pub fn variable_to_toml(
variable: &Variable,
) -> anyhow::Result<String> {
let inner = toml_pretty::to_string(variable, TOML_PRETTY_OPTIONS)
.context("failed to serialize variable to toml")?;
Ok(format!("[[variable]]\n{inner}"))
}
pub struct ToUpdateItem {
pub variable: Variable,
pub update_value: bool,
@@ -39,11 +47,7 @@ pub async fn get_updates_for_view(
for variable in map.values() {
if !variables.iter().any(|v| v.name == variable.name) {
diffs.push(DiffData::Delete {
current: format!(
"[[variable]]\n{}",
toml_pretty::to_string(&variable, TOML_PRETTY_OPTIONS)
.context("failed to serialize variable to toml")?
),
current: variable_to_toml(variable)?,
});
}
}
@@ -58,26 +62,14 @@ pub async fn get_updates_for_view(
continue;
}
diffs.push(DiffData::Update {
proposed: format!(
"[[variable]]\n{}",
toml_pretty::to_string(variable, TOML_PRETTY_OPTIONS)
.context("failed to serialize variable to toml")?
),
current: format!(
"[[variable]]\n{}",
toml_pretty::to_string(original, TOML_PRETTY_OPTIONS)
.context("failed to serialize variable to toml")?
),
proposed: variable_to_toml(variable)?,
current: variable_to_toml(original)?,
});
}
None => {
diffs.push(DiffData::Create {
name: variable.name.clone(),
proposed: format!(
"[[variable]]\n{}",
toml_pretty::to_string(variable, TOML_PRETTY_OPTIONS)
.context("failed to serialize variable to toml")?
),
proposed: variable_to_toml(variable)?,
});
}
}

View File

@@ -8,12 +8,10 @@ use komodo_client::{
entities::{permission::PermissionLevel, server::Server},
};
use crate::{
helpers::periphery_client, resource, ws::core_periphery_forward_ws,
};
use crate::permission::get_check_permissions;
#[instrument(name = "ConnectContainerExec", skip(ws))]
pub async fn handler(
pub async fn terminal(
Query(ConnectContainerExecQuery {
server,
container,
@@ -22,60 +20,36 @@ pub async fn handler(
ws: WebSocketUpgrade,
) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
let Some((mut client_socket, user)) = super::ws_login(socket).await
let Some((mut client_socket, user)) =
super::ws_login(socket).await
else {
return;
};
let server = match resource::get_check_permissions::<Server>(
let server = match get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Write,
PermissionLevel::Read.terminal(),
)
.await
{
Ok(server) => server,
Err(e) => {
debug!("could not get server | {e:#}");
let _ =
client_socket.send(Message::text(format!("ERROR: {e:#}"))).await;
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
let periphery = match periphery_client(&server) {
Ok(periphery) => periphery,
Err(e) => {
debug!("couldn't get periphery | {e:#}");
let _ =
client_socket.send(Message::text(format!("ERROR: {e:#}"))).await;
let _ = client_socket.close().await;
return;
}
};
trace!("connecting to periphery container exec websocket");
let periphery_socket = match periphery
.connect_container_exec(
container,
shell
)
.await
{
Ok(ws) => ws,
Err(e) => {
debug!("Failed connect to periphery container exec websocket | {e:#}");
let _ =
client_socket.send(Message::text(format!("ERROR: {e:#}"))).await;
let _ = client_socket.close().await;
return;
}
};
trace!("connected to periphery container exec websocket");
core_periphery_forward_ws(client_socket, periphery_socket).await
super::handle_container_terminal(
client_socket,
&server,
container,
shell,
)
.await
})
}

View File

@@ -0,0 +1,69 @@
use axum::{
extract::{Query, WebSocketUpgrade, ws::Message},
response::IntoResponse,
};
use futures::SinkExt;
use komodo_client::{
api::terminal::ConnectDeploymentExecQuery,
entities::{
deployment::Deployment, permission::PermissionLevel,
server::Server,
},
};
use crate::{permission::get_check_permissions, resource::get};
#[instrument(name = "ConnectDeploymentExec", skip(ws))]
pub async fn terminal(
Query(ConnectDeploymentExecQuery { deployment, shell }): Query<
ConnectDeploymentExecQuery,
>,
ws: WebSocketUpgrade,
) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
let Some((mut client_socket, user)) =
super::ws_login(socket).await
else {
return;
};
let deployment = match get_check_permissions::<Deployment>(
&deployment,
&user,
PermissionLevel::Read.terminal(),
)
.await
{
Ok(deployment) => deployment,
Err(e) => {
debug!("could not get deployment | {e:#}");
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
let server =
match get::<Server>(&deployment.config.server_id).await {
Ok(server) => server,
Err(e) => {
debug!("could not get server | {e:#}");
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
super::handle_container_terminal(
client_socket,
&server,
deployment.name,
shell,
)
.await
})
}

View File

@@ -9,7 +9,10 @@ use axum::{
routing::get,
};
use futures::{SinkExt, StreamExt};
use komodo_client::{entities::user::User, ws::WsLoginMessage};
use komodo_client::{
entities::{server::Server, user::User},
ws::WsLoginMessage,
};
use tokio::net::TcpStream;
use tokio_tungstenite::{
MaybeTlsStream, WebSocketStream, tungstenite,
@@ -17,6 +20,8 @@ use tokio_tungstenite::{
use tokio_util::sync::CancellationToken;
mod container;
mod deployment;
mod stack;
mod terminal;
mod update;
@@ -24,7 +29,9 @@ pub fn router() -> Router {
Router::new()
.route("/update", get(update::handler))
.route("/terminal", get(terminal::handler))
.route("/container", get(container::handler))
.route("/container/terminal", get(container::terminal))
.route("/deployment/terminal", get(deployment::terminal))
.route("/stack/terminal", get(stack::terminal))
}
#[instrument(level = "debug")]
@@ -118,6 +125,48 @@ async fn check_user_valid(user_id: &str) -> anyhow::Result<User> {
Ok(user)
}
async fn handle_container_terminal(
mut client_socket: WebSocket,
server: &Server,
container: String,
shell: String,
) {
let periphery = match crate::helpers::periphery_client(server) {
Ok(periphery) => periphery,
Err(e) => {
debug!("couldn't get periphery | {e:#}");
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
trace!("connecting to periphery container exec websocket");
let periphery_socket = match periphery
.connect_container_exec(container, shell)
.await
{
Ok(ws) => ws,
Err(e) => {
debug!(
"Failed connect to periphery container exec websocket | {e:#}"
);
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
trace!("connected to periphery container exec websocket");
core_periphery_forward_ws(client_socket, periphery_socket).await
}
async fn core_periphery_forward_ws(
client_socket: axum::extract::ws::WebSocket,
periphery_socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
@@ -143,9 +192,7 @@ async fn core_periphery_forward_ws(
if let Err(e) =
periphery_send.send(axum_to_tungstenite(msg)).await
{
debug!(
"Failed to send terminal message | {e:?}",
);
debug!("Failed to send terminal message | {e:?}",);
cancel.cancel();
break;
};

90
bin/core/src/ws/stack.rs Normal file
View File

@@ -0,0 +1,90 @@
use axum::{
extract::{Query, WebSocketUpgrade, ws::Message},
response::IntoResponse,
};
use futures::SinkExt;
use komodo_client::{
api::terminal::ConnectStackExecQuery,
entities::{
permission::PermissionLevel, server::Server, stack::Stack,
},
};
use crate::{permission::get_check_permissions, resource::get};
#[instrument(name = "ConnectStackExec", skip(ws))]
pub async fn terminal(
Query(ConnectStackExecQuery {
stack,
service,
shell,
}): Query<ConnectStackExecQuery>,
ws: WebSocketUpgrade,
) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
let Some((mut client_socket, user)) =
super::ws_login(socket).await
else {
return;
};
let stack = match get_check_permissions::<Stack>(
&stack,
&user,
PermissionLevel::Read.terminal(),
)
.await
{
Ok(stack) => stack,
Err(e) => {
debug!("could not get stack | {e:#}");
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
let server = match get::<Server>(&stack.config.server_id).await {
Ok(server) => server,
Err(e) => {
debug!("could not get server | {e:#}");
let _ = client_socket
.send(Message::text(format!("ERROR: {e:#}")))
.await;
let _ = client_socket.close().await;
return;
}
};
let services = stack
.info
.deployed_services
.unwrap_or(stack.info.latest_services);
let container = match services
.into_iter()
.find(|s| s.service_name == service)
{
Some(service) => service.container_name,
None => {
let _ = client_socket
.send(Message::text(format!(
"ERROR: Service {service} could not be found"
)))
.await;
let _ = client_socket.close().await;
return;
}
};
super::handle_container_terminal(
client_socket,
&server,
container,
shell,
)
.await
})
}

View File

@@ -9,7 +9,8 @@ use komodo_client::{
};
use crate::{
helpers::periphery_client, resource, ws::core_periphery_forward_ws,
helpers::periphery_client, permission::get_check_permissions,
ws::core_periphery_forward_ws,
};
#[instrument(name = "ConnectTerminal", skip(ws))]
@@ -26,10 +27,10 @@ pub async fn handler(
return;
};
let server = match resource::get_check_permissions::<Server>(
let server = match get_check_permissions::<Server>(
&server,
&user,
PermissionLevel::Write,
PermissionLevel::Read.terminal(),
)
.await
{

View File

@@ -90,9 +90,9 @@ async fn user_can_see_update(
if user.admin {
return Ok(());
}
let permissions =
let permission =
get_user_permission_on_target(user, update_target).await?;
if permissions > PermissionLevel::None {
if permission.level > PermissionLevel::None {
Ok(())
} else {
Err(anyhow!(

View File

@@ -1,6 +1,6 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
FROM rust:1.86.0-bullseye AS builder
FROM rust:1.87.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./

View File

@@ -14,7 +14,7 @@ use komodo_client::{
EnvironmentVar, Version,
build::{Build, BuildConfig},
environment_vars_from_str, get_image_name, optional_string,
to_komodo_name,
to_docker_compatible_name, to_path_compatible_name,
update::Log,
},
parsers::QUOTE_PATTERN,
@@ -45,8 +45,9 @@ impl Resolve<super::Args> for GetDockerfileContentsOnHost {
dockerfile_path,
} = self;
let root =
periphery_config().build_dir().join(to_komodo_name(&name));
let root = periphery_config()
.build_dir()
.join(to_path_compatible_name(&name));
let build_dir =
root.join(&build_path).components().collect::<PathBuf>();
@@ -92,7 +93,7 @@ impl Resolve<super::Args> for WriteDockerfileContentsToHost {
} = self;
let full_path = periphery_config()
.build_dir()
.join(to_komodo_name(&name))
.join(to_path_compatible_name(&name))
.join(&build_path)
.join(dockerfile_path)
.components()
@@ -177,7 +178,7 @@ impl Resolve<super::Args> for build::Build {
}
};
let name = to_komodo_name(name);
let name = to_docker_compatible_name(name);
let build_path =
periphery_config().build_dir().join(&name).join(build_path);

View File

@@ -5,7 +5,8 @@ use command::run_komodo_command;
use formatting::format_serror;
use git::{GitRes, write_commit_file};
use komodo_client::entities::{
FileContents, stack::ComposeProject, to_komodo_name, update::Log,
FileContents, stack::ComposeProject, to_path_compatible_name,
update::Log,
};
use periphery_client::api::{compose::*, git::RepoActionResponse};
use resolver_api::Resolve;
@@ -137,8 +138,9 @@ impl Resolve<super::Args> for GetComposeContentsOnHost {
run_directory,
file_paths,
} = self;
let root =
periphery_config().stack_dir().join(to_komodo_name(&name));
let root = periphery_config()
.stack_dir()
.join(to_path_compatible_name(&name));
let run_directory =
root.join(&run_directory).components().collect::<PathBuf>();
@@ -197,7 +199,7 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
} = self;
let file_path = periphery_config()
.stack_dir()
.join(to_komodo_name(&name))
.join(to_path_compatible_name(&name))
.join(&run_directory)
.join(file_path)
.components()

View File

@@ -3,7 +3,7 @@ use command::run_komodo_command;
use futures::future::join_all;
use komodo_client::entities::{
docker::container::{Container, ContainerListItem, ContainerStats},
to_komodo_name,
to_docker_compatible_name,
update::Log,
};
use periphery_client::api::container::*;
@@ -234,7 +234,7 @@ impl Resolve<super::Args> for RenameContainer {
curr_name,
new_name,
} = self;
let new = to_komodo_name(&new_name);
let new = to_docker_compatible_name(&new_name);
let command = format!("docker rename {curr_name} {new}");
Ok(run_komodo_command("Docker Rename", None, command).await)
}

View File

@@ -10,7 +10,7 @@ use komodo_client::{
Conversion, Deployment, DeploymentConfig, DeploymentImage,
RestartMode, conversions_from_str, extract_registry_domain,
},
environment_vars_from_str, to_komodo_name,
environment_vars_from_str, to_docker_compatible_name,
update::Log,
},
parsers::QUOTE_PATTERN,
@@ -129,7 +129,7 @@ fn docker_run_command(
}: &Deployment,
image: &str,
) -> anyhow::Result<String> {
let name = to_komodo_name(name);
let name = to_docker_compatible_name(name);
let ports = parse_conversions(
&conversions_from_str(ports).context("Invalid ports")?,
"-p",

View File

@@ -14,7 +14,7 @@ use komodo_client::entities::{
ComposeFile, ComposeService, ComposeServiceDeploy, Stack,
StackServiceNames,
},
to_komodo_name,
to_path_compatible_name,
update::Log,
};
use periphery_client::api::{
@@ -431,7 +431,7 @@ pub async fn write_stack(
)> {
let root = periphery_config()
.stack_dir()
.join(to_komodo_name(&stack.name));
.join(to_path_compatible_name(&stack.name));
let run_directory = root.join(&stack.config.run_directory);
// This will remove any intermediate '/./' in the path, which is a problem for some OS.
// Cannot use 'canonicalize' yet as directory may not exist.
@@ -694,7 +694,7 @@ async fn compose_down(
format!(" {}", services.join(" "))
};
let log = run_komodo_command(
"compose down",
"Compose Down",
None,
format!("{docker_compose} -p {project} down{service_args}"),
)

View File

@@ -73,6 +73,9 @@ pub fn periphery_config() -> &'static PeripheryConfig {
.periphery_logging_opentelemetry_service_name
.unwrap_or(config.logging.opentelemetry_service_name),
},
pretty_startup_config: env
.periphery_pretty_startup_config
.unwrap_or(config.pretty_startup_config),
allowed_ips: env
.periphery_allowed_ips
.unwrap_or(config.allowed_ips),

View File

@@ -3,8 +3,11 @@ use std::{collections::HashMap, sync::OnceLock};
use anyhow::{Context, anyhow};
use bollard::{
Docker,
container::{InspectContainerOptions, ListContainersOptions},
network::InspectNetworkOptions,
query_parameters::{
InspectContainerOptions, InspectNetworkOptions,
ListContainersOptions, ListImagesOptions, ListNetworksOptions,
ListVolumesOptions,
},
};
use command::run_komodo_command;
use komodo_client::entities::{
@@ -13,7 +16,7 @@ use komodo_client::entities::{
ContainerConfig, GraphDriverData, HealthConfig, PortBinding,
container::*, image::*, network::*, volume::*,
},
to_komodo_name,
to_docker_compatible_name,
update::Log,
};
use run_command::async_run_command;
@@ -42,7 +45,7 @@ impl DockerClient {
) -> anyhow::Result<Vec<ContainerListItem>> {
let mut containers = self
.docker
.list_containers(Some(ListContainersOptions::<String> {
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
@@ -63,11 +66,16 @@ impl DockerClient {
created: container.created,
size_rw: container.size_rw,
size_root_fs: container.size_root_fs,
state: container
.state
.context("no container state")?
.parse()
.context("failed to parse container state")?,
state: match container.state.context("no container state")? {
bollard::secret::ContainerSummaryStateEnum::EMPTY => ContainerStateStatusEnum::Empty,
bollard::secret::ContainerSummaryStateEnum::CREATED => ContainerStateStatusEnum::Created,
bollard::secret::ContainerSummaryStateEnum::RUNNING => ContainerStateStatusEnum::Running,
bollard::secret::ContainerSummaryStateEnum::PAUSED => ContainerStateStatusEnum::Paused,
bollard::secret::ContainerSummaryStateEnum::RESTARTING => ContainerStateStatusEnum::Restarting,
bollard::secret::ContainerSummaryStateEnum::EXITED => ContainerStateStatusEnum::Exited,
bollard::secret::ContainerSummaryStateEnum::REMOVING => ContainerStateStatusEnum::Removing,
bollard::secret::ContainerSummaryStateEnum::DEAD => ContainerStateStatusEnum::Dead,
},
status: container.status,
network_mode: container
.host_config
@@ -371,6 +379,7 @@ impl DockerClient {
bollard::secret::MountTypeEnum::EMPTY => MountTypeEnum::Empty,
bollard::secret::MountTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountTypeEnum::VOLUME => MountTypeEnum::Volume,
bollard::secret::MountTypeEnum::IMAGE => MountTypeEnum::Image,
bollard::secret::MountTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
bollard::secret::MountTypeEnum::NPIPE => MountTypeEnum::Npipe,
bollard::secret::MountTypeEnum::CLUSTER => MountTypeEnum::Cluster,
@@ -456,6 +465,7 @@ impl DockerClient {
bollard::secret::MountPointTypeEnum::EMPTY => MountTypeEnum::Empty,
bollard::secret::MountPointTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountPointTypeEnum::VOLUME => MountTypeEnum::Volume,
bollard::secret::MountPointTypeEnum::IMAGE => MountTypeEnum::Image,
bollard::secret::MountPointTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
bollard::secret::MountPointTypeEnum::NPIPE => MountTypeEnum::Npipe,
bollard::secret::MountPointTypeEnum::CLUSTER => MountTypeEnum::Cluster,
@@ -543,7 +553,7 @@ impl DockerClient {
) -> anyhow::Result<Vec<NetworkListItem>> {
let networks = self
.docker
.list_networks::<String>(None)
.list_networks(Option::<ListNetworksOptions>::None)
.await?
.into_iter()
.map(|network| {
@@ -593,7 +603,7 @@ impl DockerClient {
) -> anyhow::Result<Network> {
let network = self
.docker
.inspect_network::<String>(
.inspect_network(
network_name,
InspectNetworkOptions {
verbose: true,
@@ -653,7 +663,7 @@ impl DockerClient {
) -> anyhow::Result<Vec<ImageListItem>> {
let images = self
.docker
.list_images::<String>(None)
.list_images(Option::<ListImagesOptions>::None)
.await?
.into_iter()
.map(|image| {
@@ -787,7 +797,7 @@ impl DockerClient {
) -> anyhow::Result<Vec<VolumeListItem>> {
let volumes = self
.docker
.list_volumes::<String>(None)
.list_volumes(Option::<ListVolumesOptions>::None)
.await?
.volumes
.unwrap_or_default()
@@ -977,7 +987,7 @@ pub fn stop_container_command(
signal: Option<TerminationSignal>,
time: Option<i32>,
) -> String {
let container_name = to_komodo_name(container_name);
let container_name = to_docker_compatible_name(container_name);
let signal = signal
.map(|signal| format!(" --signal {signal}"))
.unwrap_or_default();

View File

@@ -4,7 +4,7 @@ use anyhow::{Context, anyhow};
use komodo_client::{
entities::{
CloneArgs, EnvironmentVar, SearchCombinator, stack::Stack,
to_komodo_name,
to_path_compatible_name,
},
parsers::QUOTE_PATTERN,
};
@@ -102,7 +102,7 @@ pub async fn pull_or_clone_stack(
let root = periphery_config()
.stack_dir()
.join(to_komodo_name(&stack.name));
.join(to_path_compatible_name(&stack.name));
let mut args: CloneArgs = stack.into();
// Set the clone destination to the one created for this run

View File

@@ -6,6 +6,7 @@ use std::{net::SocketAddr, str::FromStr};
use anyhow::Context;
use axum_server::tls_rustls::RustlsConfig;
use config::periphery_config;
mod api;
mod compose;
@@ -22,7 +23,12 @@ async fn app() -> anyhow::Result<()> {
logger::init(&config.logging)?;
info!("Komodo Periphery version: v{}", env!("CARGO_PKG_VERSION"));
info!("{:?}", config.sanitized());
if periphery_config().pretty_startup_config {
info!("{:#?}", config.sanitized());
} else {
info!("{:?}", config.sanitized());
}
stats::spawn_system_stats_polling_thread();

23
bin/util/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "komodo_util"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
[[bin]]
name = "util"
path = "src/main.rs"
[dependencies]
tracing-subscriber.workspace = true
futures-util.workspace = true
dotenvy.workspace = true
tracing.workspace = true
anyhow.workspace = true
mungos.workspace = true
tokio.workspace = true
serde.workspace = true
envy.workspace = true

22
bin/util/aio.Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM rust:1.87.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
COPY ./lib ./lib
COPY ./client/core/rs ./client/core/rs
COPY ./client/periphery ./client/periphery
COPY ./bin/util ./bin/util
# Compile bin
RUN cargo build -p komodo_util --release
# Copy binaries to distroless base
FROM gcr.io/distroless/cc
COPY --from=builder /builder/target/release/util /usr/local/bin/util
CMD [ "util" ]
LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo
LABEL org.opencontainers.image.description="Komodo Util"
LABEL org.opencontainers.image.licenses=GPL-3.0

View File

@@ -0,0 +1,139 @@
# Copy Database Utility
Copy the Komodo database contents between running, mongo-compatible databases.
Can be used to move between MongoDB / FerretDB, or upgrade from FerretDB v1 to v2.
```yaml
services:
copy_database:
image: ghcr.io/moghtech/komodo-util
environment:
MODE: CopyDatabase
SOURCE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@source:27017
SOURCE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@target:27017
TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
```
## FerretDB v2 Update Guide
Up to Komodo 1.17.5, users who wanted to use Postgres / Sqlite were instructed to deploy FerretDB v1.
Now that v2 is out however, v1 will go largely unsupported. Users are recommended to migrate to v2 for
the best performance and ongoing support / updates, however the internal data structures
have changed and this cannot be done in-place.
Also note that FerretDB v2 no longer supports Sqlite, and only supports
a [customized Postgres distribution](https://docs.ferretdb.io/installation/documentdb/docker/).
Nonetheless, it remains a solid option for hosts which [do not support mongo](https://github.com/moghtech/komodo/issues/59).
Also note, the same basic process outlined below can also be used to move between MongoDB and FerretDB, just replace FerretDB v2
with the database you wish to move to.
### **Step 1**: *Add* the new database to the top of your existing Komodo compose file.
**Don't forget to also add the new volumes.**
```yaml
## In Komodo compose.yaml
services:
postgres2:
# Recommended: Pin to a specific version
# https://github.com/FerretDB/documentdb/pkgs/container/postgres-documentdb
image: ghcr.io/ferretdb/postgres-documentdb
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
logging:
driver: ${COMPOSE_LOGGING_DRIVER:-local}
# ports:
# - 5432:5432
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${KOMODO_DB_USERNAME}
POSTGRES_PASSWORD: ${KOMODO_DB_PASSWORD}
POSTGRES_DB: postgres
ferretdb2:
# Recommended: Pin to a specific version
# https://github.com/FerretDB/FerretDB/pkgs/container/ferretdb
image: ghcr.io/ferretdb/ferretdb
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
depends_on:
- postgres2
logging:
driver: ${COMPOSE_LOGGING_DRIVER:-local}
# ports:
# - 27017:27017
volumes:
- ferretdb-state:/state
environment:
FERRETDB_POSTGRESQL_URL: postgres://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@postgres2:5432/postgres
...(unchanged)
volumes:
...(unchanged)
postgres-data:
ferretdb-state:
```
### **Step 2**: *Add* the database copy utility to Komodo compose file.
The SOURCE_URI points to the existing database, ie the old FerretDB v1, and it depends
on whether it was deployed using Postgres or Sqlite. The example below uses the Postgres one,
but if you use Sqlite it should just be something like `mongodb://ferretdb:27017`.
```yaml
## In Komodo compose.yaml
services:
...(new database)
copy_database:
image: ghcr.io/moghtech/komodo-util
environment:
MODE: CopyDatabase
SOURCE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb:27017/${KOMODO_DATABASE_DB_NAME:-komodo}?authMechanism=PLAIN
SOURCE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb2:27017
TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
...(unchanged)
```
### **Step 3**: *Compose Up* the new additions
Run `docker compose -p komodo --env-file compose.env -f xxxxx.compose.yaml up -d`, filling in the name of your compose.yaml.
This will start up both the old and new database, and copy the data to the new one.
Wait a few moments for the `copy_database` service to finish. When it exits,
confirm the logs show the data was moved successfully, and move on to the next step.
### **Step 4**: Point Komodo Core to the new database
In your Komodo compose.yaml, first *comment out* the `copy_database` service and old ferretdb v1 service/s.
Then update the `core` service environment to point to `ferretdb2`.
```yaml
services:
...
core:
...(unchanged)
environment:
KOMODO_DATABASE_ADDRESS: ferretdb2:27017
KOMODO_DATABASE_USERNAME: ${KOMODO_DB_USERNAME}
KOMODO_DATABASE_PASSWORD: ${KOMODO_DB_PASSWORD}
```
### **Step 5**: Final *Compose Up*
Repeat the same `docker compose` command as before to apply the changes, and then try navigating to your Komodo web page.
If it works, congrats, **you are done**. You can clean up the compose file if you would like, removing the old volumes etc.
If it does not work, check the logs for any obvious issues, and if necessary you can undo the previous steps
to go back to using the previous database.

View File

@@ -0,0 +1,27 @@
## Assumes the latest binaries for x86_64 and aarch64 are already built (by binaries.Dockerfile).
## Since theres no heavy build here, QEMU multi-arch builds are fine for this image.
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64
ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
# This is required to work with COPY --from
FROM ${X86_64_BINARIES} AS x86_64
FROM ${AARCH64_BINARIES} AS aarch64
FROM debian:bullseye-slim
WORKDIR /app
## Copy both binaries initially, but only keep appropriate one for the TARGETPLATFORM.
COPY --from=x86_64 /util /app/arch/linux/amd64
COPY --from=aarch64 /util /app/arch/linux/arm64
ARG TARGETPLATFORM
RUN mv /app/arch/${TARGETPLATFORM} /usr/local/bin/util && rm -r /app/arch
LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo
LABEL org.opencontainers.image.description="Komodo Util"
LABEL org.opencontainers.image.licenses=GPL-3.0
CMD [ "util" ]

View File

@@ -0,0 +1,16 @@
## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile).
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
# This is required to work with COPY --from
FROM ${BINARIES_IMAGE} AS binaries
FROM gcr.io/distroless/cc
COPY --from=binaries /util /usr/local/bin/util
LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo
LABEL org.opencontainers.image.description="Komodo Util"
LABEL org.opencontainers.image.licenses=GPL-3.0
CMD [ "util" ]

View File

@@ -0,0 +1,131 @@
use std::time::Duration;
use anyhow::Context;
use futures_util::{TryStreamExt, future::join_all};
use mungos::{
init::MongoBuilder,
mongodb::{
bson::{Document, RawDocumentBuf},
options::InsertManyOptions,
},
};
use serde::Deserialize;
#[derive(Deserialize)]
struct Env {
/// Provide the source mongo uri to copy from
source_uri: String,
/// Provide the source db name to copy from.
/// Default: komodo
#[serde(default = "default_db_name")]
source_db_name: String,
/// Provide the source mongo uri to copy to
target_uri: String,
/// Provide the target db name to copy to.
/// Default: komodo
#[serde(default = "default_db_name")]
target_db_name: String,
/// Give the target database some time to initialize.
#[serde(default = "default_startup_sleep_seconds")]
startup_sleep_seconds: u64,
}
fn default_db_name() -> String {
String::from("komodo")
}
fn default_startup_sleep_seconds() -> u64 {
5
}
pub async fn main() -> anyhow::Result<()> {
let env = envy::from_env::<Env>()?;
info!("Sleeping for {} seconds...", env.startup_sleep_seconds);
tokio::time::sleep(Duration::from_secs(env.startup_sleep_seconds))
.await;
info!("Copying database...");
let source_db = MongoBuilder::default()
.uri(env.source_uri)
.build()
.await
.context("Invalid SOURCE_URI")?
.database(&env.source_db_name);
let target_db = MongoBuilder::default()
.uri(env.target_uri)
.build()
.await
.context("Invalid SOURCE_URI")?
.database(&env.target_db_name);
let mut handles = Vec::new();
for collection in source_db
.list_collection_names()
.await
.context("Failed to list collections on source db")?
{
let source = source_db.collection::<RawDocumentBuf>(&collection);
let target = target_db.collection::<RawDocumentBuf>(&collection);
handles.push(tokio::spawn(async move {
let res = async {
let mut buffer = Vec::<RawDocumentBuf>::new();
let mut count = 0;
let mut cursor = source
.find(Document::new())
.await
.context("Failed to query source collection")?;
while let Some(doc) = cursor
.try_next()
.await
.context("Failed to get next document")?
{
count += 1;
buffer.push(doc);
if buffer.len() >= 20_000 {
if let Err(e) = target
.insert_many(&buffer)
.with_options(
InsertManyOptions::builder().ordered(false).build(),
)
.await
{
error!("Failed to flush document batch in {collection} collection | {e:#}");
};
buffer.clear();
}
}
if !buffer.is_empty() {
target
.insert_many(&buffer)
.with_options(
InsertManyOptions::builder().ordered(false).build(),
)
.await
.context("Failed to flush documents")?;
}
anyhow::Ok(count)
}
.await;
match res {
Ok(count) => {
if count > 0 {
info!("Finished copying {collection} collection | Copied {count}");
}
}
Err(e) => {
error!("Failed to copy {collection} collection | {e:#}")
}
}
}));
}
join_all(handles).await;
info!("Finished copying database ✅");
Ok(())
}

42
bin/util/src/main.rs Normal file
View File

@@ -0,0 +1,42 @@
#[macro_use]
extern crate tracing;
use serde::Deserialize;
mod copy_database;
#[derive(Deserialize, Debug, Default)]
enum Mode {
#[default]
CopyDatabase,
}
#[derive(Deserialize)]
struct Env {
mode: Mode,
}
async fn app() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
let env = envy::from_env::<Env>()?;
info!("Komodo Util version: v{}", env!("CARGO_PKG_VERSION"));
info!("Mode: {:?}", env.mode);
match env.mode {
Mode::CopyDatabase => copy_database::main().await,
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut term_signal = tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::terminate(),
)?;
tokio::select! {
res = tokio::spawn(app()) => res?,
_ = term_signal.recv() => Ok(()),
}
}

View File

@@ -32,6 +32,7 @@ serde_json.workspace = true
tokio-util.workspace = true
thiserror.workspace = true
typeshare.workspace = true
indexmap.workspace = true
futures.workspace = true
reqwest.workspace = true
tracing.workspace = true

View File

@@ -9,7 +9,7 @@ use crate::entities::{
Deployment, DeploymentActionState, DeploymentListItem,
DeploymentQuery, DeploymentState,
},
docker::container::{ContainerListItem, ContainerStats},
docker::container::{Container, ContainerListItem, ContainerStats},
update::Log,
};
@@ -105,6 +105,26 @@ pub struct GetDeploymentContainerResponse {
//
/// Inspect the docker container associated with the Deployment.
/// Response: [Container].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectDeploymentContainerResponse)]
#[error(serror::Error)]
pub struct InspectDeploymentContainer {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub deployment: String,
}
#[typeshare]
pub type InspectDeploymentContainerResponse = Container;
//
/// Get the deployment log's tail, split by stdout/stderr.
/// Response: [Log].
///

View File

@@ -5,7 +5,7 @@ use typeshare::typeshare;
use crate::entities::{
ResourceTarget,
permission::{Permission, PermissionLevel, UserTarget},
permission::{Permission, PermissionLevelAndSpecifics, UserTarget},
};
use super::KomodoReadRequest;
@@ -35,15 +35,15 @@ pub type ListPermissionsResponse = Vec<Permission>;
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(GetPermissionLevelResponse)]
#[response(GetPermissionResponse)]
#[error(serror::Error)]
pub struct GetPermissionLevel {
pub struct GetPermission {
/// The target to get user permission on.
pub target: ResourceTarget,
}
#[typeshare]
pub type GetPermissionLevelResponse = PermissionLevel;
pub type GetPermissionResponse = PermissionLevelAndSpecifics;
//

View File

@@ -5,6 +5,7 @@ use typeshare::typeshare;
use crate::entities::{
SearchCombinator, U64,
docker::container::Container,
stack::{
Stack, StackActionState, StackListItem, StackQuery, StackService,
},
@@ -53,6 +54,28 @@ pub type ListStackServicesResponse = Vec<StackService>;
//
/// Inspect the docker container associated with the Stack.
/// Response: [Container].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectStackContainerResponse)]
#[error(serror::Error)]
pub struct InspectStackContainer {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub stack: String,
/// The service name to inspect
pub service: String,
}
#[typeshare]
pub type InspectStackContainerResponse = Container;
//
/// Get a stack's logs. Filter down included services. Response: [GetStackLogResponse].
///
/// Note. This call will hit the underlying server directly for most up to date log.

View File

@@ -28,6 +28,32 @@ pub struct ConnectContainerExecQuery {
pub shell: String,
}
/// Query to connect to a container exec session (interactive shell over websocket) on the given Deployment.
/// This call will use access to the Deployment Terminal to permission the call.
/// TODO: Document calling.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectDeploymentExecQuery {
/// Deployment Id or name
pub deployment: String,
/// The shell to connect to
pub shell: String,
}
/// Query to connect to a container exec session (interactive shell over websocket) on the given Stack / service.
/// This call will use access to the Stack Terminal to permission the call.
/// TODO: Document calling.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectStackExecQuery {
/// Stack Id or name
pub stack: String,
/// The service name to connect to
pub service: String,
/// The shell to connect to
pub shell: String,
}
/// Execute a terminal command on the given server.
/// TODO: Document calling.
#[typeshare]

View File

@@ -5,7 +5,7 @@ use typeshare::typeshare;
use crate::entities::{
NoData, ResourceTarget, ResourceTargetVariant,
permission::{PermissionLevel, UserTarget},
permission::{PermissionLevelAndSpecifics, UserTarget},
};
use super::KomodoWriteRequest;
@@ -25,7 +25,7 @@ pub struct UpdatePermissionOnTarget {
/// Specify the target resource.
pub resource_target: ResourceTarget,
/// Specify the permission level.
pub permission: PermissionLevel,
pub permission: PermissionLevelAndSpecifics,
}
#[typeshare]
@@ -48,7 +48,7 @@ pub struct UpdatePermissionOnResourceType {
/// The resource type: eg. Server, Build, Deployment, etc.
pub resource_type: ResourceTargetVariant,
/// The base permission level.
pub permission: PermissionLevel,
pub permission: PermissionLevelAndSpecifics,
}
#[typeshare]

View File

@@ -88,7 +88,7 @@ pub struct RemoveUserFromUserGroup {
//
/// **Admin only.** Completely override the user in the group.
/// **Admin only.** Completely override the users in the group.
/// Response: [UserGroup]
#[typeshare]
#[derive(
@@ -103,3 +103,21 @@ pub struct SetUsersInUserGroup {
/// The user ids or usernames to hard set as the group's users.
pub users: Vec<String>,
}
//
/// **Admin only.** Set `everyone` property of User Group.
/// Response: [UserGroup]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(UserGroup)]
#[error(serror::Error)]
pub struct SetEveryoneUserGroup {
/// Id or name.
pub user_group: String,
/// Whether this user group applies to everyone.
pub everyone: bool,
}

View File

@@ -5,6 +5,7 @@ mod environment;
mod file_contents;
mod labels;
mod maybe_string_i64;
mod permission;
mod string_list;
mod term_signal_labels;

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