diff --git a/.gitignore b/.gitignore
index c507849..3acd8af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
target
.idea
+files
diff --git a/.idea/stacker.iml b/.idea/stacker.iml
index 227e58a..a97e925 100644
--- a/.idea/stacker.iml
+++ b/.idea/stacker.iml
@@ -4,6 +4,8 @@
+
+
diff --git a/Cargo.lock b/Cargo.lock
index a4b8f33..2ac8449 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -279,6 +279,54 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+[[package]]
+name = "amq-protocol"
+version = "7.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d40d8b2465c7959dd40cee32ba6ac334b5de57e9fca0cc756759894a4152a5d"
+dependencies = [
+ "amq-protocol-tcp",
+ "amq-protocol-types",
+ "amq-protocol-uri",
+ "cookie-factory",
+ "nom",
+ "serde",
+]
+
+[[package]]
+name = "amq-protocol-tcp"
+version = "7.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cb2100adae7da61953a2c3a01935d86caae13329fadce3333f524d6d6ce12e2"
+dependencies = [
+ "amq-protocol-uri",
+ "tcp-stream",
+ "tracing",
+]
+
+[[package]]
+name = "amq-protocol-types"
+version = "7.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "156ff13c8a3ced600b4e54ed826a2ae6242b6069d00dd98466827cef07d3daff"
+dependencies = [
+ "cookie-factory",
+ "nom",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "amq-protocol-uri"
+version = "7.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "751bbd7d440576066233e740576f1b31fdc6ab86cfabfbd48c548de77eca73e4"
+dependencies = [
+ "amq-protocol-types",
+ "percent-encoding",
+ "url",
+]
+
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -294,6 +342,104 @@ dependencies = [
"libc",
]
+[[package]]
+name = "async-channel"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand 2.0.1",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "blocking",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "async-global-executor-trait"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75"
+dependencies = [
+ "async-global-executor",
+ "async-trait",
+ "executor-trait",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite",
+ "log",
+ "parking",
+ "polling",
+ "rustix 0.37.27",
+ "slab",
+ "socket2 0.4.9",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-reactor-trait"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e"
+dependencies = [
+ "async-io",
+ "async-trait",
+ "futures-core",
+ "reactor-trait",
+]
+
+[[package]]
+name = "async-task"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1"
+
[[package]]
name = "async-trait"
version = "0.1.74"
@@ -314,6 +460,12 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -368,6 +520,31 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "fastrand 2.0.1",
+ "futures-io",
+ "futures-lite",
+ "piper",
+ "tracing",
+]
+
[[package]]
name = "brotli"
version = "3.4.0"
@@ -416,6 +593,15 @@ dependencies = [
"bytes",
]
+[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "cc"
version = "1.0.83"
@@ -448,6 +634,25 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "config"
version = "0.13.3"
@@ -484,6 +689,12 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "cookie-factory"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
+
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -562,6 +773,41 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "deranged"
version = "0.3.9"
@@ -571,6 +817,37 @@ dependencies = [
"powerfmt",
]
+[[package]]
+name = "derive_builder"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
+dependencies = [
+ "derive_builder_core",
+ "syn 1.0.109",
+]
+
[[package]]
name = "derive_more"
version = "0.99.17"
@@ -584,6 +861,15 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "des"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -621,6 +907,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
[[package]]
name = "dotenvy"
version = "0.15.7"
@@ -645,6 +937,12 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
[[package]]
name = "errno"
version = "0.3.5"
@@ -661,6 +959,24 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+[[package]]
+name = "executor-trait"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08"
+dependencies = [
+ "async-trait",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
[[package]]
name = "fastrand"
version = "2.0.1"
@@ -683,6 +999,18 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "flume"
+version = "0.10.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "pin-project",
+ "spin 0.9.8",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -772,6 +1100,21 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
[[package]]
name = "futures-macro"
version = "0.3.29"
@@ -850,6 +1193,12 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
[[package]]
name = "h2"
version = "0.3.21"
@@ -862,7 +1211,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
- "indexmap",
+ "indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
@@ -1030,6 +1379,12 @@ dependencies = [
"cc",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "0.4.0"
@@ -1051,6 +1406,27 @@ dependencies = [
"serde",
]
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.1",
+ "serde",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -1060,6 +1436,17 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
[[package]]
name = "ipnet"
version = "2.8.0"
@@ -1125,6 +1512,29 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
+[[package]]
+name = "lapin"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f3067a1fcfbc3fc46455809c023e69b8f6602463201010f4ae5a3b572adb9dc"
+dependencies = [
+ "amq-protocol",
+ "async-global-executor-trait",
+ "async-reactor-trait",
+ "async-trait",
+ "executor-trait",
+ "flume",
+ "futures-core",
+ "futures-io",
+ "parking_lot 0.12.1",
+ "pinky-swear",
+ "reactor-trait",
+ "serde",
+ "serde_json",
+ "tracing",
+ "waker-fn",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -1143,6 +1553,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
[[package]]
name = "linux-raw-sys"
version = "0.4.10"
@@ -1372,6 +1788,29 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+[[package]]
+name = "p12"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224"
+dependencies = [
+ "cbc",
+ "cipher",
+ "des",
+ "getrandom",
+ "hmac",
+ "lazy_static",
+ "rc2",
+ "sha1",
+ "yasna",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
[[package]]
name = "parking_lot"
version = "0.11.2"
@@ -1515,12 +1954,51 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "pinky-swear"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac"
+dependencies = [
+ "doc-comment",
+ "flume",
+ "parking_lot 0.12.1",
+ "tracing",
+]
+
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys",
+]
+
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -1605,6 +2083,26 @@ dependencies = [
"getrandom",
]
+[[package]]
+name = "rc2"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "reactor-trait"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58"
+dependencies = [
+ "async-trait",
+ "futures-core",
+ "futures-io",
+]
+
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -1790,6 +2288,20 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys",
+]
+
[[package]]
name = "rustix"
version = "0.38.19"
@@ -1799,7 +2311,7 @@ dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.4.10",
"windows-sys",
]
@@ -1815,6 +2327,42 @@ dependencies = [
"webpki",
]
+[[package]]
+name = "rustls"
+version = "0.21.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
+dependencies = [
+ "log",
+ "ring 0.17.4",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-connector"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "060bcc1795b840d0e56d78f3293be5f652aa1611d249b0e63ffe19f4a8c9ae23"
+dependencies = [
+ "log",
+ "rustls 0.21.8",
+ "rustls-native-certs",
+ "rustls-webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
[[package]]
name = "rustls-pemfile"
version = "1.0.3"
@@ -1824,6 +2372,16 @@ dependencies = [
"base64 0.21.4",
]
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring 0.17.4",
+ "untrusted 0.9.0",
+]
+
[[package]]
name = "ryu"
version = "1.0.15"
@@ -1933,7 +2491,7 @@ version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0adc7a19d45e581abc6d169c865a0b14b84bb43a9e966d1cca4d733e70f7f35a"
dependencies = [
- "indexmap",
+ "indexmap 1.9.3",
"itertools 0.10.5",
"num-traits",
"once_cell",
@@ -1971,6 +2529,19 @@ dependencies = [
"regex",
]
+[[package]]
+name = "serde_yaml"
+version = "0.9.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
+dependencies = [
+ "indexmap 2.1.0",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -2057,6 +2628,9 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
[[package]]
name = "sqlformat"
@@ -2106,7 +2680,7 @@ dependencies = [
"hex",
"hkdf",
"hmac",
- "indexmap",
+ "indexmap 1.9.3",
"itoa",
"libc",
"log",
@@ -2116,7 +2690,7 @@ dependencies = [
"paste",
"percent-encoding",
"rand",
- "rustls",
+ "rustls 0.20.9",
"rustls-pemfile",
"serde",
"serde_json",
@@ -2177,9 +2751,14 @@ dependencies = [
"actix-web-httpauth",
"chrono",
"config",
+ "derive_builder",
"futures",
+ "futures-lite",
"futures-util",
+ "glob",
"hmac",
+ "indexmap 2.1.0",
+ "lapin",
"rand",
"regex",
"reqwest",
@@ -2187,6 +2766,7 @@ dependencies = [
"serde_derive",
"serde_json",
"serde_valid",
+ "serde_yaml",
"sha2",
"sqlx",
"thiserror",
@@ -2266,6 +2846,18 @@ dependencies = [
"libc",
]
+[[package]]
+name = "tcp-stream"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5"
+dependencies = [
+ "cfg-if",
+ "p12",
+ "rustls-connector",
+ "rustls-pemfile",
+]
+
[[package]]
name = "tempfile"
version = "3.8.0"
@@ -2273,9 +2865,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
- "fastrand",
+ "fastrand 2.0.1",
"redox_syscall 0.3.5",
- "rustix",
+ "rustix 0.38.19",
"windows-sys",
]
@@ -2410,7 +3002,7 @@ version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
- "rustls",
+ "rustls 0.20.9",
"tokio",
"webpki",
]
@@ -2598,6 +3190,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+
[[package]]
name = "untrusted"
version = "0.7.1"
@@ -2649,6 +3247,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+[[package]]
+name = "waker-fn"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
+
[[package]]
name = "want"
version = "0.3.1"
@@ -2891,6 +3495,12 @@ dependencies = [
"linked-hash-map",
]
+[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+
[[package]]
name = "zstd"
version = "0.12.4"
diff --git a/Cargo.toml b/Cargo.toml
index 9153031..0301748 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,6 +40,13 @@ actix-http = "3.4.0"
hmac = "0.12.1"
sha2 = "0.10.8"
+# dctypes
+derive_builder = "0.12.0"
+indexmap = { version = "2.0.0", features = ["serde"], optional = true }
+serde_yaml = "0.9"
+lapin = { version = "2.3.1", features = ["serde_json"] }
+futures-lite = "1.13.0"
+
[dependencies.sqlx]
version = "0.6.3"
features = [
@@ -51,3 +58,10 @@ features = [
"json",
"offline"
]
+
+[features]
+default = ["indexmap"]
+indexmap = ["dep:indexmap"]
+
+[dev-dependencies]
+glob = "0.3"
diff --git a/configuration.yaml b/configuration.yaml
index 5099d3d..030dd49 100644
--- a/configuration.yaml
+++ b/configuration.yaml
@@ -9,3 +9,9 @@ database:
username: postgres
password: postgres
database_name: stacker
+
+amqp:
+ host: 127.0.0.1
+ port: 5672
+ username: guest
+ password: guest
diff --git a/docker-compose.yml b/docker-compose.yml
index 2d3b934..26311c4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,7 +13,7 @@ networks:
services:
stacker:
- image: trydirect/stacker:0.0.3
+ image: trydirect/stacker:0.0.4
build: .
container_name: stacker
restart: always
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index 1ba68f2..bdfdd89 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -7,7 +7,7 @@ volumes:
services:
stacker:
- image: trydirect/stacker:0.0.3
+ image: trydirect/stacker:0.0.4
build: .
container_name: stacker
restart: always
diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql
index e908e97..b20b2cd 100644
--- a/migrations/20230905145525_creating_stack_tables.up.sql
+++ b/migrations/20230905145525_creating_stack_tables.up.sql
@@ -1,12 +1,13 @@
--- Add up migration script here
--- Add migration script here
CREATE TABLE user_stack (
- id serial,
+ id serial4 NOT NULL,
stack_id uuid NOT NULL,
user_id VARCHAR(50) NOT NULL,
name TEXT NOT NULL UNIQUE,
body JSON NOT NULL,
created_at timestamptz NOT NULL,
- updated_at timestamptz NOT NULL
-)
+ updated_at timestamptz NOT NULL,
+ CONSTRAINT user_stack_pkey PRIMARY KEY (id)
+);
+CREATE INDEX idx_stack_id ON user_stack(stack_id);
+CREATE INDEX idx_stack_user_id ON user_stack(user_id);
\ No newline at end of file
diff --git a/src/configuration.rs b/src/configuration.rs
index a3beeaf..90d22c9 100644
--- a/src/configuration.rs
+++ b/src/configuration.rs
@@ -7,6 +7,7 @@ pub struct Settings {
pub app_host: String,
pub auth_url: String,
pub max_clients_number: i64,
+ pub amqp: AmqpSettings
}
#[derive(Debug, serde::Deserialize)]
@@ -18,6 +19,13 @@ pub struct DatabaseSettings {
pub database_name: String,
}
+#[derive(Debug, serde::Deserialize)]
+pub struct AmqpSettings {
+ pub username: String,
+ pub password: String,
+ pub host: String,
+ pub port: u16,
+}
impl DatabaseSettings {
// Connection string: postgresql://:@:/
pub fn connection_string(&self) -> String {
@@ -35,6 +43,15 @@ impl DatabaseSettings {
}
}
+impl AmqpSettings {
+ pub fn connection_string(&self) -> String {
+ format!(
+ "amqp://{}:{}@{}:{}/%2f",
+ self.username, self.password, self.host, self.port,
+ )
+ }
+}
+
pub fn get_configuration() -> Result {
// Initialize our configuration reader
let mut settings = config::Config::default();
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 93a1be8..78a0847 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -1,10 +1,99 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_valid::Validate;
+use std::fmt;
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Role {
+ pub role: Option>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Requirements {
+ #[validate(minimum=0.1)]
+ pub cpu: Option,
+ #[validate(min_length=1)]
+ #[validate(max_length=10)]
+ #[validate(pattern = r"^\d+G$")]
+ #[serde(rename = "disk_size")]
+ pub disk_size: Option,
+ #[serde(rename = "ram_size")]
+ #[validate(min_length=1)]
+ #[validate(max_length=10)]
+ #[validate(pattern = r"^\d+G$")]
+ pub ram_size: Option,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Ports {
+ #[serde(rename(deserialize = "sharedPorts"))]
+ #[serde(rename(serialize = "shared_ports"))]
+ #[serde(alias = "shared_ports")]
+ pub shared_ports: Option>,
+ pub ports: Option>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct DockerImage {
+ pub dockerhub_user: Option,
+ pub dockerhub_name: Option,
+ pub dockerhub_image: Option,
+}
+
+impl fmt::Display for DockerImage
+{
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let tag = "latest";
+
+ let dim = self.dockerhub_image.clone()
+ .unwrap_or("".to_string());
+ write!(f, "{}/{}:{}", self.dockerhub_user.clone()
+ .unwrap_or("trydirect".to_string()).clone(),
+ self.dockerhub_name.clone().unwrap_or(dim), tag
+ )
+ }
+}
+
+impl AsRef for App {
+ fn as_ref(&self) -> &DockerImage {
+ &self.docker_image
+ }
+}
+
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
pub struct StackForm {
+ #[serde(rename= "commonDomain")]
+ pub common_domain: Option,
+ pub domain_list: Option,
+ pub stack_code: Option,
+ pub region: String,
+ pub zone: Option,
+ pub server: String,
+ pub os: String,
+ pub ssl: String,
+ pub vars: Option>,
+ pub integrated_features: Option>,
+ pub extended_features: Option>,
+ pub subscriptions: Option>,
+ pub form_app: Option>,
+ pub disk_type: Option,
+ pub save_token: bool,
+ pub cloud_token: String,
+ pub provider: String,
+ pub selected_plan: String,
+ pub custom: Custom,
+}
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[serde(rename_all = "snake_case")]
+pub struct StackPayload {
+ pub(crate) id: Option,
+ pub(crate) user_token: Option,
+ pub(crate) user_email: Option,
+ #[serde(rename= "commonDomain")]
pub common_domain: String,
pub domain_list: Option,
pub region: String,
@@ -18,12 +107,13 @@ pub struct StackForm {
#[serde(rename = "extended_features")]
pub extended_features: Option>,
pub subscriptions: Option>,
+ pub form_app: Option>,
+ pub disk_type: Option,
#[serde(rename = "save_token")]
pub save_token: bool,
#[serde(rename = "cloud_token")]
pub cloud_token: String,
pub provider: String,
- #[serde(rename = "stack_code")]
pub stack_code: String,
#[serde(rename = "selected_plan")]
pub selected_plan: String,
@@ -52,13 +142,11 @@ pub struct Custom {
pub feature: Option>,
pub service: Option>,
#[serde(rename = "servers_count")]
- pub servers_count: i64,
- #[serde(rename = "custom_stack_name")]
- pub custom_stack_name: String,
+ pub servers_count: u32,
#[serde(rename = "custom_stack_code")]
pub custom_stack_code: String,
- #[serde(rename = "custom_stack_git_url")]
- pub custom_stack_git_url: Option,
+ #[serde(rename = "project_git_url")]
+ pub project_git_url: Option,
#[serde(rename = "custom_stack_category")]
pub custom_stack_category: Option>,
#[serde(rename = "custom_stack_short_description")]
@@ -73,44 +161,8 @@ pub struct Custom {
pub project_description: Option,
}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
-pub struct Web {
- pub name: String,
- pub code: String,
- pub domain: Option,
- pub shared_ports: Option>,
- pub versions: Option>,
- pub custom: bool,
- #[serde(rename = "type")]
- pub type_field: String,
- pub main: bool,
- #[serde(rename = "_id")]
- pub id: String,
- #[serde(rename = "dockerhub_user")]
- pub dockerhub_user: String,
- #[serde(rename = "dockerhub_name")]
- pub dockerhub_name: String,
- pub url_app: Option,
- pub url_git: Option,
- #[validate(min_length=1)]
- #[validate(max_length=10)]
- #[validate(pattern = r"^\d+G$")]
- #[serde(rename = "disk_size")]
- pub disk_size: String,
- #[serde(rename = "ram_size")]
- #[validate(min_length=1)]
- #[validate(max_length=10)]
- #[validate(pattern = r"^\d+G$")]
- pub ram_size: String,
- #[validate(minimum=0.1)]
- pub cpu: f64,
-}
-
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
-pub struct Feature {
+pub struct App {
#[serde(rename = "_etag")]
pub etag: Option,
#[serde(rename = "_id")]
@@ -121,57 +173,60 @@ pub struct Feature {
pub updated: Option,
pub name: String,
pub code: String,
- pub role: Vec,
#[serde(rename = "type")]
pub type_field: String,
+ #[serde(flatten)]
+ pub role: Role,
pub default: Option,
- pub popularity: Option,
- pub descr: Option,
+ #[serde(flatten)]
pub ports: Option,
+ pub versions: Option>,
+ #[serde(flatten)]
+ pub docker_image: DockerImage,
+ #[serde(flatten)]
+ pub requirements: Requirements,
+ pub popularity: Option,
pub commercial: Option,
pub subscription: Option,
pub autodeploy: Option,
pub suggested: Option,
pub dependency: Option,
- #[serde(rename = "avoid_render")]
pub avoid_render: Option,
pub price: Option,
pub icon: Option,
- #[serde(rename = "category_id")]
+ pub domain: Option,
+ pub main: bool,
pub category_id: Option,
- #[serde(rename = "parent_app_id")]
pub parent_app_id: Option,
- #[serde(rename = "full_description")]
+ pub descr: Option,
pub full_description: Option,
pub description: Option,
- #[serde(rename = "plan_type")]
pub plan_type: Option,
- #[serde(rename = "ansible_var")]
pub ansible_var: Option,
- #[serde(rename = "repo_dir")]
pub repo_dir: Option,
- #[validate(min_length=1)]
- pub cpu: String,
- #[validate(min_length=1)]
- #[serde(rename = "ram_size")]
- pub ram_size: String,
- #[validate(min_length=1)]
- #[serde(rename = "disk_size")]
- pub disk_size: String,
- #[serde(rename = "dockerhub_image")]
- pub dockerhub_image: Option,
- pub versions: Option>,
- pub domain: Option,
- pub shared_ports: Option>,
- pub main: bool,
+ pub url_app: Option,
+ pub url_git: Option,
}
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Ports {
- pub public: Vec,
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Web {
+ #[serde(flatten)]
+ pub app: App,
+ pub custom: Option,
}
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Feature {
+ #[serde(flatten)]
+ pub app: App,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[serde(rename_all = "camelCase")]
+pub struct Service {
+ #[serde(flatten)]
+ pub(crate) app: App,
+}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -199,13 +254,13 @@ pub struct Version {
#[serde(rename = "_etag")]
pub etag: Option,
#[serde(rename = "_id")]
- pub id: i64,
+ pub id: u32,
#[serde(rename = "_created")]
pub created: Option,
#[serde(rename = "_updated")]
pub updated: Option,
#[serde(rename = "app_id")]
- pub app_id: i64,
+ pub app_id: u32,
pub name: String,
pub version: String,
#[serde(rename = "update_status")]
@@ -213,61 +268,4 @@ pub struct Version {
pub tag: String,
}
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
-pub struct Service {
- #[serde(rename = "_etag")]
- pub etag: Option,
- #[serde(rename = "_id")]
- pub id: i64,
- #[serde(rename = "_created")]
- pub created: Option,
- #[serde(rename = "_updated")]
- pub updated: Option,
- pub name: String,
- pub code: String,
- pub role: Option>,
- #[serde(rename = "type")]
- pub type_field: String,
- pub default: Option,
- pub popularity: Option,
- pub descr: Option,
- pub ports: Option,
- pub commercial: Option,
- pub subscription: Option,
- pub autodeploy: Option,
- pub suggested: Option,
- pub dependency: Option,
- #[serde(rename = "avoid_render")]
- pub avoid_render: Option,
- pub price: Option,
- pub icon: Option,
- #[serde(rename = "category_id")]
- pub category_id: Option,
- #[serde(rename = "parent_app_id")]
- pub parent_app_id: Option,
- #[serde(rename = "full_description")]
- pub full_description: Option,
- pub description: Option,
- #[serde(rename = "plan_type")]
- pub plan_type: Option,
- #[serde(rename = "ansible_var")]
- pub ansible_var: Option,
- #[serde(rename = "repo_dir")]
- pub repo_dir: Option,
- #[validate(min_length=1)]
- pub cpu: String,
- #[serde(rename = "ram_size")]
- #[validate(min_length=1)]
- pub ram_size: String,
- #[serde(rename = "disk_size")]
- #[validate(min_length=1)]
- pub disk_size: String,
- #[serde(rename = "dockerhub_image")]
- pub dockerhub_image: Option,
- pub versions: Option>,
- pub domain: String,
- pub shared_ports: Option>,
- pub main: bool,
-}
diff --git a/src/forms/user.rs b/src/forms/user.rs
index 2da0dcf..8ff0b4c 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -16,45 +16,45 @@ pub struct User {
#[serde(rename = "_id")]
pub id: String,
#[serde(rename = "first_name")]
- pub first_name: String,
+ pub first_name: Option,
#[serde(rename = "last_name")]
- pub last_name: String,
- pub created: String,
- pub updated: String,
+ pub last_name: Option,
+ pub created: Option,
+ pub updated: Option,
pub email: String,
#[serde(rename = "email_confirmed")]
pub email_confirmed: bool,
pub social: bool,
- pub website: String,
+ pub website: Option,
pub currency: Value,
- pub phone: String,
+ pub phone: Option,
#[serde(rename = "password_change_required")]
pub password_change_required: Value,
- pub photo: String,
- pub country: String,
+ pub photo: Option,
+ pub country: Option,
#[serde(rename = "billing_first_name")]
pub billing_first_name: Value,
#[serde(rename = "billing_last_name")]
pub billing_last_name: Value,
#[serde(rename = "billing_postcode")]
- pub billing_postcode: String,
+ pub billing_postcode: Option,
#[serde(rename = "billing_address_1")]
- pub billing_address_1: String,
+ pub billing_address_1: Option,
#[serde(rename = "billing_address_2")]
- pub billing_address_2: String,
+ pub billing_address_2: Option,
#[serde(rename = "billing_city")]
- pub billing_city: String,
+ pub billing_city: Option,
#[serde(rename = "billing_country_code")]
- pub billing_country_code: String,
+ pub billing_country_code: Option,
#[serde(rename = "billing_country_area")]
- pub billing_country_area: String,
- pub tokens: Vec,
- pub subscriptions: Vec,
- pub plan: Plan,
+ pub billing_country_area: Option,
+ pub tokens: Option>,
+ pub subscriptions: Option>,
+ pub plan: Option,
#[serde(rename = "deployments_left")]
pub deployments_left: Value,
#[serde(rename = "suspension_hints")]
- pub suspension_hints: SuspensionHints,
+ pub suspension_hints: Option,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -72,9 +72,9 @@ pub struct Subscription {
#[serde(rename = "user_id")]
pub user_id: i64,
#[serde(rename = "date_created")]
- pub date_created: String,
+ pub date_created: Option,
#[serde(rename = "date_updated")]
- pub date_updated: String,
+ pub date_updated: Option,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -92,21 +92,21 @@ pub struct Plan {
pub billing_email: String,
#[serde(rename = "date_of_purchase")]
pub date_of_purchase: String,
- pub currency: String,
- pub price: String,
- pub period: String,
+ pub currency: Option,
+ pub price: Option,
+ pub period: Option,
#[serde(rename = "date_start")]
pub date_start: String,
pub active: bool,
#[serde(rename = "billing_id")]
- pub billing_id: String,
+ pub billing_id: Option,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportedStacks {
- pub monthly: i64,
- pub annually: i64,
+ pub monthly: Option,
+ pub annually: Option,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -132,8 +132,8 @@ impl TryInto for UserForm {
// )?;
Ok(UserModel {
id: self.user.id,
- first_name: self.user.first_name,
- last_name: self.user.last_name,
+ first_name: self.user.first_name.unwrap_or("Noname".to_string()),
+ last_name: self.user.last_name.unwrap_or("Noname".to_string()),
email: self.user.email,
email_confirmed: self.user.email_confirmed,
})
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 80a21b3..87e7327 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,15 +1,15 @@
-use actix_web::error::{ErrorBadRequest, ErrorInternalServerError};
+use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorNotFound, ErrorInternalServerError};
+use serde_derive::Serialize;
use actix_web::web::Json;
use actix_web::Error;
use actix_web::Result;
-use serde_derive::Serialize;
#[derive(Serialize)]
pub(crate) struct JsonResponse {
- message: String,
- id: Option,
- item: Option,
- list: Option>,
+ pub(crate) message: String,
+ pub(crate) id: Option,
+ pub(crate) item: Option,
+ pub(crate) list: Option>
}
#[derive(Serialize, Default)]
@@ -62,6 +62,45 @@ where
))
}
+ pub(crate) fn not_found(self, msg: String) -> Result>, Error> {
+
+ let json_response = JsonResponse {
+ message: msg,
+ id: self.id,
+ item: self.item,
+ list: self.list
+ };
+
+ Err(ErrorNotFound(
+ serde_json::to_string(&json_response).unwrap()))
+ }
+
+ pub(crate) fn internal_error(self, msg: String) -> Result>, Error> {
+
+ let json_response = JsonResponse {
+ message: msg,
+ id: self.id,
+ item: self.item,
+ list: self.list
+ };
+
+ Err(ErrorInternalServerError(
+ serde_json::to_string(&json_response).unwrap()))
+ }
+
+ pub(crate) fn conflict(self, msg: String) -> Result>, Error> {
+
+ let json_response = JsonResponse {
+ message: msg,
+ id: self.id,
+ item: self.item,
+ list: self.list
+ };
+
+ Err(ErrorConflict(
+ serde_json::to_string(&json_response).unwrap()))
+ }
+
pub(crate) fn err_internal_server_error>(
self,
msg: I,
@@ -81,4 +120,75 @@ where
pub fn build() -> JsonResponseBuilder {
JsonResponseBuilder::default()
}
+
+ pub(crate) fn new(message: String,
+ id: Option,
+ item:Option,
+ list: Option>) -> Self {
+ tracing::debug!("Executed..");
+ JsonResponse {
+ message,
+ id,
+ item,
+ list,
+ }
+ }
+
+ // pub(crate) fn ok(id: i32, message: &str) -> JsonResponse {
+ //
+ // let msg = if !message.trim().is_empty() {
+ // message.to_string()
+ // }
+ // else{
+ // String::from("Success")
+ // };
+ //
+ // JsonResponse {
+ // message: msg,
+ // id: Some(id),
+ // item: None,
+ // list: None,
+ // }
+ // }
+
+ // pub(crate) fn not_found() -> Self {
+ // JsonResponse {
+ // id: None,
+ // item: None,
+ // message: format!("Object not found"),
+ // list: None,
+ // }
+ // }
+ //
+ // pub(crate) fn internal_error(message: &str) -> Self {
+ //
+ // let msg = if !message.trim().is_empty() {
+ // message.to_string()
+ // }
+ // else{
+ // String::from("Internal error")
+ // };
+ // JsonResponse {
+ // id: None,
+ // item: None,
+ // message: msg,
+ // list: None,
+ // }
+ // }
+ //
+ // pub(crate) fn not_valid(message: &str) -> Self {
+ //
+ // let msg = if !message.trim().is_empty() {
+ // message.to_string()
+ // }
+ // else{
+ // String::from("Validation error")
+ // };
+ // JsonResponse {
+ // id: None,
+ // item: None,
+ // message: msg,
+ // list: None,
+ // }
+ // }
}
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 203eb0d..e5439f7 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,4 +1,5 @@
pub mod client;
pub(crate) mod json;
+pub(crate) mod stack;
pub use json::*;
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
new file mode 100644
index 0000000..c319d8a
--- /dev/null
+++ b/src/helpers/stack/builder.rs
@@ -0,0 +1,167 @@
+use crate::helpers::stack::dctypes::{
+ Compose,
+ Port,
+ Ports,
+ PublishedPort,
+ Service,
+ Services
+};
+use serde_yaml;
+use crate::forms::{StackForm, stack};
+use crate::models::stack::Stack;
+#[derive(Clone, Debug)]
+struct Config {}
+
+impl Default for Config {
+ fn default() -> Self {
+ Config {}
+ }
+}
+
+/// A builder for constructing docker compose.
+#[derive(Clone, Debug)]
+pub struct DcBuilder {
+ config: Config,
+ pub(crate) stack: Stack
+}
+
+impl TryInto> for stack::Ports {
+ type Error = String;
+ fn try_into(self) -> Result, Self::Error> {
+ convert_shared_ports(self.shared_ports.clone().unwrap())
+ }
+}
+
+
+fn convert_shared_ports(ports: Vec) -> Result, String> {
+ let mut _ports: Vec = vec![];
+ for p in ports {
+ let port = p.parse::().map_err(|e| e.to_string())?;
+ _ports.push(Port {
+ target: port,
+ host_ip: None,
+ published: Some(PublishedPort::Single(port)),
+ protocol: None,
+ mode: None,
+ });
+ }
+ Ok(_ports)
+}
+
+impl DcBuilder {
+
+ pub fn new(stack: Stack) -> Self {
+ DcBuilder {
+ config: Config::default(),
+ stack: stack,
+ }
+ }
+
+
+ pub fn build(&self) -> Option {
+
+ tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
+ let _stack = serde_json::from_value::(self.stack.body.clone());
+ let mut services = indexmap::IndexMap::new();
+ match _stack {
+ Ok(apps) => {
+ println!("stack item {:?}", apps.custom.web);
+
+ for app_type in apps.custom.web {
+ let code = app_type.app.code.clone().to_owned();
+ let mut service = Service {
+ image: Some(app_type.app.docker_image.to_string()),
+ ..Default::default()
+ };
+
+ if let Some(ports) = &app_type.app.ports {
+ if !ports.shared_ports.clone()?.is_empty() {
+ service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
+ }
+ }
+
+ service.restart = Some("always".to_owned());
+ services.insert(
+ code,
+ Some(service),
+ );
+ }
+
+ if let Some(srvs) = apps.custom.service {
+
+ if !srvs.is_empty() {
+
+ for app_type in srvs {
+ let code = app_type.app.code.to_owned();
+ let mut service = Service {
+ image: Some(app_type.app.docker_image.to_string()),
+ ..Default::default()
+ };
+
+ if let Some(ports) = &app_type.app.ports {
+ if !ports.shared_ports.clone()?.is_empty() {
+ service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
+ }
+ }
+ service.restart = Some("always".to_owned());
+ services.insert(
+ code,
+ Some(service),
+ );
+ }
+ }
+ }
+ if let Some(features) = apps.custom.feature {
+
+ if !features.is_empty() {
+
+ for app_type in features {
+ let code = app_type.app.code.to_owned();
+ let mut service = Service {
+ // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
+ image: Some(app_type.app.docker_image.to_string()),
+ ..Default::default()
+ };
+
+ if let Some(ports) = &app_type.app.ports {
+ if !ports.shared_ports.clone()?.is_empty() {
+ service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
+ }
+ }
+ service.restart = Some("always".to_owned());
+ services.insert(
+ code,
+ Some(service),
+ );
+ }
+ }
+ }
+ }
+ Err(e) => {
+ tracing::debug!("Unpack stack form {:?}", e);
+ ()
+ }
+ }
+
+ let compose_content = Compose {
+ version: Some("3.8".to_string()),
+ services: {
+ Services(services)
+ },
+ ..Default::default()
+ };
+
+ let fname= format!("./files/{}.yml", self.stack.stack_id);
+ tracing::debug!("Save docker compose to file {:?}", fname);
+ let target_file = std::path::Path::new(fname.as_str());
+ // serialize to string
+ let serialized = match serde_yaml::to_string(&compose_content) {
+ Ok(s) => s,
+ Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
+ };
+ // serialize to file
+ std::fs::write(target_file, serialized.clone()).unwrap();
+
+ Some(serialized)
+ }
+}
diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes.rs
new file mode 100644
index 0000000..931deab
--- /dev/null
+++ b/src/helpers/stack/dctypes.rs
@@ -0,0 +1,842 @@
+use derive_builder::*;
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use serde::{Deserialize, Serialize};
+use serde_yaml::Value;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::fmt;
+use std::str::FromStr;
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum ComposeFile {
+ V2Plus(Compose),
+ #[cfg(feature = "indexmap")]
+ V1(IndexMap),
+ #[cfg(not(feature = "indexmap"))]
+ V1(HashMap),
+ Single(SingleService),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
+pub struct SingleService {
+ pub service: Service,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
+pub struct Compose {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub version: Option,
+ #[serde(default, skip_serializing_if = "Services::is_empty")]
+ pub services: Services,
+ #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
+ pub volumes: TopLevelVolumes,
+ #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
+ pub networks: ComposeNetworks,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub service: Option,
+ #[cfg(feature = "indexmap")]
+ #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
+ pub extensions: IndexMap,
+ #[cfg(not(feature = "indexmap"))]
+ #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
+ pub extensions: HashMap,
+}
+
+impl Compose {
+ pub fn new() -> Self {
+ Default::default()
+ }
+}
+
+#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
+#[builder(setter(into), default)]
+pub struct Service {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub hostname: Option,
+ #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+ pub privileged: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub healthcheck: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub deploy: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub image: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub container_name: Option,
+ #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
+ pub build_: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pid: Option,
+ #[serde(default, skip_serializing_if = "Ports::is_empty")]
+ pub ports: Ports,
+ #[serde(default, skip_serializing_if = "Environment::is_empty")]
+ pub environment: Environment,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub network_mode: Option,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub devices: Vec,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub restart: Option,
+ #[serde(default, skip_serializing_if = "Labels::is_empty")]
+ pub labels: Labels,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tmpfs: Option,
+ #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
+ pub ulimits: Ulimits,
+ #[serde(default, skip_serializing_if = "Volumes::is_empty")]
+ pub volumes: Volumes,
+ #[serde(default, skip_serializing_if = "Networks::is_empty")]
+ pub networks: Networks,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub cap_add: Vec,
+ #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")]
+ pub depends_on: DependsOnOptions,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub command: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub entrypoint: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub env_file: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub stop_grace_period: Option,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub profiles: Vec,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub links: Vec,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub dns: Vec,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub ipc: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub net: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub stop_signal: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub user: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub working_dir: Option,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub expose: Vec,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub volumes_from: Vec,
+ #[cfg(feature = "indexmap")]
+ #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
+ pub extends: IndexMap,
+ #[cfg(not(feature = "indexmap"))]
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub extends: HashMap,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub logging: Option,
+ #[serde(default, skip_serializing_if = "is_zero")]
+ pub scale: i64,
+ #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+ pub init: bool,
+ #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+ pub stdin_open: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub shm_size: Option,
+ #[cfg(feature = "indexmap")]
+ #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
+ pub extensions: IndexMap,
+ #[cfg(not(feature = "indexmap"))]
+ #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
+ pub extensions: HashMap,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub extra_hosts: Vec,
+ #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+ pub tty: bool,
+ #[serde(default, skip_serializing_if = "SysCtls::is_empty")]
+ pub sysctls: SysCtls,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub security_opt: Vec,
+}
+
+impl Service {
+ pub fn image(&self) -> &str {
+ self.image.as_deref().unwrap_or_default()
+ }
+
+ pub fn network_mode(&self) -> &str {
+ self.network_mode.as_deref().unwrap_or_default()
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(untagged)]
+pub enum EnvFile {
+ Simple(String),
+ List(Vec),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum DependsOnOptions {
+ Simple(Vec),
+ #[cfg(feature = "indexmap")]
+ Conditional(IndexMap),
+ #[cfg(not(feature = "indexmap"))]
+ Conditional(HashMap),
+}
+
+impl Default for DependsOnOptions {
+ fn default() -> Self {
+ Self::Simple(Vec::new())
+ }
+}
+
+impl DependsOnOptions {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::Simple(v) => v.is_empty(),
+ Self::Conditional(m) => m.is_empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+pub struct DependsCondition {
+ pub condition: String,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct LoggingParameters {
+ pub driver: String,
+ #[cfg(feature = "indexmap")]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub options: Option>,
+ #[cfg(not(feature = "indexmap"))]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub options: Option>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum Ports {
+ Short(Vec),
+ Long(Vec),
+}
+
+impl Default for Ports {
+ fn default() -> Self {
+ Self::Short(Vec::default())
+ }
+}
+
+impl Ports {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::Short(v) => v.is_empty(),
+ Self::Long(v) => v.is_empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Port {
+ pub target: u16,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub host_ip: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub published: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub protocol: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub mode: Option,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum PublishedPort {
+ Single(u16),
+ Range(String),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum Environment {
+ List(Vec),
+ #[cfg(feature = "indexmap")]
+ KvPair(IndexMap>),
+ #[cfg(not(feature = "indexmap"))]
+ KvPair(HashMap>),
+}
+
+impl Default for Environment {
+ fn default() -> Self {
+ Self::List(Vec::new())
+ }
+}
+
+impl Environment {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::List(v) => v.is_empty(),
+ Self::KvPair(m) => m.is_empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
+#[serde(try_from = "String")]
+pub struct Extension(String);
+
+impl FromStr for Extension {
+ type Err = ExtensionParseError;
+
+ fn from_str(s: &str) -> Result {
+ let owned = s.to_owned();
+ Extension::try_from(owned)
+ }
+}
+
+impl TryFrom for Extension {
+ type Error = ExtensionParseError;
+
+ fn try_from(s: String) -> Result {
+ if s.starts_with("x-") {
+ Ok(Self(s))
+ } else {
+ Err(ExtensionParseError(s))
+ }
+ }
+}
+
+/// The result of a failed TryFrom conversion for [`Extension`]
+///
+/// Contains the string that was being converted
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct ExtensionParseError(pub String);
+
+impl fmt::Display for ExtensionParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
+ }
+}
+
+impl std::error::Error for ExtensionParseError {}
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Services(pub IndexMap>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Services(pub HashMap>);
+
+impl Services {
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Labels {
+ List(Vec),
+ #[cfg(feature = "indexmap")]
+ Map(IndexMap),
+ #[cfg(not(feature = "indexmap"))]
+ Map(HashMap),
+}
+
+impl Default for Labels {
+ fn default() -> Self {
+ Self::List(Vec::new())
+ }
+}
+
+impl Labels {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::List(v) => v.is_empty(),
+ Self::Map(m) => m.is_empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Tmpfs {
+ Simple(String),
+ List(Vec),
+}
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct Ulimits(pub IndexMap);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct Ulimits(pub HashMap);
+
+impl Ulimits {
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Ulimit {
+ Single(i64),
+ SoftHard { soft: i64, hard: i64 },
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Networks {
+ Simple(Vec),
+ Advanced(AdvancedNetworks),
+}
+
+impl Default for Networks {
+ fn default() -> Self {
+ Self::Simple(Vec::new())
+ }
+}
+
+impl Networks {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::Simple(n) => n.is_empty(),
+ Self::Advanced(n) => n.0.is_empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum BuildStep {
+ Simple(String),
+ Advanced(AdvancedBuildStep),
+}
+
+#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
+#[serde(deny_unknown_fields)]
+#[builder(setter(into), default)]
+pub struct AdvancedBuildStep {
+ pub context: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub dockerfile: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub args: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub shm_size: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub target: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub network: Option,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub cache_from: Vec,
+ #[serde(default, skip_serializing_if = "Labels::is_empty")]
+ pub labels: Labels,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum BuildArgs {
+ Simple(String),
+ List(Vec),
+ #[cfg(feature = "indexmap")]
+ KvPair(IndexMap),
+ #[cfg(not(feature = "indexmap"))]
+ KvPair(HashMap),
+}
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct AdvancedNetworks(pub IndexMap>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct AdvancedNetworks(pub HashMap>);
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct AdvancedNetworkSettings {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub ipv4_address: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub ipv6_address: Option,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub aliases: Vec,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum SysCtls {
+ List(Vec),
+ #[cfg(feature = "indexmap")]
+ Map(IndexMap>),
+ #[cfg(not(feature = "indexmap"))]
+ Map(HashMap>),
+}
+
+impl Default for SysCtls {
+ fn default() -> Self {
+ Self::List(Vec::new())
+ }
+}
+
+impl SysCtls {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::List(v) => v.is_empty(),
+ Self::Map(m) => m.is_empty(),
+ }
+ }
+}
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TopLevelVolumes(pub IndexMap>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TopLevelVolumes(pub HashMap>);
+
+impl TopLevelVolumes {
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeVolume {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub driver: Option,
+ #[cfg(feature = "indexmap")]
+ #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
+ pub driver_opts: IndexMap>,
+ #[cfg(not(feature = "indexmap"))]
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub driver_opts: HashMap>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub external: Option,
+ #[serde(default, skip_serializing_if = "Labels::is_empty")]
+ pub labels: Labels,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum ExternalVolume {
+ Bool(bool),
+ Name { name: String },
+}
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeNetworks(pub IndexMap>);
+
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeNetworks(pub HashMap