Compare commits
	
		
			No commits in common. "8f470278a749feb3f46e46f35c3eeeed565d3018" and "eeafa086f323ecd58f103d906a37245f26f7fa7f" have entirely different histories.
		
	
	
		
			8f470278a7
			...
			eeafa086f3
		
	
		
							
								
								
									
										505
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										505
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -151,12 +151,6 @@ dependencies = [ | |||||||
|  "windows-sys 0.59.0", |  "windows-sys 0.59.0", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "arc-swap" |  | ||||||
| version = "1.7.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "assert_cmd" | name = "assert_cmd" | ||||||
| version = "2.0.17" | version = "2.0.17" | ||||||
| @ -184,12 +178,6 @@ dependencies = [ | |||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "atomic-waker" |  | ||||||
| version = "1.1.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "autocfg" | name = "autocfg" | ||||||
| version = "1.4.0" | version = "1.4.0" | ||||||
| @ -413,9 +401,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap" | name = "clap" | ||||||
| version = "4.5.37" | version = "4.5.36" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" | checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "clap_builder", |  "clap_builder", | ||||||
|  "clap_derive", |  "clap_derive", | ||||||
| @ -423,9 +411,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_builder" | name = "clap_builder" | ||||||
| version = "4.5.37" | version = "4.5.36" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" | checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anstream", |  "anstream", | ||||||
|  "anstyle", |  "anstyle", | ||||||
| @ -568,21 +556,6 @@ dependencies = [ | |||||||
|  "cfg-if", |  "cfg-if", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "crossbeam-channel" |  | ||||||
| version = "0.5.15" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" |  | ||||||
| dependencies = [ |  | ||||||
|  "crossbeam-utils", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "crossbeam-utils" |  | ||||||
| version = "0.8.21" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "crossterm" | name = "crossterm" | ||||||
| version = "0.25.0" | version = "0.25.0" | ||||||
| @ -733,24 +706,15 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "der" | name = "der" | ||||||
| version = "0.7.10" | version = "0.7.9" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "const-oid", |  "const-oid", | ||||||
|  "pem-rfc7468", |  "pem-rfc7468", | ||||||
|  "zeroize", |  "zeroize", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "deranged" |  | ||||||
| version = "0.4.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" |  | ||||||
| dependencies = [ |  | ||||||
|  "powerfmt", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "derive-new" | name = "derive-new" | ||||||
| version = "0.7.0" | version = "0.7.0" | ||||||
| @ -1250,10 +1214,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "js-sys", |  | ||||||
|  "libc", |  "libc", | ||||||
|  "wasi 0.11.0+wasi-snapshot-preview1", |  "wasi 0.11.0+wasi-snapshot-preview1", | ||||||
|  "wasm-bindgen", |  | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -1314,25 +1276,6 @@ dependencies = [ | |||||||
|  "tracing", |  "tracing", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "h2" |  | ||||||
| version = "0.4.9" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" |  | ||||||
| dependencies = [ |  | ||||||
|  "atomic-waker", |  | ||||||
|  "bytes", |  | ||||||
|  "fnv", |  | ||||||
|  "futures-core", |  | ||||||
|  "futures-sink", |  | ||||||
|  "http 1.3.1", |  | ||||||
|  "indexmap", |  | ||||||
|  "slab", |  | ||||||
|  "tokio", |  | ||||||
|  "tokio-util", |  | ||||||
|  "tracing", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "harmony" | name = "harmony" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| @ -1344,14 +1287,13 @@ dependencies = [ | |||||||
|  "harmony_macros", |  "harmony_macros", | ||||||
|  "harmony_types", |  "harmony_types", | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  "inquire", |  | ||||||
|  "k8s-openapi", |  "k8s-openapi", | ||||||
|  "kube", |  "kube", | ||||||
|  "libredfish", |  "libredfish", | ||||||
|  "log", |  "log", | ||||||
|  "opnsense-config", |  "opnsense-config", | ||||||
|  "opnsense-config-xml", |  "opnsense-config-xml", | ||||||
|  "reqwest 0.11.27", |  "reqwest", | ||||||
|  "russh", |  "russh", | ||||||
|  "rust-ipmi", |  "rust-ipmi", | ||||||
|  "semver", |  "semver", | ||||||
| @ -1560,30 +1502,6 @@ version = "1.0.3" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "httptest" |  | ||||||
| version = "0.16.3" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "bde82de3ef9bd882493c6a5edbc3363ad928925b30ccecc0f2ddeb42601b3021" |  | ||||||
| dependencies = [ |  | ||||||
|  "bstr", |  | ||||||
|  "bytes", |  | ||||||
|  "crossbeam-channel", |  | ||||||
|  "form_urlencoded", |  | ||||||
|  "futures", |  | ||||||
|  "http 1.3.1", |  | ||||||
|  "http-body-util", |  | ||||||
|  "hyper 1.6.0", |  | ||||||
|  "hyper-util", |  | ||||||
|  "log", |  | ||||||
|  "once_cell", |  | ||||||
|  "regex", |  | ||||||
|  "serde", |  | ||||||
|  "serde_json", |  | ||||||
|  "serde_urlencoded", |  | ||||||
|  "tokio", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "hyper" | name = "hyper" | ||||||
| version = "0.14.32" | version = "0.14.32" | ||||||
| @ -1594,7 +1512,7 @@ dependencies = [ | |||||||
|  "futures-channel", |  "futures-channel", | ||||||
|  "futures-core", |  "futures-core", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "h2 0.3.26", |  "h2", | ||||||
|  "http 0.2.12", |  "http 0.2.12", | ||||||
|  "http-body 0.4.6", |  "http-body 0.4.6", | ||||||
|  "httparse", |  "httparse", | ||||||
| @ -1617,11 +1535,9 @@ dependencies = [ | |||||||
|  "bytes", |  "bytes", | ||||||
|  "futures-channel", |  "futures-channel", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "h2 0.4.9", |  | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  "http-body 1.0.1", |  "http-body 1.0.1", | ||||||
|  "httparse", |  "httparse", | ||||||
|  "httpdate", |  | ||||||
|  "itoa", |  "itoa", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
|  "smallvec", |  "smallvec", | ||||||
| @ -1694,22 +1610,6 @@ dependencies = [ | |||||||
|  "tokio-native-tls", |  "tokio-native-tls", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "hyper-tls" |  | ||||||
| version = "0.6.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" |  | ||||||
| dependencies = [ |  | ||||||
|  "bytes", |  | ||||||
|  "http-body-util", |  | ||||||
|  "hyper 1.6.0", |  | ||||||
|  "hyper-util", |  | ||||||
|  "native-tls", |  | ||||||
|  "tokio", |  | ||||||
|  "tokio-native-tls", |  | ||||||
|  "tower-service", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "hyper-util" | name = "hyper-util" | ||||||
| version = "0.1.11" | version = "0.1.11" | ||||||
| @ -1967,16 +1867,6 @@ version = "2.11.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "iri-string" |  | ||||||
| version = "0.7.8" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" |  | ||||||
| dependencies = [ |  | ||||||
|  "memchr", |  | ||||||
|  "serde", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "is_terminal_polyfill" | name = "is_terminal_polyfill" | ||||||
| version = "1.70.1" | version = "1.70.1" | ||||||
| @ -2045,39 +1935,6 @@ dependencies = [ | |||||||
|  "thiserror 2.0.12", |  "thiserror 2.0.12", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "jsonwebtoken" |  | ||||||
| version = "9.3.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" |  | ||||||
| dependencies = [ |  | ||||||
|  "base64 0.22.1", |  | ||||||
|  "js-sys", |  | ||||||
|  "pem", |  | ||||||
|  "ring", |  | ||||||
|  "serde", |  | ||||||
|  "serde_json", |  | ||||||
|  "simple_asn1", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "k3d-rs" |  | ||||||
| version = "0.1.0" |  | ||||||
| dependencies = [ |  | ||||||
|  "async-trait", |  | ||||||
|  "env_logger", |  | ||||||
|  "futures-util", |  | ||||||
|  "httptest", |  | ||||||
|  "log", |  | ||||||
|  "octocrab", |  | ||||||
|  "pretty_assertions", |  | ||||||
|  "regex", |  | ||||||
|  "reqwest 0.12.15", |  | ||||||
|  "sha2", |  | ||||||
|  "tokio", |  | ||||||
|  "url", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "k8s-openapi" | name = "k8s-openapi" | ||||||
| version = "0.24.0" | version = "0.24.0" | ||||||
| @ -2184,7 +2041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "e7f0a8985e53d18c60dc82e7b5fa512fd194ea4c0d8bf1409b65cf44f8b0a8d9" | checksum = "e7f0a8985e53d18c60dc82e7b5fa512fd194ea4c0d8bf1409b65cf44f8b0a8d9" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "log", |  "log", | ||||||
|  "reqwest 0.11.27", |  "reqwest", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_derive", |  "serde_derive", | ||||||
|  "serde_json", |  "serde_json", | ||||||
| @ -2356,12 +2213,6 @@ dependencies = [ | |||||||
|  "zeroize", |  "zeroize", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "num-conv" |  | ||||||
| version = "0.1.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "num-integer" | name = "num-integer" | ||||||
| version = "0.1.46" | version = "0.1.46" | ||||||
| @ -2411,46 +2262,6 @@ dependencies = [ | |||||||
|  "memchr", |  "memchr", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "octocrab" |  | ||||||
| version = "0.44.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "aaf799a9982a4d0b4b3fa15b4c1ff7daf5bd0597f46456744dcbb6ddc2e4c827" |  | ||||||
| dependencies = [ |  | ||||||
|  "arc-swap", |  | ||||||
|  "async-trait", |  | ||||||
|  "base64 0.22.1", |  | ||||||
|  "bytes", |  | ||||||
|  "cfg-if", |  | ||||||
|  "chrono", |  | ||||||
|  "either", |  | ||||||
|  "futures", |  | ||||||
|  "futures-util", |  | ||||||
|  "http 1.3.1", |  | ||||||
|  "http-body 1.0.1", |  | ||||||
|  "http-body-util", |  | ||||||
|  "hyper 1.6.0", |  | ||||||
|  "hyper-rustls", |  | ||||||
|  "hyper-timeout", |  | ||||||
|  "hyper-util", |  | ||||||
|  "jsonwebtoken", |  | ||||||
|  "once_cell", |  | ||||||
|  "percent-encoding", |  | ||||||
|  "pin-project", |  | ||||||
|  "secrecy", |  | ||||||
|  "serde", |  | ||||||
|  "serde_json", |  | ||||||
|  "serde_path_to_error", |  | ||||||
|  "serde_urlencoded", |  | ||||||
|  "snafu", |  | ||||||
|  "tokio", |  | ||||||
|  "tower", |  | ||||||
|  "tower-http", |  | ||||||
|  "tracing", |  | ||||||
|  "url", |  | ||||||
|  "web-time", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "once_cell" | name = "once_cell" | ||||||
| version = "1.21.3" | version = "1.21.3" | ||||||
| @ -2731,26 +2542,6 @@ dependencies = [ | |||||||
|  "sha2", |  "sha2", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "pin-project" |  | ||||||
| version = "1.1.10" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" |  | ||||||
| dependencies = [ |  | ||||||
|  "pin-project-internal", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "pin-project-internal" |  | ||||||
| version = "1.1.10" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" |  | ||||||
| dependencies = [ |  | ||||||
|  "proc-macro2", |  | ||||||
|  "quote", |  | ||||||
|  "syn", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pin-project-lite" | name = "pin-project-lite" | ||||||
| version = "0.2.16" | version = "0.2.16" | ||||||
| @ -2845,12 +2636,6 @@ dependencies = [ | |||||||
|  "portable-atomic", |  "portable-atomic", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "powerfmt" |  | ||||||
| version = "0.2.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "ppv-lite86" | name = "ppv-lite86" | ||||||
| version = "0.2.21" | version = "0.2.21" | ||||||
| @ -3065,11 +2850,11 @@ dependencies = [ | |||||||
|  "encoding_rs", |  "encoding_rs", | ||||||
|  "futures-core", |  "futures-core", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "h2 0.3.26", |  "h2", | ||||||
|  "http 0.2.12", |  "http 0.2.12", | ||||||
|  "http-body 0.4.6", |  "http-body 0.4.6", | ||||||
|  "hyper 0.14.32", |  "hyper 0.14.32", | ||||||
|  "hyper-tls 0.5.0", |  "hyper-tls", | ||||||
|  "ipnet", |  "ipnet", | ||||||
|  "js-sys", |  "js-sys", | ||||||
|  "log", |  "log", | ||||||
| @ -3083,7 +2868,7 @@ dependencies = [ | |||||||
|  "serde_json", |  "serde_json", | ||||||
|  "serde_urlencoded", |  "serde_urlencoded", | ||||||
|  "sync_wrapper 0.1.2", |  "sync_wrapper 0.1.2", | ||||||
|  "system-configuration 0.5.1", |  "system-configuration", | ||||||
|  "tokio", |  "tokio", | ||||||
|  "tokio-native-tls", |  "tokio-native-tls", | ||||||
|  "tower-service", |  "tower-service", | ||||||
| @ -3094,52 +2879,6 @@ dependencies = [ | |||||||
|  "winreg", |  "winreg", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "reqwest" |  | ||||||
| version = "0.12.15" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" |  | ||||||
| dependencies = [ |  | ||||||
|  "base64 0.22.1", |  | ||||||
|  "bytes", |  | ||||||
|  "encoding_rs", |  | ||||||
|  "futures-core", |  | ||||||
|  "futures-util", |  | ||||||
|  "h2 0.4.9", |  | ||||||
|  "http 1.3.1", |  | ||||||
|  "http-body 1.0.1", |  | ||||||
|  "http-body-util", |  | ||||||
|  "hyper 1.6.0", |  | ||||||
|  "hyper-rustls", |  | ||||||
|  "hyper-tls 0.6.0", |  | ||||||
|  "hyper-util", |  | ||||||
|  "ipnet", |  | ||||||
|  "js-sys", |  | ||||||
|  "log", |  | ||||||
|  "mime", |  | ||||||
|  "native-tls", |  | ||||||
|  "once_cell", |  | ||||||
|  "percent-encoding", |  | ||||||
|  "pin-project-lite", |  | ||||||
|  "rustls-pemfile 2.2.0", |  | ||||||
|  "serde", |  | ||||||
|  "serde_json", |  | ||||||
|  "serde_urlencoded", |  | ||||||
|  "sync_wrapper 1.0.2", |  | ||||||
|  "system-configuration 0.6.1", |  | ||||||
|  "tokio", |  | ||||||
|  "tokio-native-tls", |  | ||||||
|  "tokio-util", |  | ||||||
|  "tower", |  | ||||||
|  "tower-service", |  | ||||||
|  "url", |  | ||||||
|  "wasm-bindgen", |  | ||||||
|  "wasm-bindgen-futures", |  | ||||||
|  "wasm-streams", |  | ||||||
|  "web-sys", |  | ||||||
|  "windows-registry", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "rfc6979" | name = "rfc6979" | ||||||
| version = "0.4.0" | version = "0.4.0" | ||||||
| @ -3291,9 +3030,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "russh-sftp" | name = "russh-sftp" | ||||||
| version = "2.1.1" | version = "2.1.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3bb94393cafad0530145b8f626d8687f1ee1dedb93d7ba7740d6ae81868b13b5" | checksum = "f08ed364d54b74d988c964b464a53a1916379f9441cfd10ca8fb264be1349842" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags 2.9.0", |  "bitflags 2.9.0", | ||||||
|  "bytes", |  "bytes", | ||||||
| @ -3597,16 +3336,6 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "serde_path_to_error" |  | ||||||
| version = "0.1.17" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" |  | ||||||
| dependencies = [ |  | ||||||
|  "itoa", |  | ||||||
|  "serde", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "serde_tokenstream" | name = "serde_tokenstream" | ||||||
| version = "0.2.2" | version = "0.2.2" | ||||||
| @ -3722,18 +3451,6 @@ dependencies = [ | |||||||
|  "rand_core 0.6.4", |  "rand_core 0.6.4", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "simple_asn1" |  | ||||||
| version = "0.6.3" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" |  | ||||||
| dependencies = [ |  | ||||||
|  "num-bigint", |  | ||||||
|  "num-traits", |  | ||||||
|  "thiserror 2.0.12", |  | ||||||
|  "time", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "slab" | name = "slab" | ||||||
| version = "0.4.9" | version = "0.4.9" | ||||||
| @ -3749,27 +3466,6 @@ version = "1.15.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "snafu" |  | ||||||
| version = "0.8.5" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" |  | ||||||
| dependencies = [ |  | ||||||
|  "snafu-derive", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "snafu-derive" |  | ||||||
| version = "0.8.5" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" |  | ||||||
| dependencies = [ |  | ||||||
|  "heck", |  | ||||||
|  "proc-macro2", |  | ||||||
|  "quote", |  | ||||||
|  "syn", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "socket2" | name = "socket2" | ||||||
| version = "0.5.9" | version = "0.5.9" | ||||||
| @ -3915,9 +3611,6 @@ name = "sync_wrapper" | |||||||
| version = "1.0.2" | 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 = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | ||||||
| dependencies = [ |  | ||||||
|  "futures-core", |  | ||||||
| ] |  | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "synstructure" | name = "synstructure" | ||||||
| @ -3938,18 +3631,7 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags 1.3.2", |  "bitflags 1.3.2", | ||||||
|  "core-foundation 0.9.4", |  "core-foundation 0.9.4", | ||||||
|  "system-configuration-sys 0.5.0", |  "system-configuration-sys", | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "system-configuration" |  | ||||||
| version = "0.6.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" |  | ||||||
| dependencies = [ |  | ||||||
|  "bitflags 2.9.0", |  | ||||||
|  "core-foundation 0.9.4", |  | ||||||
|  "system-configuration-sys 0.6.0", |  | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -3962,16 +3644,6 @@ dependencies = [ | |||||||
|  "libc", |  "libc", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "system-configuration-sys" |  | ||||||
| version = "0.6.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" |  | ||||||
| dependencies = [ |  | ||||||
|  "core-foundation-sys", |  | ||||||
|  "libc", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tap" | name = "tap" | ||||||
| version = "1.0.1" | version = "1.0.1" | ||||||
| @ -4047,37 +3719,6 @@ dependencies = [ | |||||||
|  "once_cell", |  "once_cell", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "time" |  | ||||||
| version = "0.3.41" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" |  | ||||||
| dependencies = [ |  | ||||||
|  "deranged", |  | ||||||
|  "itoa", |  | ||||||
|  "num-conv", |  | ||||||
|  "powerfmt", |  | ||||||
|  "serde", |  | ||||||
|  "time-core", |  | ||||||
|  "time-macros", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "time-core" |  | ||||||
| version = "0.1.4" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "time-macros" |  | ||||||
| version = "0.2.22" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" |  | ||||||
| dependencies = [ |  | ||||||
|  "num-conv", |  | ||||||
|  "time-core", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tiny-keccak" | name = "tiny-keccak" | ||||||
| version = "2.0.2" | version = "2.0.2" | ||||||
| @ -4195,13 +3836,10 @@ dependencies = [ | |||||||
|  "base64 0.22.1", |  "base64 0.22.1", | ||||||
|  "bitflags 2.9.0", |  "bitflags 2.9.0", | ||||||
|  "bytes", |  "bytes", | ||||||
|  "futures-util", |  | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  "http-body 1.0.1", |  "http-body 1.0.1", | ||||||
|  "iri-string", |  | ||||||
|  "mime", |  "mime", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
|  "tower", |  | ||||||
|  "tower-layer", |  "tower-layer", | ||||||
|  "tower-service", |  "tower-service", | ||||||
|  "tracing", |  "tracing", | ||||||
| @ -4372,7 +4010,6 @@ dependencies = [ | |||||||
|  "form_urlencoded", |  "form_urlencoded", | ||||||
|  "idna", |  "idna", | ||||||
|  "percent-encoding", |  "percent-encoding", | ||||||
|  "serde", |  | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -4537,19 +4174,6 @@ dependencies = [ | |||||||
|  "unicode-ident", |  "unicode-ident", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "wasm-streams" |  | ||||||
| version = "0.4.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" |  | ||||||
| dependencies = [ |  | ||||||
|  "futures-util", |  | ||||||
|  "js-sys", |  | ||||||
|  "wasm-bindgen", |  | ||||||
|  "wasm-bindgen-futures", |  | ||||||
|  "web-sys", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "web-sys" | name = "web-sys" | ||||||
| version = "0.3.77" | version = "0.3.77" | ||||||
| @ -4560,17 +4184,6 @@ dependencies = [ | |||||||
|  "wasm-bindgen", |  "wasm-bindgen", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "web-time" |  | ||||||
| version = "1.1.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" |  | ||||||
| dependencies = [ |  | ||||||
|  "js-sys", |  | ||||||
|  "serde", |  | ||||||
|  "wasm-bindgen", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "winapi" | name = "winapi" | ||||||
| version = "0.3.9" | version = "0.3.9" | ||||||
| @ -4603,7 +4216,7 @@ dependencies = [ | |||||||
|  "windows-interface", |  "windows-interface", | ||||||
|  "windows-link", |  "windows-link", | ||||||
|  "windows-result", |  "windows-result", | ||||||
|  "windows-strings 0.4.0", |  "windows-strings", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -4634,17 +4247,6 @@ version = "0.1.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows-registry" |  | ||||||
| version = "0.4.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" |  | ||||||
| dependencies = [ |  | ||||||
|  "windows-result", |  | ||||||
|  "windows-strings 0.3.1", |  | ||||||
|  "windows-targets 0.53.0", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows-result" | name = "windows-result" | ||||||
| version = "0.3.2" | version = "0.3.2" | ||||||
| @ -4654,15 +4256,6 @@ dependencies = [ | |||||||
|  "windows-link", |  "windows-link", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows-strings" |  | ||||||
| version = "0.3.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" |  | ||||||
| dependencies = [ |  | ||||||
|  "windows-link", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows-strings" | name = "windows-strings" | ||||||
| version = "0.4.0" | version = "0.4.0" | ||||||
| @ -4723,29 +4316,13 @@ dependencies = [ | |||||||
|  "windows_aarch64_gnullvm 0.52.6", |  "windows_aarch64_gnullvm 0.52.6", | ||||||
|  "windows_aarch64_msvc 0.52.6", |  "windows_aarch64_msvc 0.52.6", | ||||||
|  "windows_i686_gnu 0.52.6", |  "windows_i686_gnu 0.52.6", | ||||||
|  "windows_i686_gnullvm 0.52.6", |  "windows_i686_gnullvm", | ||||||
|  "windows_i686_msvc 0.52.6", |  "windows_i686_msvc 0.52.6", | ||||||
|  "windows_x86_64_gnu 0.52.6", |  "windows_x86_64_gnu 0.52.6", | ||||||
|  "windows_x86_64_gnullvm 0.52.6", |  "windows_x86_64_gnullvm 0.52.6", | ||||||
|  "windows_x86_64_msvc 0.52.6", |  "windows_x86_64_msvc 0.52.6", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows-targets" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" |  | ||||||
| dependencies = [ |  | ||||||
|  "windows_aarch64_gnullvm 0.53.0", |  | ||||||
|  "windows_aarch64_msvc 0.53.0", |  | ||||||
|  "windows_i686_gnu 0.53.0", |  | ||||||
|  "windows_i686_gnullvm 0.53.0", |  | ||||||
|  "windows_i686_msvc 0.53.0", |  | ||||||
|  "windows_x86_64_gnu 0.53.0", |  | ||||||
|  "windows_x86_64_gnullvm 0.53.0", |  | ||||||
|  "windows_x86_64_msvc 0.53.0", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_aarch64_gnullvm" | name = "windows_aarch64_gnullvm" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4758,12 +4335,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_aarch64_gnullvm" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_aarch64_msvc" | name = "windows_aarch64_msvc" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4776,12 +4347,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_aarch64_msvc" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_i686_gnu" | name = "windows_i686_gnu" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4794,24 +4359,12 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_i686_gnu" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_i686_gnullvm" | name = "windows_i686_gnullvm" | ||||||
| version = "0.52.6" | version = "0.52.6" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_i686_gnullvm" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_i686_msvc" | name = "windows_i686_msvc" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4824,12 +4377,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_i686_msvc" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_x86_64_gnu" | name = "windows_x86_64_gnu" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4842,12 +4389,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_gnu" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_x86_64_gnullvm" | name = "windows_x86_64_gnullvm" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4860,12 +4401,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_gnullvm" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_x86_64_msvc" | name = "windows_x86_64_msvc" | ||||||
| version = "0.48.5" | version = "0.48.5" | ||||||
| @ -4878,12 +4413,6 @@ version = "0.52.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_msvc" |  | ||||||
| version = "0.53.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "winreg" | name = "winreg" | ||||||
| version = "0.50.0" | version = "0.50.0" | ||||||
|  | |||||||
| @ -10,7 +10,6 @@ members = [ | |||||||
|   "opnsense-config", |   "opnsense-config", | ||||||
|   "opnsense-config-xml", |   "opnsense-config-xml", | ||||||
|   "harmony_cli", |   "harmony_cli", | ||||||
|   "k3d", |  | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [workspace.package] | [workspace.package] | ||||||
| @ -23,7 +22,7 @@ log = "0.4.22" | |||||||
| env_logger = "0.11.5" | env_logger = "0.11.5" | ||||||
| derive-new = "0.7.0" | derive-new = "0.7.0" | ||||||
| async-trait = "0.1.82" | async-trait = "0.1.82" | ||||||
| tokio = { version = "1.40.0", features = ["io-std", "fs", "macros", "rt-multi-thread"] } | tokio = { version = "1.40.0", features = ["io-std", "fs"] } | ||||||
| cidr = "0.2.3" | cidr = "0.2.3" | ||||||
| russh = "0.45.0" | russh = "0.45.0" | ||||||
| russh-keys = "0.45.0" | russh-keys = "0.45.0" | ||||||
| @ -34,7 +33,6 @@ k8s-openapi = { version = "0.24.0", features = ["v1_30"] } | |||||||
| serde_yaml = "0.9.34" | serde_yaml = "0.9.34" | ||||||
| serde-value = "0.7.0" | serde-value = "0.7.0" | ||||||
| http = "1.2.0" | http = "1.2.0" | ||||||
| inquire = "0.7.5" |  | ||||||
| 
 | 
 | ||||||
| [workspace.dependencies.uuid] | [workspace.dependencies.uuid] | ||||||
| version = "1.11.0" | version = "1.11.0" | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     data::Version, |     data::Version, | ||||||
|  |     inventory::Inventory, | ||||||
|     maestro::Maestro, |     maestro::Maestro, | ||||||
|     modules::lamp::{LAMPConfig, LAMPScore}, |     modules::lamp::{LAMPConfig, LAMPScore}, | ||||||
|     topology::{K8sAnywhereTopology, Url}, |     topology::{HAClusterTopology, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| @ -17,7 +18,9 @@ async fn main() { | |||||||
|         }, |         }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let mut maestro = Maestro::<K8sAnywhereTopology>::load_from_env(); |     let inventory = Inventory::autoload(); | ||||||
|  |     let topology = HAClusterTopology::autoload(); | ||||||
|  |     let mut maestro = Maestro::new(inventory, topology); | ||||||
|     maestro.register_all(vec![Box::new(lamp_stack)]); |     maestro.register_all(vec![Box::new(lamp_stack)]); | ||||||
|     harmony_tui::init(maestro).await.unwrap(); |     harmony_tui::init(maestro).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ async fn main() { | |||||||
|     let topology = HAClusterTopology::autoload(); |     let topology = HAClusterTopology::autoload(); | ||||||
|     let mut maestro = Maestro::new(inventory, topology); |     let mut maestro = Maestro::new(inventory, topology); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     maestro.register_all(vec![ |     maestro.register_all(vec![ | ||||||
|         Box::new(SuccessScore {}), |         Box::new(SuccessScore {}), | ||||||
|         Box::new(ErrorScore {}), |         Box::new(ErrorScore {}), | ||||||
|  | |||||||
| @ -30,4 +30,3 @@ k8s-openapi = { workspace = true } | |||||||
| serde_yaml = { workspace = true } | serde_yaml = { workspace = true } | ||||||
| http = { workspace = true } | http = { workspace = true } | ||||||
| serde-value = { workspace = true } | serde-value = { workspace = true } | ||||||
| inquire.workspace = true |  | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ pub enum InterpretName { | |||||||
|     Dummy, |     Dummy, | ||||||
|     Panic, |     Panic, | ||||||
|     OPNSense, |     OPNSense, | ||||||
|     K3dInstallation, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::fmt::Display for InterpretName { | impl std::fmt::Display for InterpretName { | ||||||
| @ -33,7 +32,6 @@ impl std::fmt::Display for InterpretName { | |||||||
|             InterpretName::Dummy => f.write_str("Dummy"), |             InterpretName::Dummy => f.write_str("Dummy"), | ||||||
|             InterpretName::Panic => f.write_str("Panic"), |             InterpretName::Panic => f.write_str("Panic"), | ||||||
|             InterpretName::OPNSense => f.write_str("OPNSense"), |             InterpretName::OPNSense => f.write_str("OPNSense"), | ||||||
|             InterpretName::K3dInstallation => f.write_str("K3dInstallation"), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| use std::sync::{Arc, Mutex, RwLock}; | use std::sync::{Arc, RwLock}; | ||||||
| 
 | 
 | ||||||
| use log::{info, warn}; | use log::info; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|     interpret::{InterpretError, InterpretStatus, Outcome}, |     interpret::{InterpretError, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::Topology, |     topology::Topology, | ||||||
| @ -15,7 +15,6 @@ pub struct Maestro<T: Topology> { | |||||||
|     inventory: Inventory, |     inventory: Inventory, | ||||||
|     topology: T, |     topology: T, | ||||||
|     scores: Arc<RwLock<ScoreVec<T>>>, |     scores: Arc<RwLock<ScoreVec<T>>>, | ||||||
|     topology_preparation_result: Mutex<Option<Outcome>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> Maestro<T> { | impl<T: Topology> Maestro<T> { | ||||||
| @ -24,28 +23,9 @@ impl<T: Topology> Maestro<T> { | |||||||
|             inventory, |             inventory, | ||||||
|             topology, |             topology, | ||||||
|             scores: Arc::new(RwLock::new(Vec::new())), |             scores: Arc::new(RwLock::new(Vec::new())), | ||||||
|             topology_preparation_result: None.into(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Ensures the associated Topology is ready for operations.
 |  | ||||||
|     /// Delegates the readiness check and potential setup actions to the Topology.
 |  | ||||||
|     pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { |  | ||||||
|         info!("Ensuring topology '{}' is ready...", self.topology.name()); |  | ||||||
|         let outcome = self.topology.ensure_ready().await?; |  | ||||||
|         info!( |  | ||||||
|             "Topology '{}' readiness check complete: {}", |  | ||||||
|             self.topology.name(), |  | ||||||
|             outcome.status |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         self.topology_preparation_result |  | ||||||
|             .lock() |  | ||||||
|             .unwrap() |  | ||||||
|             .replace(outcome.clone()); |  | ||||||
|         Ok(outcome) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Load the inventory and inventory from environment.
 |     // Load the inventory and inventory from environment.
 | ||||||
|     // This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
 |     // This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
 | ||||||
|     // When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
 |     // When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
 | ||||||
| @ -67,31 +47,16 @@ impl<T: Topology> Maestro<T> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn start(&mut self) { | ||||||
|  |         info!("Starting Maestro"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn register_all(&mut self, mut scores: ScoreVec<T>) { |     pub fn register_all(&mut self, mut scores: ScoreVec<T>) { | ||||||
|         let mut score_mut = self.scores.write().expect("Should acquire lock"); |         let mut score_mut = self.scores.write().expect("Should acquire lock"); | ||||||
|         score_mut.append(&mut scores); |         score_mut.append(&mut scores); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn is_topology_initialized(&self) -> bool { |  | ||||||
|         let result = self.topology_preparation_result.lock().unwrap(); |  | ||||||
|         if let Some(outcome) = result.as_ref() { |  | ||||||
|             match outcome.status { |  | ||||||
|                 InterpretStatus::SUCCESS => return true, |  | ||||||
|                 _ => return false, |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             false |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> { |     pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> { | ||||||
|         if !self.is_topology_initialized() { |  | ||||||
|             warn!( |  | ||||||
|                 "Launching interpret for score {} but Topology {} is not fully initialized!", |  | ||||||
|                 score.name(), |  | ||||||
|                 self.topology.name(), |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         info!("Running score {score:?}"); |         info!("Running score {score:?}"); | ||||||
|         let interpret = score.create_interpret(); |         let interpret = score.create_interpret(); | ||||||
|         info!("Launching interpret {interpret:?}"); |         info!("Launching interpret {interpret:?}"); | ||||||
|  | |||||||
| @ -3,8 +3,6 @@ use harmony_macros::ip; | |||||||
| use harmony_types::net::MacAddress; | use harmony_types::net::MacAddress; | ||||||
| 
 | 
 | ||||||
| use crate::executors::ExecutorError; | use crate::executors::ExecutorError; | ||||||
| use crate::interpret::InterpretError; |  | ||||||
| use crate::interpret::Outcome; |  | ||||||
| 
 | 
 | ||||||
| use super::DHCPStaticEntry; | use super::DHCPStaticEntry; | ||||||
| use super::DhcpServer; | use super::DhcpServer; | ||||||
| @ -14,16 +12,16 @@ use super::DnsServer; | |||||||
| use super::Firewall; | use super::Firewall; | ||||||
| use super::HttpServer; | use super::HttpServer; | ||||||
| use super::IpAddress; | use super::IpAddress; | ||||||
| use super::K8sclient; |  | ||||||
| use super::LoadBalancer; | use super::LoadBalancer; | ||||||
| use super::LoadBalancerService; | use super::LoadBalancerService; | ||||||
| use super::LogicalHost; | use super::LogicalHost; | ||||||
|  | use super::OcK8sclient; | ||||||
| use super::Router; | use super::Router; | ||||||
| use super::TftpServer; | use super::TftpServer; | ||||||
| 
 | 
 | ||||||
| use super::Topology; | use super::Topology; | ||||||
| use super::Url; | use super::Url; | ||||||
| use super::k8s::K8sClient; | use super::openshift::OpenshiftClient; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| @ -42,22 +40,16 @@ pub struct HAClusterTopology { | |||||||
|     pub switch: Vec<LogicalHost>, |     pub switch: Vec<LogicalHost>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] |  | ||||||
| impl Topology for HAClusterTopology { | impl Topology for HAClusterTopology { | ||||||
|     fn name(&self) -> &str { |     fn name(&self) -> &str { | ||||||
|         todo!() |         todo!() | ||||||
|     } |     } | ||||||
|     async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { |  | ||||||
|         todo!( |  | ||||||
|             "ensure_ready, not entirely sure what it should do here, probably something like verify that the hosts are reachable and all services are up and ready." |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl K8sclient for HAClusterTopology { | impl OcK8sclient for HAClusterTopology { | ||||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error> { |     async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error> { | ||||||
|         Ok(Arc::new(K8sClient::try_default().await?)) |         Ok(Arc::new(OpenshiftClient::try_default().await?)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,144 +0,0 @@ | |||||||
| use std::io; |  | ||||||
| 
 |  | ||||||
| use async_trait::async_trait; |  | ||||||
| use inquire::Confirm; |  | ||||||
| use log::{info, warn}; |  | ||||||
| use tokio::sync::OnceCell; |  | ||||||
| 
 |  | ||||||
| use crate::{ |  | ||||||
|     interpret::{InterpretError, Outcome}, |  | ||||||
|     inventory::Inventory, |  | ||||||
|     maestro::Maestro, |  | ||||||
|     modules::k3d::K3DInstallationScore, |  | ||||||
|     topology::LocalhostTopology, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use super::{Topology, k8s::K8sClient}; |  | ||||||
| 
 |  | ||||||
| struct K8sState { |  | ||||||
|     client: K8sClient, |  | ||||||
|     source: K8sSource, |  | ||||||
|     message: String, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| enum K8sSource { |  | ||||||
|     RemoteCluster, |  | ||||||
|     LocalK3d, |  | ||||||
|     // TODO: Add variants for cloud providers like AwsEks, Gke, Aks
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub struct K8sAnywhereTopology { |  | ||||||
|     k8s_state: OnceCell<Option<K8sState>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl K8sAnywhereTopology { |  | ||||||
|     async fn try_load_system_kubeconfig(&self) -> Option<K8sClient> { |  | ||||||
|         todo!("Use kube-rs default behavior to load system kubeconfig"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn try_load_kubeconfig(&self, path: &str) -> Option<K8sClient> { |  | ||||||
|         todo!("Use kube-rs to load kubeconfig at path {path}"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn try_install_k3d(&self) -> Result<K8sClient, InterpretError> { |  | ||||||
|         let maestro = Maestro::new(Inventory::autoload(), LocalhostTopology::new()); |  | ||||||
|         let k3d_score = K3DInstallationScore::new(); |  | ||||||
|         maestro.interpret(Box::new(k3d_score)).await?; |  | ||||||
|         todo!( |  | ||||||
|             "Create Maestro with LocalDockerTopology or something along these lines and run a K3dInstallationScore on it" |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn try_get_or_install_k8s_client(&self) -> Result<Option<K8sState>, InterpretError> { |  | ||||||
|         let k8s_anywhere_config = K8sAnywhereConfig { |  | ||||||
|             kubeconfig: std::env::var("HARMONY_KUBECONFIG") |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|v| v.to_string()), |  | ||||||
|             use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG") |  | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |  | ||||||
|             autoinstall: std::env::var("HARMONY_AUTOINSTALL") |  | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if k8s_anywhere_config.use_system_kubeconfig { |  | ||||||
|             match self.try_load_system_kubeconfig().await { |  | ||||||
|                 Some(client) => todo!(), |  | ||||||
|                 None => todo!(), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if let Some(kubeconfig) = k8s_anywhere_config.kubeconfig { |  | ||||||
|             match self.try_load_kubeconfig(&kubeconfig).await { |  | ||||||
|                 Some(client) => todo!(), |  | ||||||
|                 None => todo!(), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         info!("No kubernetes configuration found"); |  | ||||||
| 
 |  | ||||||
|         if !k8s_anywhere_config.autoinstall { |  | ||||||
|             let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") |  | ||||||
|                 .with_default(false) |  | ||||||
|                 .prompt() |  | ||||||
|                 .expect("Unexpected prompt error"); |  | ||||||
| 
 |  | ||||||
|             if !confirmation { |  | ||||||
|                 warn!( |  | ||||||
|                     "Installation cancelled, K8sAnywhere could not initialize a valid Kubernetes client" |  | ||||||
|                 ); |  | ||||||
|                 return Ok(None); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         info!("Starting K8sAnywhere installation"); |  | ||||||
|         match self.try_install_k3d().await { |  | ||||||
|             Ok(client) => Ok(Some(K8sState { |  | ||||||
|                 client, |  | ||||||
|                 source: K8sSource::LocalK3d, |  | ||||||
|                 message: "Successfully installed K3D cluster and acquired client".to_string(), |  | ||||||
|             })), |  | ||||||
|             Err(_) => todo!(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct K8sAnywhereConfig { |  | ||||||
|     /// The path of the KUBECONFIG file that Harmony should use to interact with the Kubernetes
 |  | ||||||
|     /// cluster
 |  | ||||||
|     ///
 |  | ||||||
|     /// Default : None
 |  | ||||||
|     kubeconfig: Option<String>, |  | ||||||
| 
 |  | ||||||
|     /// Whether to use the system KUBECONFIG, either the environment variable or the file in the
 |  | ||||||
|     /// default or configured location
 |  | ||||||
|     ///
 |  | ||||||
|     /// Default : false
 |  | ||||||
|     use_system_kubeconfig: bool, |  | ||||||
| 
 |  | ||||||
|     /// Whether to install automatically a kubernetes cluster
 |  | ||||||
|     ///
 |  | ||||||
|     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 |  | ||||||
|     ///
 |  | ||||||
|     /// Default: true
 |  | ||||||
|     autoinstall: bool, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl Topology for K8sAnywhereTopology { |  | ||||||
|     fn name(&self) -> &str { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { |  | ||||||
|         match self |  | ||||||
|             .k8s_state |  | ||||||
|             .get_or_try_init(|| self.try_get_or_install_k8s_client()) |  | ||||||
|             .await? |  | ||||||
|         { |  | ||||||
|             Some(k8s_state) => Ok(Outcome::success(k8s_state.message.clone())), |  | ||||||
|             None => Err(InterpretError::new( |  | ||||||
|                 "No K8s client could be found or installed".to_string(), |  | ||||||
|             )), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| use async_trait::async_trait; |  | ||||||
| use derive_new::new; |  | ||||||
| 
 |  | ||||||
| use crate::interpret::{InterpretError, Outcome}; |  | ||||||
| 
 |  | ||||||
| use super::Topology; |  | ||||||
| 
 |  | ||||||
| #[derive(new)] |  | ||||||
| pub struct LocalhostTopology; |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl Topology for LocalhostTopology { |  | ||||||
|     fn name(&self) -> &str { |  | ||||||
|         "LocalHostTopology" |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { |  | ||||||
|         Ok(Outcome::success( |  | ||||||
|             "Localhost is Chuck Norris, always ready.".to_string(), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,15 +1,10 @@ | |||||||
| mod ha_cluster; | mod ha_cluster; | ||||||
| mod host_binding; | mod host_binding; | ||||||
| mod http; | mod http; | ||||||
| mod k8s_anywhere; |  | ||||||
| mod localhost; |  | ||||||
| pub use k8s_anywhere::*; |  | ||||||
| pub use localhost::*; |  | ||||||
| pub mod k8s; |  | ||||||
| mod load_balancer; | mod load_balancer; | ||||||
|  | pub mod openshift; | ||||||
| mod router; | mod router; | ||||||
| mod tftp; | mod tftp; | ||||||
| use async_trait::async_trait; |  | ||||||
| pub use ha_cluster::*; | pub use ha_cluster::*; | ||||||
| pub use load_balancer::*; | pub use load_balancer::*; | ||||||
| pub use router::*; | pub use router::*; | ||||||
| @ -22,38 +17,8 @@ pub use tftp::*; | |||||||
| 
 | 
 | ||||||
| use std::net::IpAddr; | use std::net::IpAddr; | ||||||
| 
 | 
 | ||||||
| use super::interpret::{InterpretError, Outcome}; | pub trait Topology { | ||||||
| 
 |  | ||||||
| /// Represents a logical view of an infrastructure environment providing specific capabilities.
 |  | ||||||
| ///
 |  | ||||||
| /// A Topology acts as a self-contained "package" responsible for managing access
 |  | ||||||
| /// to its underlying resources and ensuring they are in a ready state before use.
 |  | ||||||
| /// It defines the contract for the capabilities it provides through implemented
 |  | ||||||
| /// capability traits (e.g., `HasK8sCapability`, `HasDnsServer`).
 |  | ||||||
| #[async_trait] |  | ||||||
| pub trait Topology: Send + Sync { |  | ||||||
|     /// Returns a unique identifier or name for this specific topology instance.
 |  | ||||||
|     /// This helps differentiate between multiple instances of potentially the same type.
 |  | ||||||
|     fn name(&self) -> &str; |     fn name(&self) -> &str; | ||||||
| 
 |  | ||||||
|     /// Ensures that the topology and its required underlying components or services
 |  | ||||||
|     /// are ready to provide their declared capabilities.
 |  | ||||||
|     ///
 |  | ||||||
|     /// Implementations of this method MUST be idempotent. Subsequent calls after a
 |  | ||||||
|     /// successful readiness check should ideally be cheap NO-OPs.
 |  | ||||||
|     ///
 |  | ||||||
|     /// This method encapsulates the logic for:
 |  | ||||||
|     /// 1.  **Checking Current State:** Assessing if the required resources/services are already running and configured.
 |  | ||||||
|     /// 2.  **Discovery:** Identifying the runtime environment (e.g., local Docker, AWS, existing cluster).
 |  | ||||||
|     /// 3.  **Initialization/Bootstrapping:** Performing necessary setup actions if not already ready. This might involve:
 |  | ||||||
|     ///     *   Making API calls.
 |  | ||||||
|     ///     *   Running external commands (e.g., `k3d`, `docker`).
 |  | ||||||
|     ///     *   **Internal Orchestration:** For complex topologies, this method might manage dependencies on other sub-topologies, ensuring *their* `ensure_ready` is called first. Using nested `Maestros` to run setup `Scores` against these sub-topologies is the recommended pattern for non-trivial bootstrapping, allowing reuse of Harmony's core orchestration logic.
 |  | ||||||
|     ///
 |  | ||||||
|     /// # Returns
 |  | ||||||
|     /// - `Ok(Outcome)`: Indicates the topology is now ready. The `Outcome` status might be `SUCCESS` if actions were taken, or `NOOP` if it was already ready. The message should provide context.
 |  | ||||||
|     /// - `Err(TopologyError)`: Indicates the topology could not reach a ready state due to configuration issues, discovery failures, bootstrap errors, or unsupported environments.
 |  | ||||||
|     async fn ensure_ready(&self) -> Result<Outcome, InterpretError>; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub type IpAddress = IpAddr; | pub type IpAddress = IpAddr; | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ use serde::Serialize; | |||||||
| 
 | 
 | ||||||
| use crate::executors::ExecutorError; | use crate::executors::ExecutorError; | ||||||
| 
 | 
 | ||||||
| use super::{IpAddress, LogicalHost, k8s::K8sClient}; | use super::{IpAddress, LogicalHost, openshift::OpenshiftClient}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct DHCPStaticEntry { | pub struct DHCPStaticEntry { | ||||||
| @ -42,8 +42,8 @@ pub struct NetworkDomain { | |||||||
|     pub name: String, |     pub name: String, | ||||||
| } | } | ||||||
| #[async_trait] | #[async_trait] | ||||||
| pub trait K8sclient: Send + Sync + std::fmt::Debug { | pub trait OcK8sclient: Send + Sync + std::fmt::Debug { | ||||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error>; |     async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
|  | |||||||
| @ -2,11 +2,11 @@ use k8s_openapi::NamespaceResourceScope; | |||||||
| use kube::{Api, Client, Error, Resource, api::PostParams}; | use kube::{Api, Client, Error, Resource, api::PostParams}; | ||||||
| use serde::de::DeserializeOwned; | use serde::de::DeserializeOwned; | ||||||
| 
 | 
 | ||||||
| pub struct K8sClient { | pub struct OpenshiftClient { | ||||||
|     client: Client, |     client: Client, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl K8sClient { | impl OpenshiftClient { | ||||||
|     pub async fn try_default() -> Result<Self, Error> { |     pub async fn try_default() -> Result<Self, Error> { | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|             client: Client::try_default().await?, |             client: Client::try_default().await?, | ||||||
| @ -1,64 +0,0 @@ | |||||||
| use async_trait::async_trait; |  | ||||||
| use serde::Serialize; |  | ||||||
| 
 |  | ||||||
| use crate::{ |  | ||||||
|     data::{Id, Version}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |  | ||||||
|     inventory::Inventory, |  | ||||||
|     score::Score, |  | ||||||
|     topology::Topology, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, Serialize)] |  | ||||||
| pub struct K3DInstallationScore {} |  | ||||||
| 
 |  | ||||||
| impl K3DInstallationScore { |  | ||||||
|     pub fn new() -> Self { |  | ||||||
|         Self {} |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl<T: Topology> Score<T> for K3DInstallationScore { |  | ||||||
|     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { |  | ||||||
|         todo!(" |  | ||||||
|         1. Decide if I create a new crate for k3d management, especially to avoid the ocrtograb dependency |  | ||||||
|         2. Implement k3d management |  | ||||||
|         3. Find latest tag |  | ||||||
|         4. Download k3d to some path managed by harmony (or not?) |  | ||||||
|         5. Bootstrap cluster |  | ||||||
|         6. Get kubeconfig |  | ||||||
|         7. Load kubeconfig in k8s anywhere |  | ||||||
|         8. Complete k8sanywhere setup |  | ||||||
|         ")
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn name(&self) -> String { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug)] |  | ||||||
| struct K3dInstallationInterpret {} |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl<T: Topology> Interpret<T> for K3dInstallationInterpret { |  | ||||||
|     async fn execute( |  | ||||||
|         &self, |  | ||||||
|         inventory: &Inventory, |  | ||||||
|         topology: &T, |  | ||||||
|     ) -> Result<Outcome, InterpretError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
|     fn get_name(&self) -> InterpretName { |  | ||||||
|         InterpretName::K3dInstallation |  | ||||||
|     } |  | ||||||
|     fn get_version(&self) -> Version { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
|     fn get_status(&self) -> InterpretStatus { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
|     fn get_children(&self) -> Vec<Id> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,2 +0,0 @@ | |||||||
| mod install; |  | ||||||
| pub use install::*; |  | ||||||
| @ -5,7 +5,7 @@ use serde_json::json; | |||||||
| use crate::{ | use crate::{ | ||||||
|     interpret::Interpret, |     interpret::Interpret, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{K8sclient, Topology}, |     topology::{OcK8sclient, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::resource::{K8sResourceInterpret, K8sResourceScore}; | use super::resource::{K8sResourceInterpret, K8sResourceScore}; | ||||||
| @ -16,7 +16,7 @@ pub struct K8sDeploymentScore { | |||||||
|     pub image: String, |     pub image: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore { | impl<T: Topology + OcK8sclient> Score<T> for K8sDeploymentScore { | ||||||
|     fn create_interpret(&self) -> Box<dyn Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|         let deployment: Deployment = serde_json::from_value(json!( |         let deployment: Deployment = serde_json::from_value(json!( | ||||||
|             { |             { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ use crate::{ | |||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{K8sclient, Topology}, |     topology::{OcK8sclient, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| @ -63,7 +63,7 @@ impl< | |||||||
|         + Default |         + Default | ||||||
|         + Send |         + Send | ||||||
|         + Sync, |         + Sync, | ||||||
|     T: Topology + K8sclient, |     T: Topology + OcK8sclient, | ||||||
| > Interpret<T> for K8sResourceInterpret<K> | > Interpret<T> for K8sResourceInterpret<K> | ||||||
| where | where | ||||||
|     <K as kube::Resource>::DynamicType: Default, |     <K as kube::Resource>::DynamicType: Default, | ||||||
| @ -74,7 +74,7 @@ where | |||||||
|         topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         topology |         topology | ||||||
|             .k8s_client() |             .oc_client() | ||||||
|             .await |             .await | ||||||
|             .expect("Environment should provide enough information to instanciate a client") |             .expect("Environment should provide enough information to instanciate a client") | ||||||
|             .apply_namespaced(&self.score.resource) |             .apply_namespaced(&self.score.resource) | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ use crate::{ | |||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::k8s::deployment::K8sDeploymentScore, |     modules::k8s::deployment::K8sDeploymentScore, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{K8sclient, Topology, Url}, |     topology::{OcK8sclient, Topology, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| @ -51,7 +51,7 @@ pub struct LAMPInterpret { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | impl<T: Topology + OcK8sclient> Interpret<T> for LAMPInterpret { | ||||||
|     async fn execute( |     async fn execute( | ||||||
|         &self, |         &self, | ||||||
|         inventory: &Inventory, |         inventory: &Inventory, | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ pub mod dhcp; | |||||||
| pub mod dns; | pub mod dns; | ||||||
| pub mod dummy; | pub mod dummy; | ||||||
| pub mod http; | pub mod http; | ||||||
| pub mod k3d; |  | ||||||
| pub mod k8s; | pub mod k8s; | ||||||
| pub mod lamp; | pub mod lamp; | ||||||
| pub mod load_balancer; | pub mod load_balancer; | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ assert_cmd = "2.0.17" | |||||||
| clap = { version = "4.5.35", features = ["derive"] } | clap = { version = "4.5.35", features = ["derive"] } | ||||||
| harmony = { path = "../harmony" } | harmony = { path = "../harmony" } | ||||||
| harmony_tui = { path = "../harmony_tui", optional = true } | harmony_tui = { path = "../harmony_tui", optional = true } | ||||||
| inquire.workspace = true | inquire = "0.7.5" | ||||||
| tokio.workspace = true | tokio.workspace = true | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ pub mod tui { | |||||||
| ///     harmony_tui::init(maestro).await.unwrap();
 | ///     harmony_tui::init(maestro).await.unwrap();
 | ||||||
| /// }
 | /// }
 | ||||||
| /// ```
 | /// ```
 | ||||||
| pub async fn init<T: Topology + Send + Sync + 'static>( | pub async fn init<T: Topology + std::fmt::Debug + Send + Sync + 'static>( | ||||||
|     maestro: Maestro<T>, |     maestro: Maestro<T>, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     HarmonyTUI::new(maestro).init().await |     HarmonyTUI::new(maestro).init().await | ||||||
| @ -63,21 +63,12 @@ pub struct HarmonyTUI<T: Topology> { | |||||||
|     tui_state: TuiWidgetState, |     tui_state: TuiWidgetState, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug)] | ||||||
| enum HarmonyTuiEvent<T: Topology> { | enum HarmonyTuiEvent<T: Topology> { | ||||||
|     LaunchScore(Box<dyn Score<T>>), |     LaunchScore(Box<dyn Score<T>>), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> std::fmt::Display for HarmonyTuiEvent<T> { | impl<T: Topology + std::fmt::Debug + Send + Sync + 'static> HarmonyTUI<T> { | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         let output = match self { |  | ||||||
|             HarmonyTuiEvent::LaunchScore(score) => format!("LaunchScore({})", score.name()), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         f.write_str(&output) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl<T: Topology + Send + Sync + 'static> HarmonyTUI<T> { |  | ||||||
|     pub fn new(maestro: Maestro<T>) -> Self { |     pub fn new(maestro: Maestro<T>) -> Self { | ||||||
|         let maestro = Arc::new(maestro); |         let maestro = Arc::new(maestro); | ||||||
|         let (_handle, sender) = Self::start_channel(maestro.clone()); |         let (_handle, sender) = Self::start_channel(maestro.clone()); | ||||||
| @ -100,7 +91,7 @@ impl<T: Topology + Send + Sync + 'static> HarmonyTUI<T> { | |||||||
|         let handle = tokio::spawn(async move { |         let handle = tokio::spawn(async move { | ||||||
|             info!("Starting message channel receiver loop"); |             info!("Starting message channel receiver loop"); | ||||||
|             while let Some(event) = receiver.recv().await { |             while let Some(event) = receiver.recv().await { | ||||||
|                 info!("Received event {event}"); |                 info!("Received event {event:#?}"); | ||||||
|                 match event { |                 match event { | ||||||
|                     HarmonyTuiEvent::LaunchScore(score_item) => { |                     HarmonyTuiEvent::LaunchScore(score_item) => { | ||||||
|                         let maestro = maestro.clone(); |                         let maestro = maestro.clone(); | ||||||
|  | |||||||
| @ -19,21 +19,13 @@ enum ExecutionState { | |||||||
|     CANCELED, |     CANCELED, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug)] | ||||||
| struct Execution<T: Topology> { | struct Execution<T: Topology> { | ||||||
|     state: ExecutionState, |     state: ExecutionState, | ||||||
|     score: Box<dyn Score<T>>, |     score: Box<dyn Score<T>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> std::fmt::Display for Execution<T> { | #[derive(Debug)] | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         f.write_fmt(format_args!( |  | ||||||
|             "Execution of {} status {:?}", |  | ||||||
|             self.score.name(), |  | ||||||
|             self.state |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub(crate) struct ScoreListWidget<T: Topology> { | pub(crate) struct ScoreListWidget<T: Topology> { | ||||||
|     list_state: Arc<RwLock<ListState>>, |     list_state: Arc<RwLock<ListState>>, | ||||||
|     scores: Vec<Box<dyn Score<T>>>, |     scores: Vec<Box<dyn Score<T>>>, | ||||||
| @ -42,7 +34,7 @@ pub(crate) struct ScoreListWidget<T: Topology> { | |||||||
|     sender: mpsc::Sender<HarmonyTuiEvent<T>>, |     sender: mpsc::Sender<HarmonyTuiEvent<T>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> ScoreListWidget<T> { | impl<T: Topology + std::fmt::Debug> ScoreListWidget<T> { | ||||||
|     pub(crate) fn new( |     pub(crate) fn new( | ||||||
|         scores: Vec<Box<dyn Score<T>>>, |         scores: Vec<Box<dyn Score<T>>>, | ||||||
|         sender: mpsc::Sender<HarmonyTuiEvent<T>>, |         sender: mpsc::Sender<HarmonyTuiEvent<T>>, | ||||||
| @ -107,7 +99,7 @@ impl<T: Topology> ScoreListWidget<T> { | |||||||
|             match confirm { |             match confirm { | ||||||
|                 true => { |                 true => { | ||||||
|                     execution.state = ExecutionState::RUNNING; |                     execution.state = ExecutionState::RUNNING; | ||||||
|                     info!("Launch execution {execution}"); |                     info!("Launch execution {:?}", execution); | ||||||
|                     self.sender |                     self.sender | ||||||
|                         .send(HarmonyTuiEvent::LaunchScore(execution.score.clone_box())) |                         .send(HarmonyTuiEvent::LaunchScore(execution.score.clone_box())) | ||||||
|                         .await |                         .await | ||||||
|  | |||||||
| @ -1,22 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "k3d-rs" |  | ||||||
| edition = "2021" |  | ||||||
| version.workspace = true |  | ||||||
| readme.workspace = true |  | ||||||
| license.workspace = true |  | ||||||
| 
 |  | ||||||
| [dependencies] |  | ||||||
| log = { workspace = true } |  | ||||||
| async-trait = { workspace = true } |  | ||||||
| tokio = { workspace = true } |  | ||||||
| octocrab = "0.44.0" |  | ||||||
| regex = "1.11.1" |  | ||||||
| reqwest = { version = "0.12", features = ["stream"]  } |  | ||||||
| url.workspace = true |  | ||||||
| sha2 = "0.10.8" |  | ||||||
| futures-util = "0.3.31" |  | ||||||
| 
 |  | ||||||
| [dev-dependencies] |  | ||||||
| env_logger = { workspace = true } |  | ||||||
| httptest = "0.16.3" |  | ||||||
| pretty_assertions = "1.4.1" |  | ||||||
| @ -1,303 +0,0 @@ | |||||||
| use futures_util::StreamExt; |  | ||||||
| use log::{debug, info, warn}; |  | ||||||
| use sha2::{Digest, Sha256}; |  | ||||||
| use std::io::Read; |  | ||||||
| use std::path::PathBuf; |  | ||||||
| use tokio::fs; |  | ||||||
| use tokio::fs::File; |  | ||||||
| use tokio::io::AsyncWriteExt; |  | ||||||
| use url::Url; |  | ||||||
| 
 |  | ||||||
| const CHECKSUM_FAILED_MSG: &str = "Downloaded file failed checksum verification"; |  | ||||||
| 
 |  | ||||||
| /// Represents an asset that can be downloaded from a URL with checksum verification.
 |  | ||||||
| ///
 |  | ||||||
| /// This struct facilitates secure downloading of files from remote URLs by
 |  | ||||||
| /// verifying the integrity of the downloaded content using SHA-256 checksums.
 |  | ||||||
| /// It handles downloading the file, saving it to disk, and verifying the checksum matches
 |  | ||||||
| /// the expected value.
 |  | ||||||
| ///
 |  | ||||||
| /// # Examples
 |  | ||||||
| ///
 |  | ||||||
| /// ```compile_fail
 |  | ||||||
| /// # use url::Url;
 |  | ||||||
| /// # use std::path::PathBuf;
 |  | ||||||
| ///
 |  | ||||||
| /// # async fn example() -> Result<(), String> {
 |  | ||||||
| /// let asset = DownloadableAsset {
 |  | ||||||
| ///     url: Url::parse("https://example.com/file.zip").unwrap(),
 |  | ||||||
| ///     file_name: "file.zip".to_string(),
 |  | ||||||
| ///     checksum: "a1b2c3d4e5f6...".to_string(),
 |  | ||||||
| /// };
 |  | ||||||
| ///
 |  | ||||||
| /// let download_dir = PathBuf::from("/tmp/downloads");
 |  | ||||||
| /// let file_path = asset.download_to_path(download_dir).await?;
 |  | ||||||
| /// # Ok(())
 |  | ||||||
| /// # }
 |  | ||||||
| /// ```
 |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub(crate) struct DownloadableAsset { |  | ||||||
|     pub(crate) url: Url, |  | ||||||
|     pub(crate) file_name: String, |  | ||||||
|     pub(crate) checksum: String, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl DownloadableAsset { |  | ||||||
|     fn verify_checksum(&self, file: PathBuf) -> bool { |  | ||||||
|         if !file.exists() { |  | ||||||
|             warn!("File does not exist: {:?}", file); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let mut file = match std::fs::File::open(&file) { |  | ||||||
|             Ok(file) => file, |  | ||||||
|             Err(e) => { |  | ||||||
|                 warn!("Failed to open file for checksum verification: {:?}", e); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let mut hasher = Sha256::new(); |  | ||||||
|         let mut buffer = [0; 1024 * 1024]; // 1MB buffer
 |  | ||||||
| 
 |  | ||||||
|         loop { |  | ||||||
|             let bytes_read = match file.read(&mut buffer) { |  | ||||||
|                 Ok(0) => break, |  | ||||||
|                 Ok(n) => n, |  | ||||||
|                 Err(e) => { |  | ||||||
|                     warn!("Error reading file for checksum: {:?}", e); |  | ||||||
|                     return false; |  | ||||||
|                 } |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             hasher.update(&buffer[..bytes_read]); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let result = hasher.finalize(); |  | ||||||
|         let calculated_hash = format!("{:x}", result); |  | ||||||
| 
 |  | ||||||
|         debug!("Expected checksum: {}", self.checksum); |  | ||||||
|         debug!("Calculated checksum: {}", calculated_hash); |  | ||||||
| 
 |  | ||||||
|         calculated_hash == self.checksum |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Downloads the asset to the specified directory, verifying its checksum.
 |  | ||||||
|     ///
 |  | ||||||
|     /// This function will:
 |  | ||||||
|     /// 1. Create the target directory if it doesn't exist
 |  | ||||||
|     /// 2. Check if the file already exists with the correct checksum
 |  | ||||||
|     /// 3. If not, download the file from the URL
 |  | ||||||
|     /// 4. Verify the downloaded file's checksum matches the expected value
 |  | ||||||
|     ///
 |  | ||||||
|     /// # Arguments
 |  | ||||||
|     ///
 |  | ||||||
|     /// * `folder` - The directory path where the file should be saved
 |  | ||||||
|     ///
 |  | ||||||
|     /// # Returns
 |  | ||||||
|     ///
 |  | ||||||
|     /// * `Ok(PathBuf)` - The path to the downloaded file on success
 |  | ||||||
|     /// * `Err(String)` - A descriptive error message if the download or verification fails
 |  | ||||||
|     ///
 |  | ||||||
|     /// # Errors
 |  | ||||||
|     ///
 |  | ||||||
|     /// This function will return an error if:
 |  | ||||||
|     /// - The network request fails
 |  | ||||||
|     /// - The server responds with a non-success status code
 |  | ||||||
|     /// - Writing to disk fails
 |  | ||||||
|     /// - The checksum verification fails
 |  | ||||||
|     pub(crate) async fn download_to_path(&self, folder: PathBuf) -> Result<PathBuf, String> { |  | ||||||
|         if !folder.exists() { |  | ||||||
|             fs::create_dir_all(&folder) |  | ||||||
|                 .await |  | ||||||
|                 .expect("Failed to create download directory"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let target_file_path = folder.join(&self.file_name); |  | ||||||
|         debug!("Downloading to path: {:?}", target_file_path); |  | ||||||
| 
 |  | ||||||
|         if self.verify_checksum(target_file_path.clone()) { |  | ||||||
|             debug!("File already exists with correct checksum, skipping download"); |  | ||||||
|             return Ok(target_file_path); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         debug!("Downloading from URL: {}", self.url); |  | ||||||
|         let client = reqwest::Client::new(); |  | ||||||
|         let response = client |  | ||||||
|             .get(self.url.clone()) |  | ||||||
|             .send() |  | ||||||
|             .await |  | ||||||
|             .map_err(|e| format!("Failed to download file: {e}"))?; |  | ||||||
| 
 |  | ||||||
|         if !response.status().is_success() { |  | ||||||
|             return Err(format!( |  | ||||||
|                 "Failed to download file, status: {}", |  | ||||||
|                 response.status() |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let mut file = File::create(&target_file_path) |  | ||||||
|             .await |  | ||||||
|             .expect("Failed to create target file"); |  | ||||||
| 
 |  | ||||||
|         let mut stream = response.bytes_stream(); |  | ||||||
|         while let Some(chunk_result) = stream.next().await { |  | ||||||
|             let chunk = chunk_result.expect("Error while downloading file"); |  | ||||||
|             file.write_all(&chunk) |  | ||||||
|                 .await |  | ||||||
|                 .expect("Failed to write data to file"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         file.flush().await.expect("Failed to flush file"); |  | ||||||
|         drop(file); |  | ||||||
| 
 |  | ||||||
|         if !self.verify_checksum(target_file_path.clone()) { |  | ||||||
|             return Err(CHECKSUM_FAILED_MSG.to_string()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         info!( |  | ||||||
|             "File downloaded and verified successfully: {}", |  | ||||||
|             target_file_path.to_string_lossy() |  | ||||||
|         ); |  | ||||||
|         Ok(target_file_path) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use httptest::{ |  | ||||||
|         matchers::{self, request}, |  | ||||||
|         responders, Expectation, Server, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const BASE_TEST_PATH: &str = "/tmp/harmony-test-k3d-download"; |  | ||||||
|     const TEST_CONTENT: &str = "This is a test file."; |  | ||||||
|     const TEST_CONTENT_HASH: &str = |  | ||||||
|         "f29bc64a9d3732b4b9035125fdb3285f5b6455778edca72414671e0ca3b2e0de"; |  | ||||||
| 
 |  | ||||||
|     fn setup_test() -> (PathBuf, Server) { |  | ||||||
|         let _ = env_logger::builder().try_init(); |  | ||||||
| 
 |  | ||||||
|         // Create unique test directory
 |  | ||||||
|         let test_id = std::time::SystemTime::now() |  | ||||||
|             .duration_since(std::time::UNIX_EPOCH) |  | ||||||
|             .unwrap() |  | ||||||
|             .as_millis(); |  | ||||||
|         let download_path = format!("{}/test_{}", BASE_TEST_PATH, test_id); |  | ||||||
|         std::fs::create_dir_all(&download_path).unwrap(); |  | ||||||
| 
 |  | ||||||
|         (PathBuf::from(download_path), Server::run()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn test_download_to_path_success() { |  | ||||||
|         let (folder, server) = setup_test(); |  | ||||||
| 
 |  | ||||||
|         server.expect( |  | ||||||
|             Expectation::matching(request::method_path("GET", "/test.txt")) |  | ||||||
|                 .respond_with(responders::status_code(200).body(TEST_CONTENT)), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         let asset = DownloadableAsset { |  | ||||||
|             url: Url::parse(&server.url("/test.txt").to_string()).unwrap(), |  | ||||||
|             file_name: "test.txt".to_string(), |  | ||||||
|             checksum: TEST_CONTENT_HASH.to_string(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let result = asset |  | ||||||
|             .download_to_path(folder.join("success")) |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
|         let downloaded_content = std::fs::read_to_string(result).unwrap(); |  | ||||||
|         assert_eq!(downloaded_content, TEST_CONTENT); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn test_download_to_path_already_exists() { |  | ||||||
|         let (folder, server) = setup_test(); |  | ||||||
| 
 |  | ||||||
|         server.expect( |  | ||||||
|             Expectation::matching(matchers::any()) |  | ||||||
|                 .times(0) |  | ||||||
|                 .respond_with(responders::status_code(200).body(TEST_CONTENT)), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         let asset = DownloadableAsset { |  | ||||||
|             url: Url::parse(&server.url("/test.txt").to_string()).unwrap(), |  | ||||||
|             file_name: "test.txt".to_string(), |  | ||||||
|             checksum: TEST_CONTENT_HASH.to_string(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let target_file_path = folder.join(&asset.file_name); |  | ||||||
|         std::fs::write(&target_file_path, TEST_CONTENT).unwrap(); |  | ||||||
| 
 |  | ||||||
|         let result = asset.download_to_path(folder).await.unwrap(); |  | ||||||
|         let content = std::fs::read_to_string(result).unwrap(); |  | ||||||
|         assert_eq!(content, TEST_CONTENT); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn test_download_to_path_server_error() { |  | ||||||
|         let (folder, server) = setup_test(); |  | ||||||
| 
 |  | ||||||
|         server.expect( |  | ||||||
|             Expectation::matching(matchers::any()).respond_with(responders::status_code(404)), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         let asset = DownloadableAsset { |  | ||||||
|             url: Url::parse(&server.url("/test.txt").to_string()).unwrap(), |  | ||||||
|             file_name: "test.txt".to_string(), |  | ||||||
|             checksum: TEST_CONTENT_HASH.to_string(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let result = asset.download_to_path(folder.join("error")).await; |  | ||||||
|         assert!(result.is_err()); |  | ||||||
|         assert!(result.unwrap_err().contains("status: 404")); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn test_download_to_path_checksum_failure() { |  | ||||||
|         let (folder, server) = setup_test(); |  | ||||||
| 
 |  | ||||||
|         let invalid_content = "This is NOT the expected content"; |  | ||||||
|         server.expect( |  | ||||||
|             Expectation::matching(matchers::any()) |  | ||||||
|                 .respond_with(responders::status_code(200).body(invalid_content)), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         let asset = DownloadableAsset { |  | ||||||
|             url: Url::parse(&server.url("/test.txt").to_string()).unwrap(), |  | ||||||
|             file_name: "test.txt".to_string(), |  | ||||||
|             checksum: TEST_CONTENT_HASH.to_string(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let join_handle = |  | ||||||
|             tokio::spawn(async move { asset.download_to_path(folder.join("failure")).await }); |  | ||||||
| 
 |  | ||||||
|         assert_eq!( |  | ||||||
|             join_handle.await.unwrap().err().unwrap(), |  | ||||||
|             CHECKSUM_FAILED_MSG |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn test_download_with_specific_path_matcher() { |  | ||||||
|         let (folder, server) = setup_test(); |  | ||||||
| 
 |  | ||||||
|         server.expect( |  | ||||||
|             Expectation::matching(matchers::request::path("/specific/path.txt")) |  | ||||||
|                 .respond_with(responders::status_code(200).body(TEST_CONTENT)), |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         let asset = DownloadableAsset { |  | ||||||
|             url: Url::parse(&server.url("/specific/path.txt").to_string()).unwrap(), |  | ||||||
|             file_name: "path.txt".to_string(), |  | ||||||
|             checksum: TEST_CONTENT_HASH.to_string(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let result = asset.download_to_path(folder).await.unwrap(); |  | ||||||
|         let downloaded_content = std::fs::read_to_string(result).unwrap(); |  | ||||||
|         assert_eq!(downloaded_content, TEST_CONTENT); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										164
									
								
								k3d/src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								k3d/src/lib.rs
									
									
									
									
									
								
							| @ -1,164 +0,0 @@ | |||||||
| mod downloadable_asset; |  | ||||||
| use downloadable_asset::*; |  | ||||||
| 
 |  | ||||||
| use log::{debug, info}; |  | ||||||
| use std::path::PathBuf; |  | ||||||
| 
 |  | ||||||
| const K3D_BIN_FILE_NAME: &str = "k3d"; |  | ||||||
| 
 |  | ||||||
| pub struct K3d { |  | ||||||
|     base_dir: PathBuf, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl K3d { |  | ||||||
|     pub fn new(base_dir: PathBuf) -> Self { |  | ||||||
|         Self { base_dir } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn get_binary_for_current_platform( |  | ||||||
|         &self, |  | ||||||
|         latest_release: octocrab::models::repos::Release, |  | ||||||
|     ) -> DownloadableAsset { |  | ||||||
|         let os = std::env::consts::OS; |  | ||||||
|         let arch = std::env::consts::ARCH; |  | ||||||
| 
 |  | ||||||
|         debug!("Detecting platform: OS={}, ARCH={}", os, arch); |  | ||||||
| 
 |  | ||||||
|         // 2. Construct the binary name pattern based on platform
 |  | ||||||
|         let binary_pattern = match (os, arch) { |  | ||||||
|             ("linux", "x86") => "k3d-linux-386", |  | ||||||
|             ("linux", "x86_64") => "k3d-linux-amd64", |  | ||||||
|             ("linux", "arm") => "k3d-linux-arm", |  | ||||||
|             ("linux", "aarch64") => "k3d-linux-arm64", |  | ||||||
|             ("windows", "x86_64") => "k3d-windows-amd64.exe", |  | ||||||
|             ("macos", "x86_64") => "k3d-darwin-amd64", |  | ||||||
|             ("macos", "aarch64") => "k3d-darwin-arm64", |  | ||||||
|             _ => panic!("Unsupported platform: {}-{}", os, arch), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         debug!("Looking for binary matching pattern: {}", binary_pattern); |  | ||||||
| 
 |  | ||||||
|         // 3. Find the matching binary in release assets
 |  | ||||||
|         let binary_asset = latest_release |  | ||||||
|             .assets |  | ||||||
|             .iter() |  | ||||||
|             .find(|asset| asset.name == binary_pattern) |  | ||||||
|             .unwrap_or_else(|| panic!("No matching binary found for {}", binary_pattern)); |  | ||||||
| 
 |  | ||||||
|         let binary_url = binary_asset.browser_download_url.clone(); |  | ||||||
| 
 |  | ||||||
|         // 4. Find and parse the checksums file
 |  | ||||||
|         let checksums_asset = latest_release |  | ||||||
|             .assets |  | ||||||
|             .iter() |  | ||||||
|             .find(|asset| asset.name == "checksums.txt") |  | ||||||
|             .expect("Checksums file not found in release assets"); |  | ||||||
| 
 |  | ||||||
|         // 5. Download and parse checksums file
 |  | ||||||
|         let checksums_url = checksums_asset.browser_download_url.clone(); |  | ||||||
| 
 |  | ||||||
|         let body = reqwest::get(checksums_url) |  | ||||||
|             .await |  | ||||||
|             .unwrap() |  | ||||||
|             .text() |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
|         println!("body: {body}"); |  | ||||||
| 
 |  | ||||||
|         // 6. Find the checksum for our binary
 |  | ||||||
|         let checksum = body |  | ||||||
|             .lines() |  | ||||||
|             .find_map(|line| { |  | ||||||
|                 if line.ends_with(&binary_pattern) { |  | ||||||
|                     Some(line.split_whitespace().next().unwrap_or("").to_string()) |  | ||||||
|                 } else { |  | ||||||
|                     None |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|             .unwrap_or_else(|| panic!("Checksum not found for {}", binary_pattern)); |  | ||||||
| 
 |  | ||||||
|         debug!("Found binary at {} with checksum {}", binary_url, checksum); |  | ||||||
| 
 |  | ||||||
|         DownloadableAsset { |  | ||||||
|             url: binary_url, |  | ||||||
|             file_name: K3D_BIN_FILE_NAME.to_string(), |  | ||||||
|             checksum, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn download_latest_release(&self) -> Result<PathBuf, String> { |  | ||||||
|         let latest_release = self.get_latest_release_tag().await.unwrap(); |  | ||||||
| 
 |  | ||||||
|         let release_binary = self.get_binary_for_current_platform(latest_release).await; |  | ||||||
|         info!("Foudn K3d binary to install : {release_binary:#?}"); |  | ||||||
|         release_binary.download_to_path(self.base_dir.clone()).await |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // TODO : Make sure this will only find actual released versions, no prereleases or test
 |  | ||||||
|     // builds
 |  | ||||||
|     pub async fn get_latest_release_tag(&self) -> Result<octocrab::models::repos::Release, String> { |  | ||||||
|         let octo = octocrab::instance(); |  | ||||||
|         let latest_release = octo |  | ||||||
|             .repos("k3d-io", "k3d") |  | ||||||
|             .releases() |  | ||||||
|             .get_latest() |  | ||||||
|             .await |  | ||||||
|             .map_err(|e| e.to_string())?; |  | ||||||
|         // debug!("Got k3d releases {releases:#?}");
 |  | ||||||
|         println!("Got k3d first releases {latest_release:#?}"); |  | ||||||
| 
 |  | ||||||
|         Ok(latest_release) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use regex::Regex; |  | ||||||
|     use std::path::PathBuf; |  | ||||||
| 
 |  | ||||||
|     use crate::{K3d, K3D_BIN_FILE_NAME}; |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn k3d_latest_release_should_get_latest() { |  | ||||||
|         let dir = get_clean_test_directory(); |  | ||||||
| 
 |  | ||||||
|         assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false); |  | ||||||
| 
 |  | ||||||
|         let k3d = K3d::new(dir.clone()); |  | ||||||
|         let latest_release = k3d.get_latest_release_tag().await.unwrap(); |  | ||||||
| 
 |  | ||||||
|         let tag_regex = Regex::new(r"^v\d+\.\d+\.\d+$").unwrap(); |  | ||||||
|         assert!(tag_regex.is_match(&latest_release.tag_name)); |  | ||||||
|         assert!(!latest_release.tag_name.is_empty()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[tokio::test] |  | ||||||
|     async fn k3d_download_latest_release_should_get_latest_bin() { |  | ||||||
|         let dir = get_clean_test_directory(); |  | ||||||
| 
 |  | ||||||
|         assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false); |  | ||||||
| 
 |  | ||||||
|         let k3d = K3d::new(dir.clone()); |  | ||||||
|         let bin_file_path = k3d.download_latest_release().await.unwrap(); |  | ||||||
|         assert_eq!(bin_file_path, dir.join(K3D_BIN_FILE_NAME)); |  | ||||||
|         assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_clean_test_directory() -> PathBuf { |  | ||||||
|         let dir = PathBuf::from("/tmp/harmony-k3d-test-dir"); |  | ||||||
| 
 |  | ||||||
|         if dir.exists() { |  | ||||||
|             if let Err(e) = std::fs::remove_dir_all(&dir) { |  | ||||||
|                 // TODO sometimes this fails because of the race when running multiple tests at
 |  | ||||||
|                 // once
 |  | ||||||
|                 panic!("Failed to clean up test directory: {}", e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if let Err(e) = std::fs::create_dir_all(&dir) { |  | ||||||
|             panic!("Failed to create test directory: {}", e); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         dir |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user