additional error handling; use path buffers; improve logic
This commit is contained in:
136
Cargo.lock
generated
136
Cargo.lock
generated
@@ -76,6 +76,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -269,25 +275,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "errno"
|
|
||||||
version = "0.3.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastrand"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
@@ -307,19 +297,7 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"wasi 0.13.3+wasi-0.2.2",
|
|
||||||
"windows-targets",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -348,9 +326,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humansize"
|
name = "humansize"
|
||||||
@@ -402,9 +380,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.7.1"
|
version = "2.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -450,18 +428,6 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libyml"
|
|
||||||
version = "0.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3e281a65eeba3d4503a2839252f86374528f9ceafe6fed97c1d3b52e1fb625c1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.4.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.25"
|
||||||
@@ -641,7 +607,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.15",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -673,19 +639,6 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "0.38.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
@@ -739,23 +692,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yml"
|
|
||||||
version = "0.0.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"itoa",
|
|
||||||
"libyml",
|
|
||||||
"log",
|
|
||||||
"memchr",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.8"
|
||||||
@@ -783,14 +719,15 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
|||||||
name = "skyforge"
|
name = "skyforge"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yml",
|
|
||||||
"tera",
|
"tera",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
"yaml_serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -820,20 +757,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"fastrand",
|
|
||||||
"getrandom 0.3.1",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tera"
|
name = "tera"
|
||||||
version = "1.20.0"
|
version = "1.20.0"
|
||||||
@@ -964,6 +887,12 @@ version = "1.0.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -992,15 +921,6 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.13.3+wasi-0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen-rt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.100"
|
version = "0.2.100"
|
||||||
@@ -1151,12 +1071,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "yaml_serde"
|
||||||
version = "0.33.0"
|
version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
checksum = "4a7f5270edc6fab0529a772a772b3e505dfd883a8de5cc5b464e35fabe586411"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
19
Cargo.toml
19
Cargo.toml
@@ -1,14 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "skyforge"
|
name = "skyforge"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.11" }
|
anyhow = "1"
|
||||||
regex = "1.10.5"
|
clap = { version = "4.5" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
regex = "1"
|
||||||
serde_yml = "0.0.10"
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1.0.112"
|
serde_json = "1"
|
||||||
tera = "1.20.0"
|
yaml_serde = "0"
|
||||||
thiserror = "1.0"
|
tera = "1"
|
||||||
walkdir = "2.3"
|
thiserror = "1"
|
||||||
|
walkdir = "2"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
regional:
|
regional:
|
||||||
partition: us
|
partition: us
|
||||||
username: netadmin
|
username: netadmin
|
||||||
|
|||||||
5
demo/spec/xyz/ex-core-r/common.yaml
Normal file
5
demo/spec/xyz/ex-core-r/common.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
common:
|
||||||
|
partition: us
|
||||||
|
layer: ex-core-r
|
||||||
|
protocol: ospf
|
||||||
|
uplink: et-0/0/36
|
||||||
2
demo/spec/xyz/ex-core-r/xyz1.common.yaml
Normal file
2
demo/spec/xyz/ex-core-r/xyz1.common.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
zonal:
|
||||||
|
zone: 1
|
||||||
2
demo/spec/xyz/ex-core-r/xyz2.common.yaml
Normal file
2
demo/spec/xyz/ex-core-r/xyz2.common.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
zonal:
|
||||||
|
zone: 2
|
||||||
4
demo/spec/xyz/ex-edge-r/common.yaml
Normal file
4
demo/spec/xyz/ex-edge-r/common.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
common:
|
||||||
|
layer: ex-edge-r
|
||||||
|
protocol: bgp
|
||||||
|
uplink: et-0/0/0
|
||||||
2
demo/spec/xyz/ex-edge-r/xyz1.common.yaml
Normal file
2
demo/spec/xyz/ex-edge-r/xyz1.common.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
zonal:
|
||||||
|
zone: 1
|
||||||
2
demo/spec/xyz/ex-edge-r/xyz2.common.yaml
Normal file
2
demo/spec/xyz/ex-edge-r/xyz2.common.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
zonal:
|
||||||
|
zone: 2
|
||||||
8
demo/spec/xyz/ex-edge-r/xyz5-ex-edge-r101.yaml
Normal file
8
demo/spec/xyz/ex-edge-r/xyz5-ex-edge-r101.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
device:
|
||||||
|
zone: 5
|
||||||
|
hostname: xyz5-ex-edge-r101
|
||||||
|
uplinks:
|
||||||
|
- xe-0/0/1
|
||||||
|
- xe-0/0/2
|
||||||
|
- xe-0/0/3
|
||||||
|
- xe-0/0/4
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
common:
|
|
||||||
layer: ex-edge-r
|
|
||||||
protocol: bgp
|
|
||||||
uplink: et-0/0/0
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
zonal:
|
|
||||||
zone: 1
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
zonal:
|
|
||||||
zone: 2
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
common:
|
|
||||||
partition: eu
|
|
||||||
layer: ex-edge-r
|
|
||||||
protocol: ospf
|
|
||||||
uplink: et-0/0/36
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
zonal:
|
|
||||||
az: 1
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
zonal:
|
|
||||||
az: 2
|
|
||||||
1
demo/tmpl/ex-core-r
Symbolic link
1
demo/tmpl/ex-core-r
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
ex-edge-r/
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
chassis {
|
|
||||||
users {
|
|
||||||
{{ username }};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
demo/tmpl/ex-edge-r/interfaces.tera
Normal file
8
demo/tmpl/ex-edge-r/interfaces.tera
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
interfaces {
|
||||||
|
{%- set interfaces = uplinks | default(value=[uplink]) %}
|
||||||
|
{%- for interace in interfaces %}
|
||||||
|
{{ interace }} {
|
||||||
|
unit 0 family inet dhcp;
|
||||||
|
}
|
||||||
|
{%- endfor %}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
interfaces {
|
|
||||||
{{ uplink }} {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
demo/tmpl/ex-edge-r/protocols.tera
Normal file
3
demo/tmpl/ex-edge-r/protocols.tera
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
protocols {
|
||||||
|
{{ protocol }};
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
protocols {
|
|
||||||
{{ protocol }} {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
files:
|
files:
|
||||||
- system
|
- system
|
||||||
- chassis
|
|
||||||
- interfaces
|
- interfaces
|
||||||
- protocols
|
- protocols
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
system {
|
system {
|
||||||
hostname {{ hostname }};
|
hostname {{ hostname }};
|
||||||
location "{{ location }}";
|
location "{{ location }}";
|
||||||
|
zone {{ zone }};
|
||||||
|
users {
|
||||||
|
{{ username }};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
../common/ex/system.tmpl
|
|
||||||
38
src/cli.rs
38
src/cli.rs
@@ -41,29 +41,21 @@ impl fmt::Display for EnvVars {
|
|||||||
/// loads `command line arguments` and `environment variables` using custom `clap`
|
/// loads `command line arguments` and `environment variables` using custom `clap`
|
||||||
pub fn parse_args() -> Args {
|
pub fn parse_args() -> Args {
|
||||||
let matches: clap::ArgMatches = build().get_matches();
|
let matches: clap::ArgMatches = build().get_matches();
|
||||||
|
let raw_devices = matches.get_one::<String>("devices").unwrap();
|
||||||
let raw_devices = matches.get_one::<String>("devices").unwrap().to_string();
|
|
||||||
let devices: Regex =
|
|
||||||
Regex::new(&raw_devices).expect("Invalid regex pattern provided for `devices`");
|
|
||||||
|
|
||||||
let loglevel: LogLevel = match matches.get_flag("debug") {
|
|
||||||
true => LogLevel::Debug,
|
|
||||||
false => match matches.get_flag("verbose") {
|
|
||||||
true => LogLevel::Verbose,
|
|
||||||
false => LogLevel::Info,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let env: EnvVars = parse_env();
|
|
||||||
|
|
||||||
Args {
|
Args {
|
||||||
devices,
|
devices: Regex::new(&raw_devices).expect("Invalid regex pattern provided for `devices`"),
|
||||||
loglevel,
|
loglevel: if matches.get_flag("debug") {
|
||||||
env,
|
LogLevel::Debug
|
||||||
|
} else if matches.get_flag("verbose") {
|
||||||
|
LogLevel::Verbose
|
||||||
|
} else {
|
||||||
|
LogLevel::Info
|
||||||
|
},
|
||||||
|
env: parse_env(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// loads only environment variables
|
/// loads environment variables
|
||||||
pub fn parse_env() -> EnvVars {
|
pub fn parse_env() -> EnvVars {
|
||||||
EnvVars {
|
EnvVars {
|
||||||
spec_path: match std::env::var("SF_SPEC_PATH") {
|
spec_path: match std::env::var("SF_SPEC_PATH") {
|
||||||
@@ -87,10 +79,10 @@ pub fn parse_env() -> EnvVars {
|
|||||||
|
|
||||||
const ABOUT_MSG: &str = r#"Skyforge Config Generation Engine"#;
|
const ABOUT_MSG: &str = r#"Skyforge Config Generation Engine"#;
|
||||||
const ENV_MSG: &str = r#"Environment:
|
const ENV_MSG: &str = r#"Environment:
|
||||||
SF_SPEC_PATH Path to the directory containing templates. Defaults to "./spec".
|
SF_SPEC_PATH Directory containing templates
|
||||||
SF_TMPL_PATH Path to the directory containing specifications. Defaults to "./tmpl".
|
SF_TMPL_PATH Directory containing specifications
|
||||||
SF_OUT_PATH Path to the directory for command output. Defaults to "./out".
|
SF_OUT_PATH Directory for command output
|
||||||
SF_LOG_PATH Path to the directory for log output. Defaults to "./log".
|
SF_LOG_PATH Directory for log output
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// builds a custom command line argument parser
|
/// builds a custom command line argument parser
|
||||||
|
|||||||
56
src/main.rs
56
src/main.rs
@@ -4,57 +4,51 @@ mod specs;
|
|||||||
mod tmpls;
|
mod tmpls;
|
||||||
|
|
||||||
use log::LogLevel;
|
use log::LogLevel;
|
||||||
use serde_yml;
|
use std::fs::{self, File, OpenOptions, create_dir_all, write};
|
||||||
use std::fs::{self, create_dir_all, write, OpenOptions};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tmpls::RenderedConfig;
|
use tmpls::RenderedConfig;
|
||||||
|
use yaml_serde;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: cli::Args = cli::parse_args();
|
let args: cli::Args = cli::parse_args();
|
||||||
let dbg: LogLevel = args.loglevel;
|
let dbg: LogLevel = args.loglevel;
|
||||||
|
|
||||||
dbug!(dbg, "{}", &args);
|
dbug!(dbg, "{:#?}", &args);
|
||||||
|
|
||||||
let specifications: Vec<specs::Specification> =
|
let specifications: Vec<specs::Specification> =
|
||||||
specs::compile(&args.devices, &args.env.spec_path, dbg);
|
specs::compile(&args.devices, &args.env.spec_path, dbg);
|
||||||
|
|
||||||
for spec in specifications {
|
for spec in specifications {
|
||||||
let result = tmpls::process_templates(&spec, dbg).ok().unwrap();
|
let result = tmpls::process_templates(&spec, dbg).ok().unwrap();
|
||||||
output_rendered_configs(result, dbg)
|
output_rendered_configs(result, &args.env.out_path, dbg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output_rendered_configs(rendered_config: RenderedConfig, dbg: LogLevel) {
|
fn output_rendered_configs(rendered_config: RenderedConfig, outdir: &str, dbg: LogLevel) {
|
||||||
info!(dbg, "Writing Output");
|
info!(dbg, "Writing Output");
|
||||||
let path: String = format!("out/{}", rendered_config.hostname);
|
let out_path = Path::new(outdir).join(rendered_config.hostname);
|
||||||
if Path::new(&path).exists() {
|
if out_path.exists() {
|
||||||
fs::remove_dir_all(&path).ok();
|
fs::remove_dir_all(&out_path).ok();
|
||||||
}
|
}
|
||||||
create_dir_all(&path).ok();
|
create_dir_all(&out_path).ok();
|
||||||
let rendered_config_path = format!("{}/all.conf", path);
|
|
||||||
|
let merged_config_path = out_path.join("all.conf");
|
||||||
|
let mut all_file: File = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&merged_config_path)
|
||||||
|
.expect("unable to open");
|
||||||
for rendered_template in rendered_config.configs {
|
for rendered_template in rendered_config.configs {
|
||||||
let outpath = format!("{}/{}.tmpl", path, rendered_template.0);
|
let template_path = out_path.join(rendered_template.0 + ".tera");
|
||||||
verb!(dbg, " | {}", &outpath);
|
verb!(dbg, " | {}", &template_path.display());
|
||||||
write(&outpath, &rendered_template.1).ok();
|
write(&template_path, &rendered_template.1).ok();
|
||||||
append_to_file(&rendered_config_path, rendered_template.1).ok();
|
all_file.write_all(&rendered_template.1.as_bytes()).ok();
|
||||||
}
|
}
|
||||||
let outpath = format!("{}/compiled.spec", path);
|
|
||||||
let spec: String = match serde_yml::to_string(&rendered_config.spec) {
|
|
||||||
Ok(yaml) => yaml,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Failed to convert to YAML: {}", e);
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
verb!(dbg, " | {}", &outpath);
|
|
||||||
write(&outpath, spec).ok();
|
|
||||||
info!(dbg, " | {} ", &rendered_config_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_to_file(path: &str, content: String) -> std::io::Result<()> {
|
let spec: String = yaml_serde::to_string(&rendered_config.spec).unwrap();
|
||||||
let mut file = OpenOptions::new().create(true).append(true).open(path)?;
|
let compiled_spec_path = out_path.join("compiled-spec.yaml");
|
||||||
|
verb!(dbg, " | {}", &compiled_spec_path.display());
|
||||||
write!(file, "{}", content)?;
|
write(&compiled_spec_path, spec).ok();
|
||||||
Ok(())
|
info!(dbg, " | {} ", &merged_config_path.display());
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/specs.rs
76
src/specs.rs
@@ -1,11 +1,11 @@
|
|||||||
use crate::{dbug, info, verb, LogLevel};
|
use crate::{LogLevel, info, verb};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_json::{json, Map, Value};
|
use serde_json::{Map, Value, json};
|
||||||
use serde_yml;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{env, fmt, fs};
|
use std::{env, fmt, fs};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
use yaml_serde;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Specification {
|
pub struct Specification {
|
||||||
@@ -19,37 +19,25 @@ pub struct Specification {
|
|||||||
|
|
||||||
impl Specification {
|
impl Specification {
|
||||||
pub fn build(
|
pub fn build(
|
||||||
partitional: &str,
|
partitional: String,
|
||||||
regional: &str,
|
regional: String,
|
||||||
common: &str,
|
common: String,
|
||||||
zonal: &str,
|
zonal: String,
|
||||||
device: &str,
|
device: String,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
// Read all YAML files
|
let json_values: Vec<Value> = [&partitional, ®ional, &common, &zonal, &device]
|
||||||
let yaml_contents: Vec<String> = [partitional, regional, common, zonal, device]
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|path| fs::read_to_string(path).ok())
|
.filter_map(|path| fs::read_to_string(path).ok())
|
||||||
.collect();
|
.map(|content| yaml_serde::from_str::<Value>(&content))
|
||||||
|
|
||||||
// Convert each YAML to JSON Value and collect them
|
|
||||||
let json_values: Vec<Value> = yaml_contents
|
|
||||||
.iter()
|
|
||||||
.map(|content| serde_yml::from_str(content))
|
|
||||||
.collect::<Result<Vec<Value>, _>>()?;
|
.collect::<Result<Vec<Value>, _>>()?;
|
||||||
|
|
||||||
// Merge all objects under a single key
|
// Merge all objects under a single key
|
||||||
let mut merged_map = Map::new();
|
let merged_map: Map<String, Value> = json_values
|
||||||
for value in json_values {
|
.into_iter()
|
||||||
if let Some(obj) = value.as_object() {
|
.filter_map(|v| v.as_object().cloned())
|
||||||
for (_key, value) in obj {
|
.flat_map(|obj| obj.into_values().filter_map(|v| v.as_object().cloned()))
|
||||||
if let Some(inner_obj) = value.as_object() {
|
.flatten()
|
||||||
for (k, v) in inner_obj {
|
.collect();
|
||||||
merged_map.insert(k.clone(), v.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the final merged object
|
// Create the final merged object
|
||||||
let compiled = json!({"data": merged_map});
|
let compiled = json!({"data": merged_map});
|
||||||
@@ -75,27 +63,13 @@ impl Specification {
|
|||||||
.filter(|c| !c.is_numeric())
|
.filter(|c| !c.is_numeric())
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hostname(&self) -> String {
|
|
||||||
PathBuf::from(&self.device)
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Specification {
|
impl fmt::Display for Specification {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Specification:\n\
|
"Specification:\nPartitional: {}\nRegional: {}\nCommon: {}\nZonal: {}\nDevice: {}\nCompiled:\n{}",
|
||||||
Partitional: {}\n\
|
|
||||||
Regional: {}\n\
|
|
||||||
Common: {}\n\
|
|
||||||
Zonal: {}\n\
|
|
||||||
Device: {}\n\
|
|
||||||
Compiled:\n{}",
|
|
||||||
self.partitional, self.regional, self.common, self.zonal, self.device, self.compiled
|
self.partitional, self.regional, self.common, self.zonal, self.device, self.compiled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -119,20 +93,8 @@ pub fn compile(pattern: &Regex, spec_path: &String, dbg: LogLevel) -> Vec<Specif
|
|||||||
let regional: String = get_regional(&spec);
|
let regional: String = get_regional(&spec);
|
||||||
let partitional: String = get_partional(&common, ®ional);
|
let partitional: String = get_partional(&common, ®ional);
|
||||||
specifications.push(
|
specifications.push(
|
||||||
match Specification::build(&partitional, ®ional, &common, &zonal, &spec) {
|
match Specification::build(partitional, regional, common, zonal, spec) {
|
||||||
Ok(compiled_spec) => {
|
Ok(compiled_spec) => compiled_spec,
|
||||||
dbug!(
|
|
||||||
dbg,
|
|
||||||
"Compiled Spec for '{}'\n | {}\n | {}\n | {}\n | {}\n | {}",
|
|
||||||
compiled_spec.get_hostname(),
|
|
||||||
compiled_spec.partitional,
|
|
||||||
compiled_spec.regional,
|
|
||||||
compiled_spec.common,
|
|
||||||
compiled_spec.zonal,
|
|
||||||
compiled_spec.device
|
|
||||||
);
|
|
||||||
compiled_spec
|
|
||||||
}
|
|
||||||
Err(e) => panic!("failed to build Specification: {}", e),
|
Err(e) => panic!("failed to build Specification: {}", e),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
43
src/tmpls.rs
43
src/tmpls.rs
@@ -1,9 +1,9 @@
|
|||||||
use crate::specs::Specification;
|
use crate::specs::Specification;
|
||||||
use crate::{info, verb, LogLevel};
|
use crate::{LogLevel, info, verb};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use serde_yml;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
use yaml_serde;
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct TemplateConfig {
|
struct TemplateConfig {
|
||||||
@@ -40,8 +40,17 @@ pub fn process_templates(
|
|||||||
|
|
||||||
// Read structure.yaml
|
// Read structure.yaml
|
||||||
let structure_path = format!("{}/structure.yaml", base_dir);
|
let structure_path = format!("{}/structure.yaml", base_dir);
|
||||||
let structure_content = fs::read_to_string(&structure_path)?;
|
let structure_content: String = match fs::read_to_string(&structure_path) {
|
||||||
let config: TemplateConfig = serde_yml::from_str(&structure_content)?;
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"while attempting to read {}: {}, skipping",
|
||||||
|
&structure_path, e
|
||||||
|
);
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let config: TemplateConfig = yaml_serde::from_str(&structure_content)?;
|
||||||
|
|
||||||
// Initialize Tera with the specific directory
|
// Initialize Tera with the specific directory
|
||||||
let mut tera = Tera::default();
|
let mut tera = Tera::default();
|
||||||
@@ -51,15 +60,35 @@ pub fn process_templates(
|
|||||||
let mut configs = Vec::new();
|
let mut configs = Vec::new();
|
||||||
for template_name in config.files {
|
for template_name in config.files {
|
||||||
// Read the template file directly
|
// Read the template file directly
|
||||||
let template_path = format!("{}/{}.tmpl", base_dir, template_name);
|
let template_path = format!("{}/{}.tera", base_dir, template_name);
|
||||||
verb!(dbg, " | {}", &template_path);
|
verb!(dbg, " | {}", &template_path);
|
||||||
let template_content = fs::read_to_string(&template_path)?;
|
let template_content = match fs::read_to_string(&template_path) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"while attempting to read {}: {}, skipping",
|
||||||
|
&template_path, e
|
||||||
|
);
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Add this specific template to Tera
|
// Add this specific template to Tera
|
||||||
tera.add_raw_template(&template_name, &template_content)?;
|
tera.add_raw_template(&template_name, &template_content)?;
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
let rendered = tera.render(&template_name, &context)?;
|
let rendered = tera
|
||||||
|
.render(&template_name, &context)
|
||||||
|
.inspect_err(|e| {
|
||||||
|
let mut chain = format!("{}", e);
|
||||||
|
let mut next_source = std::error::Error::source(e);
|
||||||
|
while let Some(source) = next_source {
|
||||||
|
chain.push_str(&format!(" -> {}", source));
|
||||||
|
next_source = source.source();
|
||||||
|
}
|
||||||
|
eprintln!("[tera] {}", chain);
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
configs.push((String::from(&template_name), rendered));
|
configs.push((String::from(&template_name), rendered));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user