From 1adf5dfe3ad031a6efc466cef93fb0273cfb60e6 Mon Sep 17 00:00:00 2001 From: Tomoki_sunzj Date: Fri, 6 Sep 2024 18:16:48 +0800 Subject: [PATCH] feat: add kbimg-rs --- .gitignore | 3 + KubeOS-Rust/Cargo.lock | 945 +++++---- KubeOS-Rust/Cargo.toml | 2 +- KubeOS-Rust/kbimg/Cargo.toml | 16 + KubeOS-Rust/kbimg/kbimg.toml | 87 + KubeOS-Rust/kbimg/src/admin_container.rs | 84 + KubeOS-Rust/kbimg/src/commands.rs | 170 ++ KubeOS-Rust/kbimg/src/docker_img.rs | 74 + KubeOS-Rust/kbimg/src/main.rs | 122 ++ KubeOS-Rust/kbimg/src/repo.rs | 203 ++ KubeOS-Rust/kbimg/src/scripts_gen.rs | 1754 +++++++++++++++++ KubeOS-Rust/kbimg/src/utils.rs | 126 ++ KubeOS-Rust/kbimg/src/values.rs | 42 + KubeOS-Rust/proxy/src/controller/apiclient.rs | 4 +- .../proxy/src/controller/apiserver_mock.rs | 9 +- .../proxy/src/controller/controller.rs | 33 +- KubeOS-Rust/proxy/src/controller/crd.rs | 17 + KubeOS-Rust/proxy/src/controller/values.rs | 4 +- Makefile | 13 +- api/v1alpha1/os_types.go | 33 +- cmd/operator/controllers/operation.go | 282 +++ cmd/operator/controllers/os_controller.go | 364 ++-- .../controllers/os_controller_test.go | 550 ++++-- cmd/operator/controllers/requirements.go | 199 ++ cmd/operator/controllers/times.go | 106 + cmd/operator/main.go | 13 +- cmd/proxy/controllers/os_controller.go | 7 + .../admin-container/admin-container.yaml | 126 +- .../config/crd/upgrade.openeuler.org_os.yaml | 30 + .../upgrade.openeuler.org_osinstances.yaml | 8 + docs/example/config/manager/manager.yaml | 65 +- .../config/samples/upgrade_v1alpha1_os.yaml | 68 +- docs/quick-start.md | 157 +- ...66\344\275\234\346\214\207\345\257\274.md" | 385 +++- pkg/values/values.go | 11 +- 35 files changed, 5225 insertions(+), 887 deletions(-) create mode 100644 KubeOS-Rust/kbimg/Cargo.toml create mode 100644 KubeOS-Rust/kbimg/kbimg.toml create mode 100644 KubeOS-Rust/kbimg/src/admin_container.rs create mode 100644 KubeOS-Rust/kbimg/src/commands.rs create mode 100644 KubeOS-Rust/kbimg/src/docker_img.rs create mode 100644 KubeOS-Rust/kbimg/src/main.rs create mode 100644 KubeOS-Rust/kbimg/src/repo.rs create mode 100644 KubeOS-Rust/kbimg/src/scripts_gen.rs create mode 100644 KubeOS-Rust/kbimg/src/utils.rs create mode 100644 KubeOS-Rust/kbimg/src/values.rs create mode 100644 cmd/operator/controllers/operation.go create mode 100644 cmd/operator/controllers/requirements.go create mode 100644 cmd/operator/controllers/times.go diff --git a/.gitignore b/.gitignore index 4d173c5e..0cac753e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ target/ # KubeOS bin /bin + +# scripts-auto +/scripts-auto diff --git a/KubeOS-Rust/Cargo.lock b/KubeOS-Rust/Cargo.lock index 93e3d07d..64a728b5 100644 --- a/KubeOS-Rust/Cargo.lock +++ b/KubeOS-Rust/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "assert-json-diff" @@ -61,20 +61,26 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -88,9 +94,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backoff" @@ -98,7 +104,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.15", "instant", "rand 0.8.5", ] @@ -111,9 +117,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -123,9 +129,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -138,32 +144,39 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "bytes" +name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.0.83" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -174,9 +187,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -184,7 +197,46 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive", + "clap_lex", + "indexmap 1.9.3", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -201,9 +253,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -211,19 +263,44 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -335,21 +412,21 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -375,33 +452,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "float-cmp" @@ -435,9 +498,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -450,9 +513,9 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -465,9 +528,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -475,15 +538,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -492,38 +555,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -560,9 +623,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -571,9 +634,9 @@ dependencies = [ [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -593,30 +656,30 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.9", + "http 0.2.12", "indexmap 1.9.3", "slab", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.11", "tracing", ] [[package]] name = "h2" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http 1.1.0", - "indexmap 2.2.6", + "indexmap 2.5.0", "slab", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.11", "tracing", ] @@ -628,9 +691,15 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -643,15 +712,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -671,20 +740,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.9", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -692,14 +761,14 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -711,9 +780,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -729,21 +798,21 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -752,16 +821,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.3", + "h2 0.4.6", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -778,7 +847,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.2.0", + "hyper 1.4.1", "hyper-util", "rustls", "rustls-pki-types", @@ -793,7 +862,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.25", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -806,7 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.25", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", @@ -820,7 +889,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.2.0", + "hyper 1.4.1", "hyper-util", "native-tls", "tokio", @@ -830,18 +899,18 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -850,9 +919,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -879,9 +948,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -899,34 +968,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.3", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.9.0" @@ -944,15 +1002,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1059,7 +1117,7 @@ dependencies = [ "base64 0.13.1", "bytes", "chrono", - "http 0.2.9", + "http 0.2.12", "percent-encoding", "serde", "serde-value", @@ -1067,6 +1125,20 @@ dependencies = [ "url", ] +[[package]] +name = "kbimg" +version = "1.0.5" +dependencies = [ + "anyhow", + "clap", + "env_logger", + "log", + "regex", + "serde", + "sysinfo", + "toml 0.7.6", +] + [[package]] name = "kube" version = "0.66.0" @@ -1092,9 +1164,9 @@ dependencies = [ "dirs-next", "either", "futures", - "http 0.2.9", - "http-body 0.4.5", - "hyper 0.14.25", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-timeout", "hyper-tls 0.5.0", "jsonpath_lib", @@ -1124,7 +1196,7 @@ checksum = "de491f8c9ee97117e0b47a629753e939c2392d5d0a40f6928e582a5fba328098" dependencies = [ "chrono", "form_urlencoded", - "http 0.2.9", + "http 0.2.12", "json-patch", "k8s-openapi", "once_cell", @@ -1172,25 +1244,24 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -1201,15 +1272,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1246,9 +1317,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -1267,9 +1338,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -1323,11 +1394,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1358,11 +1428,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1373,23 +1452,23 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "once_cell" -version = "1.17.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1406,7 +1485,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] @@ -1417,9 +1496,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -1453,6 +1532,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "parity-tokio-ipc" version = "0.9.0" @@ -1487,7 +1572,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", "winapi", ] @@ -1503,35 +1588,35 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1541,15 +1626,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -1567,15 +1655,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.3" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -1587,14 +1675,38 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1610,8 +1722,8 @@ dependencies = [ "env_logger", "futures", "h2 0.3.16", - "http 0.2.9", - "hyper 0.14.25", + "http 0.2.12", + "hyper 0.14.30", "k8s-openapi", "kube", "log", @@ -1632,9 +1744,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1698,7 +1810,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.15", ] [[package]] @@ -1711,39 +1823,41 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "bitflags 1.3.2", + "either", + "rayon-core", ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "bitflags 1.3.2", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.15", "libredox", "thiserror", ] @@ -1771,17 +1885,17 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2 0.4.3", + "h2 0.4.6", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.2.0", + "hyper 1.4.1", "hyper-rustls", "hyper-tls 0.6.0", "hyper-util", @@ -1821,7 +1935,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.10", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -1830,23 +1944,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", @@ -1862,20 +1975,20 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] name = "rustls-pki-types" -version = "1.4.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" dependencies = [ "ring", "rustls-pki-types", @@ -1884,17 +1997,17 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1939,11 +2052,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -1952,9 +2065,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -1962,9 +2075,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] @@ -1981,13 +2094,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] @@ -2003,16 +2116,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.97" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.5.0", "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2048,20 +2171,26 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "similar" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "slab" @@ -2090,9 +2219,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2112,9 +2241,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2129,9 +2258,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2144,6 +2273,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sysinfo" +version = "0.29.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2167,51 +2311,56 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ - "autocfg", "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "once_cell", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "termtree" -version = "0.2.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] @@ -2225,9 +2374,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2274,7 +2423,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] @@ -2311,9 +2460,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2322,9 +2471,9 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" dependencies = [ "async-stream", "bytes", @@ -2350,16 +2499,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.2" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2371,6 +2519,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -2382,7 +2564,7 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", @@ -2399,8 +2581,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "http-range-header", "pin-project-lite", "tower-layer", @@ -2410,15 +2592,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tower-test" @@ -2455,7 +2637,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", ] [[package]] @@ -2478,9 +2660,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -2499,9 +2681,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2511,9 +2693,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -2526,9 +2708,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2543,9 +2725,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -2570,34 +2752,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2607,9 +2790,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2617,28 +2800,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2646,9 +2829,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ "rustls-pki-types", ] @@ -2671,11 +2854,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2686,11 +2869,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2708,7 +2891,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2728,17 +2920,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2749,9 +2942,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2761,9 +2954,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2773,9 +2966,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2785,9 +2984,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2797,9 +2996,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2809,9 +3008,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2821,9 +3020,18 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -2844,8 +3052,29 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/KubeOS-Rust/Cargo.toml b/KubeOS-Rust/Cargo.toml index 68ee670a..2886023f 100644 --- a/KubeOS-Rust/Cargo.toml +++ b/KubeOS-Rust/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["agent", "cli", "manager", "proxy"] +members = ["agent", "cli", "kbimg", "manager", "proxy"] resolver = "2" [profile.release] diff --git a/KubeOS-Rust/kbimg/Cargo.toml b/KubeOS-Rust/kbimg/Cargo.toml new file mode 100644 index 00000000..c9b48977 --- /dev/null +++ b/KubeOS-Rust/kbimg/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "KubeOS kbimg" +edition = "2021" +license = "MulanPSL-2.0" +name = "kbimg" +version = "1.0.5" + +[dependencies] +anyhow = { version = "1.0" } +clap = { version = "=3.2.23", features = ["derive"] } +env_logger = { version = "0.9" } +log = { version = "= 0.4.15" } +regex = { version = "1.7.3" } +serde = { version = "1.0", features = ["derive"] } +sysinfo = { version = "=0.29.11" } +toml = { version = "=0.7.6" } \ No newline at end of file diff --git a/KubeOS-Rust/kbimg/kbimg.toml b/KubeOS-Rust/kbimg/kbimg.toml new file mode 100644 index 00000000..aaa9b48f --- /dev/null +++ b/KubeOS-Rust/kbimg/kbimg.toml @@ -0,0 +1,87 @@ +[from_repo] +agent_path = "/root/KubeOS/bin/os-agent" +image_type = "vm-repo" +legacy_bios = false +repo_path = "/etc/yum.repos.d/openEuler.repo" +root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" +version = "v1" +rpmlist = [ + "NetworkManager", + "conntrack-tools", + "containernetworking-plugins", + "coreutils", + "dhcp", + "docker", + "dosfstools", + "dracut", + "ebtables", + "ethtool", + "gawk", + "hwinfo", + "kernel", + "kubernetes-kubeadm", + "kubernetes-kubelet", + "lvm2", + "net-tools", + "openssh-server", + "passwd", + "parted", + "rsyslog", + "socat", + "sudo", + "vi", +] + +# [from_dockerimg] +# docker_img = "" +# image_type = "vm-docker" + +# [admin_container] +# dockerfile = "" +# docker_img = "" + +# [pxe_config] +# rootfs_name = "kubeos.tar" +# disk = "/dev/sda" +# server_ip = "192.168.1.50" +# local_ip = "192.168.1.100" +# route_ip = "192.168.1.1" +# netmask = "255.255.255.0" +# net_name = "eth0" + +# [[users]] +# groups = ["admin"] +# name = "foo" +# passwd = "foo" +# sudo = "ALL=(ALL) ALL" + +# [[users]] +# groups = ["example"] +# name = "bar" +# passwd = "bar" + +# [[copy_files]] +# dst = "/root/ztest2" +# src = "/root/KubeOS/ztest2" + +# [[copy_files]] +# dst = "/root/ztest1" +# src = "/root/KubeOS/ztest1/*" + +# [grub] +# passwd = "foo" + +# [systemd_service] +# name = ["kubelet", "containerd"] + +# [chroot_script] +# path = "../../ztest/myscript.sh" + +# [disk_partition] +# first = 100 +# second = 2500 +# third = 2300 +# img_size = 30 + +# [persist_mkdir] +# name = ["lv_data"] diff --git a/KubeOS-Rust/kbimg/src/admin_container.rs b/KubeOS-Rust/kbimg/src/admin_container.rs new file mode 100644 index 00000000..03196739 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/admin_container.rs @@ -0,0 +1,84 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + fs::{create_dir_all, File}, + path::PathBuf, +}; + +use anyhow::bail; + +use crate::{ + commands::AdminContainerInfo, + scripts_gen::*, + utils::{self, set_permissions}, + values::*, + Config, CreateImage, +}; + +impl CreateImage for AdminContainerInfo { + fn prepare(&self, _: &mut Config) -> anyhow::Result<()> { + let dockerfile = &self.dockerfile; + let image_name = &self.docker_img; + verify_admin_input(&dockerfile, &image_name)?; + check_dockerfile_valid(&dockerfile)?; + Ok(()) + } + + fn generate_scripts(&self, _: &Config) -> anyhow::Result { + // admin-container + match create_dir_all(ADMIN_CONTAINER_DIR) { + Ok(_) => { + // Dockerfile + let dockerfile_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_DOCKERFILE); + let mut dockerfile = File::create(&dockerfile_path)?; + gen_admin_dockerfile(&mut dockerfile)?; + set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; + // set-ssh-pub-key.service + let set_ssh_pub_key_service_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SERVICE); + let mut set_ssh_pub_key_service = File::create(&set_ssh_pub_key_service_path)?; + gen_set_ssh_pub_key_service(&mut set_ssh_pub_key_service)?; + set_permissions(&set_ssh_pub_key_service_path, CONFIG_PERMISSION)?; + // set-ssh-pub-key.sh + let set_ssh_pub_key_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SH); + let mut set_ssh_pub_key = File::create(&set_ssh_pub_key_path)?; + gen_set_ssh_pub_key(&mut set_ssh_pub_key)?; + set_permissions(&set_ssh_pub_key_path, EXEC_PERMISSION)?; + }, + Err(e) => { + bail!(e); + }, + } + // kbimg.sh + let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); + let mut kbimg = File::create(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))?; + gen_admin_vars(&mut kbimg, &self.docker_img, &self.dockerfile)?; + gen_create_admin_img(&mut kbimg)?; + set_permissions(&kbimg_path, EXEC_PERMISSION)?; + + Ok(PathBuf::from(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))) + } +} + +fn verify_admin_input(dockerfile: &PathBuf, image_name: &str) -> anyhow::Result<()> { + if !utils::is_valid_param(dockerfile.to_str().unwrap()) { + bail!("params {} is invalid, please check input", dockerfile.to_str().unwrap()); + } + if !utils::is_valid_param(image_name) { + bail!("params {} is invalid, please check input", image_name); + } + Ok(()) +} + +fn check_dockerfile_valid(dockerfile: &PathBuf) -> anyhow::Result<()> { + utils::is_file_valid("admin-container Dockerfile", dockerfile) +} diff --git a/KubeOS-Rust/kbimg/src/commands.rs b/KubeOS-Rust/kbimg/src/commands.rs new file mode 100644 index 00000000..f334f048 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/commands.rs @@ -0,0 +1,170 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::path::PathBuf; + +use clap::{Args, Parser, Subcommand}; +use serde::Deserialize; + +#[derive(Parser)] +#[clap(name = "kbimg")] +#[clap(author, version, about)] +#[clap(long_about = "A tool for creating KubeOS images.")] +pub struct Cli { + /// Path to the detailed configuration toml file + #[clap(short, long, value_parser)] + pub config: Option, + /// Enable debug mode, keep the scripts after execution + #[clap(short, long, action)] + pub debug: bool, + #[clap(subcommand)] + pub commands: Option, +} + +#[derive(Subcommand, Debug, Deserialize)] +pub enum Commands { + /// Create a new container image for upgrading KubeOS + #[clap(name = "upgrade")] + UpgradeImage(RepoInfo), + /// Create a new KubeOS vm image from repo + #[clap(name = "vm-repo")] + VMRepo(RepoInfo), + /// Create a new KubeOS vm image from docker image + #[clap(name = "vm-docker")] + VMDocker(DockerInfo), + /// Create a new KubeOS pxe image from repo + #[clap(name = "pxe-repo")] + PxeRepo(RepoInfo), + /// Create a new KubeOS pxe image from docker image + #[clap(name = "pxe-docker")] + PxeDocker(DockerInfo), + /// Create a KubeOS admin-container image + #[clap(name = "admin-container")] + AdminContainer(AdminContainerInfo), +} + +#[derive(Args, Debug, Deserialize, Clone)] +pub struct RepoInfo { + /// Required: KubeOS version + #[clap(short, long, value_parser)] + pub version: String, + /// Required: Repo path for installing packages + #[clap(short = 'p', long, value_parser)] + pub repo_path: PathBuf, + /// Required: Path to the agent binary + #[clap(short = 'b', long, value_parser)] + pub agent_path: PathBuf, + /// Required: Encrypted password for root user + #[clap(short = 'e', long, value_parser)] + pub root_passwd: String, + /// Required for upgrade + #[clap(short = 'd', long, value_parser)] + pub docker_img: Option, + /// Required: RPM packages + #[clap(short = 'r', long, value_parser)] + pub rpmlist: Vec, + /// Optional: boot mode, default is uefi, enable this flag for legacy bios + #[clap(short, long, value_parser)] + pub legacy_bios: bool, + #[clap(skip)] + pub image_type: String, + #[clap(skip)] + pub arch: Option, +} + +#[derive(Args, Debug, Deserialize, Clone)] +pub struct DockerInfo { + /// Required: Name of the container image + #[clap(short, long, value_parser)] + pub docker_img: String, + #[clap(skip)] + pub image_type: String, +} + +#[derive(Args, Debug, Deserialize, Clone)] +pub struct AdminContainerInfo { + /// Required: Name of the container image + #[clap(short, long, value_parser)] + pub docker_img: String, + /// Required: Path to the Dockerfile + #[clap(short, long, value_parser)] + pub dockerfile: PathBuf, +} + +#[derive(Debug, Deserialize, Default, Clone)] +pub struct Config { + pub from_repo: Option, + pub from_dockerimg: Option, + pub admin_container: Option, + pub users: Option>, + pub copy_files: Option>, + pub grub: Option, + pub systemd_service: Option, + pub chroot_script: Option, + pub disk_partition: Option, + pub persist_mkdir: Option, + pub pxe_config: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct User { + pub name: String, + pub passwd: String, + pub groups: Option>, + pub sudo: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct CopyFile { + pub src: String, + pub dst: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Grub { + pub passwd: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct SystemdService { + pub name: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct ChrootScript { + pub path: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct DiskPartition { + pub first: u32, + pub second: u32, + pub third: u32, + pub img_size: u32, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct PersistMkdir { + pub name: Vec, +} + +// pxe config +#[derive(Debug, Deserialize, Clone)] +pub struct PxeConfig { + pub rootfs_name: String, + pub disk: String, + pub server_ip: String, + pub local_ip: String, + pub route_ip: String, + pub netmask: String, + pub net_name: String, +} diff --git a/KubeOS-Rust/kbimg/src/docker_img.rs b/KubeOS-Rust/kbimg/src/docker_img.rs new file mode 100644 index 00000000..9c5a8c20 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/docker_img.rs @@ -0,0 +1,74 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{fs::File, path::PathBuf, process::Command, str}; + +use anyhow::bail; + +use crate::{ + commands::DockerInfo, + scripts_gen::*, + utils::{self, set_permissions}, + values::*, + Config, CreateImage, +}; + +impl CreateImage for DockerInfo { + fn prepare(&self, _: &mut Config) -> anyhow::Result<()> { + let image_name = &self.docker_img; + verify_docker_input(&image_name)?; + check_docker_image(&image_name)?; + Ok(()) + } + + fn generate_scripts(&self, config: &Config) -> anyhow::Result { + // kbimg.sh + let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); + let mut kbimg = File::create(&kbimg_path)?; + gen_global_vars(&mut kbimg)?; + gen_docker_vars(&mut kbimg, &self.docker_img)?; + gen_global_func(&mut kbimg)?; + gen_create_os_tar_from_docker(&mut kbimg)?; + if self.image_type == "vm-docker" { + // kbimg.sh + gen_init_part(&mut kbimg)?; + gen_create_img(&mut kbimg, false, &config)?; + gen_create_vm_docker_img(&mut kbimg)?; + } else { + // kbimg.sh + gen_create_pxe_docker_img(&mut kbimg)?; + } + set_permissions(&kbimg_path, EXEC_PERMISSION)?; + + Ok(PathBuf::from(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))) + } +} + +fn verify_docker_input(image_name: &str) -> anyhow::Result<()> { + if !utils::is_valid_param(image_name) { + bail!("params {} is invalid, please check input", image_name); + } + Ok(()) +} + +fn check_docker_image(image_name: &str) -> anyhow::Result<()> { + let output = + Command::new("docker").args(&["images", "-q", image_name]).output().expect("Failed to execute command"); + + if output.status.success() { + let stdout = str::from_utf8(&output.stdout).expect("Invalid UTF-8 output"); + if stdout.trim().is_empty() { + bail!("docker image does NOT exist, please pull {} first.", image_name); + } + } + Ok(()) +} diff --git a/KubeOS-Rust/kbimg/src/main.rs b/KubeOS-Rust/kbimg/src/main.rs new file mode 100644 index 00000000..beb389d5 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/main.rs @@ -0,0 +1,122 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{fs, path::PathBuf, process::exit}; + +use anyhow::{bail, Result}; +use clap::Parser; +use env_logger::{Builder, Env, Target}; +use log::{debug, error, info}; + +mod admin_container; +mod commands; +mod docker_img; +mod repo; +mod scripts_gen; +mod utils; +mod values; + +use utils::{execute_scripts, get_arch}; +use values::SCRIPTS_DIR; + +use crate::commands::{Cli, Commands, Config}; + +trait CreateImage { + /// validate cmd args, check disk size and other prepare work + fn prepare(&self, config: &mut Config) -> Result<()>; + /// generate scripts for creating image. If debug is enabled, keep the scripts, otherwise execute them + fn generate_scripts(&self, config: &Config) -> Result; +} + +fn process(info: Box, mut config: Config) -> Result<()> { + match fs::create_dir_all(SCRIPTS_DIR) { + Ok(_) => { + info.prepare(&mut config)?; + let path = info.generate_scripts(&config)?; + execute_scripts(path)?; + Ok(()) + }, + Err(e) => bail!(e), + } +} + +fn main() { + let cli = Cli::parse(); + let default_log_level: &str = if cli.debug { "debug" } else { "info" }; + Builder::from_env(Env::default().default_filter_or(default_log_level)).target(Target::Stdout).init(); + match cli.config { + Some(config) => { + info!("Loading config file"); + debug!("Config file path: {:?}", config); + let content = fs::read_to_string(config).unwrap(); + let data: Config = toml::from_str(&content).unwrap(); + debug!("Config: {:?}", data); + let info = if let Some(mut info) = data.from_repo.clone() { + info.arch = Some(get_arch()); + Some(Box::new(info) as Box) + } else if let Some(info) = data.from_dockerimg.clone() { + Some(Box::new(info) as Box) + } else if let Some(info) = data.admin_container.clone() { + Some(Box::new(info) as Box) + } else { + None + }; + if let Some(i) = info { + match process(i, data) { + Ok(_) => { + info!("Image created successfully"); + }, + Err(e) => { + error!("Failed to create image: {:?}", e); + }, + } + } + exit(0); + }, + None => {}, + } + let info = match cli.commands { + Some(Commands::UpgradeImage(mut info)) => { + info.image_type = "upgrade".to_string(); + Some(Box::new(info) as Box) + }, + Some(Commands::VMRepo(mut info)) => { + info.image_type = "vm-repo".to_string(); + debug!("VMRepo: {:?}", info); + Some(Box::new(info) as Box) + }, + Some(Commands::VMDocker(mut info)) => { + info.image_type = "vm-docker".to_string(); + Some(Box::new(info) as Box) + }, + Some(Commands::PxeRepo(mut info)) => { + info.image_type = "pxe-repo".to_string(); + Some(Box::new(info) as Box) + }, + Some(Commands::PxeDocker(mut info)) => { + info.image_type = "pxe-docker".to_string(); + Some(Box::new(info) as Box) + }, + Some(Commands::AdminContainer(info)) => Some(Box::new(info) as Box), + None => None, + }; + if let Some(i) = info { + match process(i, Config::default()) { + Ok(_) => { + info!("Image created successfully"); + }, + Err(e) => { + error!("Failed to create image: {:?}", e); + }, + } + } +} diff --git a/KubeOS-Rust/kbimg/src/repo.rs b/KubeOS-Rust/kbimg/src/repo.rs new file mode 100644 index 00000000..43aabd3b --- /dev/null +++ b/KubeOS-Rust/kbimg/src/repo.rs @@ -0,0 +1,203 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + env, + fs::{create_dir_all, File}, + path::PathBuf, +}; + +use anyhow::bail; +use sysinfo::{System, SystemExt, DiskExt}; + +use crate::{ + commands::RepoInfo, + scripts_gen::*, + utils::{self, check_pxe_conf_valid, set_permissions}, + values::*, + Config, CreateImage, +}; + +impl CreateImage for RepoInfo { + fn prepare(&self, config: &mut Config) -> anyhow::Result<()> { + verify_repo_input(&self)?; + check_disk_space(&self.image_type)?; + check_repo_file_valid(&self.repo_path)?; + check_agent_file_valid(&self.agent_path)?; + if self.image_type == "pxe-repo" { + if let Some(pxe_config) = &config.pxe_config { + check_pxe_conf_valid(&pxe_config)?; + } else { + bail!("pxe config not found!") + } + } + Ok(()) + } + + fn generate_scripts(&self, config: &Config) -> anyhow::Result { + // rpmlist + let rpmlist_path = format!("{}/{}", SCRIPTS_DIR, RPMLIST); + let mut rpmlist = File::create(&rpmlist_path)?; + gen_rpm_list(&mut rpmlist, &self.rpmlist)?; + set_permissions(&rpmlist_path, CONFIG_PERMISSION)?; + // 00bootup + match create_dir_all(BOOTUP_DIR) { + Ok(_) => { + if let Some(pxe_config) = &config.pxe_config { + let global_cfg_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_GLOBAL_CFG); + let mut global_cfg = File::create(&global_cfg_path)?; + gen_global_cfg(&mut global_cfg, &pxe_config)?; + set_permissions(&global_cfg_path, CONFIG_PERMISSION)?; + } + let module_setup_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MODULE_SETUP_SH); + let mut module_setup = File::create(&module_setup_path)?; + gen_module_setup(&mut module_setup)?; + set_permissions(&module_setup_path, EXEC_PERMISSION)?; + let mount_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MOUNT_SH); + let mut mount = File::create(&mount_path)?; + gen_mount(&mut mount)?; + set_permissions(&mount_path, EXEC_PERMISSION)?; + }, + Err(e) => { + bail!(e); + }, + } + // misc-files + match create_dir_all(MISC_FILES_DIR) { + Ok(_) => { + let boot_efi_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_EFI_MOUNT); + let mut boot_efi_mount = File::create(&boot_efi_mount_path)?; + gen_boot_efi_mount(&mut boot_efi_mount)?; + set_permissions(&boot_efi_mount_path, CONFIG_PERMISSION)?; + let boot_grub2_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_GRUB2_MOUNT); + let mut boot_grub2_mount = File::create(&boot_grub2_mount_path)?; + gen_boot_grub2_mount(&mut boot_grub2_mount)?; + set_permissions(&boot_grub2_mount_path, CONFIG_PERMISSION)?; + let etc_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_ETC_MOUNT); + let mut etc_mount = File::create(&etc_mount_path)?; + gen_etc_mount(&mut etc_mount)?; + set_permissions(&etc_mount_path, CONFIG_PERMISSION)?; + let os_agent_service_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_AGENT_SERVICE); + let mut os_agent_service = File::create(&os_agent_service_path)?; + gen_os_agent_service(&mut os_agent_service)?; + set_permissions(&os_agent_service_path, CONFIG_PERMISSION)?; + let os_release_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_RELEASE); + let mut os_release = File::create(&os_release_path)?; + gen_os_release(&mut os_release)?; + set_permissions(&os_release_path, CONFIG_PERMISSION)?; + let persist_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_PERSIST_MOUNT); + let mut persist_mount = File::create(&persist_mount_path)?; + gen_persist_mount(&mut persist_mount)?; + set_permissions(&persist_mount_path, CONFIG_PERMISSION)?; + let var_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_VAR_MOUNT); + let mut var_mount = File::create(&var_mount_path)?; + gen_var_mount(&mut var_mount)?; + set_permissions(&var_mount_path, CONFIG_PERMISSION)?; + }, + Err(e) => { + bail!(e); + }, + } + // grub.cfg + let grub_cfg_path = format!("{}/{}", SCRIPTS_DIR, GRUB_CFG); + let mut grub_cfg = File::create(&grub_cfg_path)?; + gen_grub_cfg(&mut grub_cfg)?; + set_permissions(&grub_cfg_path, CONFIG_PERMISSION)?; + // set_in_chroot.sh + let set_in_chroot_path = format!("{}/{}", SCRIPTS_DIR, SET_IN_CHROOT_SH); + let mut set_in_chroot = File::create(&set_in_chroot_path)?; + gen_set_in_chroot(&mut set_in_chroot, self.legacy_bios, &config)?; + set_permissions(&set_in_chroot_path, EXEC_PERMISSION)?; + // kbimg.sh + let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); + let mut kbimg = File::create(&kbimg_path)?; + gen_global_vars(&mut kbimg)?; + gen_repo_vars(&mut kbimg, &self)?; + gen_global_func(&mut kbimg)?; + gen_mount_proc_dev_sys(&mut kbimg)?; + gen_unmount_dir(&mut kbimg)?; + gen_create_os_tar_from_repo(&mut kbimg, &self, &config)?; + if self.image_type == "vm-repo" { + // bootloader.sh + let bootloader_path = format!("{}/{}", SCRIPTS_DIR, BOOTLOADER_SH); + let mut bootloader = File::create(&bootloader_path)?; + gen_bootloader(&mut bootloader, self.arch.as_ref().unwrap(), self.legacy_bios)?; + set_permissions(&bootloader_path, EXEC_PERMISSION)?; + // kbimg.sh + gen_init_part(&mut kbimg)?; + gen_create_img(&mut kbimg, self.legacy_bios, &config)?; + gen_create_vm_repo_img(&mut kbimg)?; + } else if self.image_type == "pxe-repo" { + // kbimg.sh + gen_create_pxe_repo_img(&mut kbimg)?; + } else { + // Dockerfile + let dockerfile_path = format!("{}/{}", SCRIPTS_DIR, DOCKERFILE); + let mut dockerfile = File::create(&dockerfile_path)?; + gen_dockerfile(&mut dockerfile)?; + set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; + // kbimg.sh + gen_create_docker_img(&mut kbimg)?; + } + set_permissions(&kbimg_path, EXEC_PERMISSION)?; + + Ok(PathBuf::from(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))) + } +} + +fn verify_repo_input(info: &RepoInfo) -> anyhow::Result<()> { + if !utils::is_valid_param(info.repo_path.to_str().unwrap()) { + bail!("params {} is invalid, please check input", info.repo_path.to_str().unwrap()); + } + if !utils::is_valid_param(&info.version) { + bail!("params {} is invalid, please check input", info.version); + } + if !utils::is_valid_param(info.agent_path.to_str().unwrap()) { + bail!("params {} is invalid, please check input", info.agent_path.to_str().unwrap()); + } + if let Some(docker_img) = &info.docker_img { + if !utils::is_valid_param(docker_img) { + bail!("params {} is invalid, please check input", docker_img); + } + } + Ok(()) +} + +fn check_disk_space(image_type: &str) -> anyhow::Result<()> { + let max_size: u64 = match image_type { + "upgrade" => 6, + "vm-repo" => 25, + "pxe-repo" => 5, + _ => bail!("Invalid image type: {}", image_type), + }; + + let current_dir = env::current_dir().expect("Failed to get current directory"); + let root_dir = current_dir.ancestors().last().expect("Failed to get current directory").to_path_buf(); + let mut sys = System::new_all(); + sys.refresh_all(); + for d in sys.disks() { + if d.mount_point() == root_dir { + if d.available_space() < max_size * 1024 * 1024 { + bail!("The available disk space is not enough, at least {}GiB.", max_size); + } + } + } + Ok(()) +} + +fn check_repo_file_valid(repo_path: &PathBuf) -> anyhow::Result<()> { + utils::is_file_valid("REPO file", repo_path) +} + +fn check_agent_file_valid(agent_path: &PathBuf) -> anyhow::Result<()> { + utils::is_file_valid("os-agent binary", agent_path) +} diff --git a/KubeOS-Rust/kbimg/src/scripts_gen.rs b/KubeOS-Rust/kbimg/src/scripts_gen.rs new file mode 100644 index 00000000..7d574888 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/scripts_gen.rs @@ -0,0 +1,1754 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{fs::File, io::Write, path::PathBuf}; + +use anyhow::{bail, Ok, Result}; + +use crate::{commands::*, values::SCRIPTS_DIR}; + +/* copyright */ +pub(crate) fn gen_copyright(file: &mut File) -> Result<()> { + writeln!( + file, + r#"## Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved. +# KubeOS is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +## See the Mulan PSL v2 for more details. +"# + )?; + + Ok(()) +} + +/* region: kbimg.sh */ +pub(crate) fn gen_global_vars(file: &mut File) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"set -e + +SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd) +LOCK="${{SCRIPTS_DIR}}"/test.lock +RPM_ROOT="${{SCRIPTS_DIR}}"/rootfs +TMP_MOUNT_PATH="${{SCRIPTS_DIR}}"/mnt +"# + )?; + Ok(()) +} + +pub(crate) fn gen_global_func(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function delete_dir() {{ + local ret=0 + local dir="$1" + unmount_dir "${{dir}}" + ret=$? + if [ "${{ret}}" -eq 0 ]; then + rm -rf "${{dir}}" + return 0 + else + log_error_print "${{dir}} is failed to unmount , can not delete ${{dir}}." + return 1 + fi +}} + +function delete_file() {{ + local file="$1" + if [ ! -e "${{file}}" ]; then + return 0 + fi + + if [ ! -f "${{file}}" ]; then + log_error_print "${{file}} is not a file." + return 1 + fi + + rm -f "${{file}}" + return 0 +}} + +function clean_space() {{ + delete_dir "${{RPM_ROOT}}" + delete_dir "${{TMP_MOUNT_PATH}}" + delete_file "${{SCRIPTS_DIR}}"/os.tar + rm -rf "${{LOCK}}" + delete_file "${{ADMIN_CONTAINER_DIR}}"/hostshell +}} + +function clean_img() {{ + delete_file "${{SCRIPTS_DIR}}"/system.img + delete_file "${{SCRIPTS_DIR}}"/update.img + delete_file "${{SCRIPTS_DIR}}"/initramfs.img + delete_file "${{SCRIPTS_DIR}}"/kubeos.tar +}} + +function file_lock() {{ + local lock_file=$1 + exec {{lock_fd}}>"${{lock_file}}" + flock -xn "${{lock_fd}}" +}} + +function test_lock() {{ + file_lock "${{LOCK}}" + local status=$? + if [ $status -ne 0 ]; then + log_error_print "There is already an generate process running." + exit 203 + fi +}} + +function log_error_print() {{ + local logmsg + logmsg="[ ERROR ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" + echo "$logmsg" +}} + +function log_info_print() {{ + local logmsg + logmsg="[ INFO ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" + echo "$logmsg" +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_mount_proc_dev_sys(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function mount_proc_dev_sys() {{ + local tmp_root=$1 + mount -t proc none "${{tmp_root}}"/proc + mount --bind /dev "${{tmp_root}}"/dev + mount --bind /dev/pts "${{tmp_root}}"/dev/pts + mount -t sysfs none "${{tmp_root}}"/sys +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_unmount_dir(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function unmount_dir() {{ + local dir=$1 + + if [ -L "${{dir}}" ] || [ -f "${{dir}}" ]; then + log_error_print "${{dir}} is not a directory, please check it." + return 1 + fi + + if [ ! -d "${{dir}}" ]; then + return 0 + fi + + local real_dir + real_dir=$(readlink -e "${{dir}}") + local mnts + mnts=$(awk '{{print $2}}' < /proc/mounts | grep "^${{real_dir}}" | sort -r) + for m in ${{mnts}}; do + log_info_print "Unmount ${{m}}" + umount -f "${{m}}" || true + done + + return 0 +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_init_part(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function init_part() {{ + local offset + offset=$(fdisk -l "${{SCRIPTS_DIR}}"/system.img | grep "$1" | awk '{{print $2}}') + local sizelimit + sizelimit=$(fdisk -l "${{SCRIPTS_DIR}}"/system.img | grep "$1" | awk '{{print $3}}') + sizelimit=$(echo "($sizelimit - $offset)*512" | bc) + offset=$(echo "${{offset}}*512" | bc) + local loop + loop=$(losetup -f) + losetup -o "${{offset}}" --sizelimit "${{sizelimit}}" "${{loop}}" "${{SCRIPTS_DIR}}"/system.img + if [ "$2" == "BOOT" ];then + mkfs.vfat -n "$2" "${{loop}}" + mount -t vfat "${{loop}}" "$3" + else + mkfs.ext4 -L "$2" "${{loop}}" + mount -t ext4 "${{loop}}" "$3" + rm -rf "$3/lost+found" + fi +}} +"# + )?; + Ok(()) +} + +// repo +pub(crate) fn gen_repo_vars(file: &mut File, info: &RepoInfo) -> Result<()> { + writeln!( + file, + r#"REPO_PATH="{}" +VERSION="{}" +AGENT_PATH="{}" +ROOT_PASSWD='{}' +BOOT_MODE="{}" +"#, + info.repo_path.to_str().unwrap(), + &info.version, + info.agent_path.to_str().unwrap(), + &info.root_passwd, + if info.legacy_bios { "legacy" } else { "efi" } + )?; + if let Some(docker_img) = &info.docker_img { + writeln!(file, "DOCKER_IMG=\"{}\"\n", docker_img)?; + } + Ok(()) +} + +pub(crate) fn gen_prepare_yum(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function prepare_yum() {{ + # init rpmdb + rpm --root "${{RPM_ROOT}}" --initdb + mkdir -p "${{RPM_ROOT}}"{{/etc/yum.repos.d,/persist,/proc,/dev/pts,/sys}} + mount_proc_dev_sys "${{RPM_ROOT}}" + # init yum repo + local iso_repo="${{RPM_ROOT}}"/etc/yum.repos.d/iso.repo + cat "${{REPO_PATH}}" > "$iso_repo" +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_install_packages(file: &mut File, arch: &str, legacy_bios: bool) -> Result<()> { + writeln!( + file, + r#"function install_packages() {{ + prepare_yum "${{REPO_PATH}}" + + echo "install package.." + + local filesize + filesize=$(stat -c "%s" "${{SCRIPTS_DIR}}"/rpmlist) + local maxsize=$((1024*1024)) + if [ "${{filesize}}" -gt "${{maxsize}}" ]; then + echo "please check if rpmlist is too big or something wrong" + exit 7 + fi + + local rpms_name + rpms_name=$(tr "\n" " " < "${{SCRIPTS_DIR}}"/rpmlist) + old_ifs="$IFS" + IFS=' '"# + )?; + + if arch == "x86_64" { + if legacy_bios { + writeln!(file, "\trpms_name+=\" grub2\"")?; + } else { + writeln!(file, "\trpms_name+=\" grub2-efi grub2-tools grub2-efi-x64-modules grub2-pc-modules\"")?; + } + writeln!( + file, + r#" read -ra rpms <<< "${{rpms_name}}" + IFS="$old_ifs" + yum -y --installroot="${{RPM_ROOT}}" install --nogpgcheck --setopt install_weak_deps=False "${{rpms[@]}}""# + )?; + } else if arch == "aarch64" { + writeln!( + file, + r#" read -ra rpms <<< "${{rpms_name}}" + IFS="$old_ifs" + yum -y --installroot="${{RPM_ROOT}}" install --nogpgcheck --setopt install_weak_deps=False "${{rpms[@]}}" grub2-efi grub2-tools grub2-efi-aa64-modules"# + )?; + } + writeln!( + file, + r#" yum -y --installroot="${{RPM_ROOT}}" clean all +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_copy_files(file: &mut File, copy_files: &Vec) -> Result<()> { + writeln!(file, "function copy_files() {{")?; + for copy_file in copy_files { + let dst = format!("{}/rootfs{}", SCRIPTS_DIR, ©_file.dst); + let dst = PathBuf::from(dst); + if !dst.exists() { + writeln!(file, "\tmkdir -p \"${{RPM_ROOT}}{}\"", ©_file.dst)?; + } + let src = PathBuf::from(©_file.src); + if src.is_dir() { + writeln!(file, "\tcp -r {} \"${{RPM_ROOT}}{}\"", ©_file.src, ©_file.dst)?; + } else { + writeln!(file, "\tcp {} \"${{RPM_ROOT}}{}\"", ©_file.src, ©_file.dst)?; + } + } + writeln!(file, "}}\n")?; + Ok(()) +} + +pub(crate) fn gen_grub_config(file: &mut File, legacy_bios: bool, grub: &Grub) -> Result<()> { + writeln!( + file, + r#"function grub_config() {{ + local GRUB_PATH"# + )?; + if legacy_bios { + writeln!(file, "\tGRUB_PATH=\"${{RPM_ROOT}}\"/boot/grub2")?; + } else { + writeln!(file, "\tGRUB_PATH=\"${{RPM_ROOT}}\"/boot/efi/EFI/openEuler")?; + } + if let Some(grub_passwd) = &grub.passwd { + writeln!( + file, + r#" local GRUB_PASSWD + GRUB_PASSWD=$(echo -e "{}\n{}" | grub2-mkpasswd-pbkdf2 | grep PBKDF2 | awk '{{print $7}}') + echo "GRUB2_PASSWD=${{GRUB_PASSWD}}" > "${{GRUB_PATH}}"/user.cfg + chmod 600 "${{GRUB_PATH}}"/user.cfg +}} +"#, + grub_passwd, grub_passwd + )?; + } + Ok(()) +} + +pub(crate) fn gen_chroot_script(file: &mut File, chroot_script: &ChrootScript) -> Result<()> { + let script_path = PathBuf::from(&chroot_script.path); + match script_path.canonicalize() { + core::result::Result::Ok(absolute_path) => { + if let Some(script_name) = absolute_path.file_name() { + writeln!( + file, + r#"function chroot_script() {{ + cp "{}" "${{RPM_ROOT}}" + chroot "${{RPM_ROOT}}" bash /{} +}} +"#, + absolute_path.as_path().to_str().unwrap(), + script_name.to_str().unwrap() + )?; + } + Ok(()) + }, + Err(e) => bail!(e), + } +} + +pub(crate) fn gen_install_misc(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { + if let Some(copy_files) = &config.copy_files { + gen_copy_files(file, ©_files)?; + } + if let Some(grub) = &config.grub { + gen_grub_config(file, legacy_bios, &grub)?; + } + if let Some(chroot_script) = &config.chroot_script { + gen_chroot_script(file, &chroot_script)?; + } + + writeln!( + file, + r#"function install_misc() {{ + cp "${{SCRIPTS_DIR}}"/misc-files/*mount "${{SCRIPTS_DIR}}"/misc-files/os-agent.service "${{RPM_ROOT}}"/usr/lib/systemd/system/ + cp "${{SCRIPTS_DIR}}"/misc-files/os-release "${{RPM_ROOT}}"/usr/lib/ + cp "${{AGENT_PATH}}" "${{RPM_ROOT}}"/usr/bin + rm "${{RPM_ROOT}}"/etc/os-release + + cat < "${{RPM_ROOT}}"/usr/lib/os-release +NAME=${{NAME}} +ID=${{NAME}} +EOF + echo "PRETTY_NAME=\"${{NAME}} ${{VERSION}}\"" >> "${{RPM_ROOT}}"/usr/lib/os-release + echo "VERSION_ID=${{VERSION}}" >> "${{RPM_ROOT}}"/usr/lib/os-release + mv "${{RPM_ROOT}}"/boot/vmlinuz* "${{RPM_ROOT}}"/boot/vmlinuz + mv "${{RPM_ROOT}}"/boot/initramfs* "${{RPM_ROOT}}"/boot/initramfs.img"# + )?; + + if legacy_bios { + writeln!( + file, + r#" cp "${{SCRIPTS_DIR}}"/grub.cfg "${{RPM_ROOT}}"/boot/grub2 + sed -i "s/insmod part_gpt/insmod part_msdos/g; \ +s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ +s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ +"${{RPM_ROOT}}"/boot/grub2/grub.cfg"# + )?; + } else { + writeln!(file, "\tcp \"${{SCRIPTS_DIR}}\"/grub.cfg \"${{RPM_ROOT}}\"/boot/efi/EFI/openEuler")?; + } + + writeln!( + file, + r#" cp -r "${{SCRIPTS_DIR}}"/00bootup "${{RPM_ROOT}}"/usr/lib/dracut/modules.d/ + cp "${{SCRIPTS_DIR}}"/set_in_chroot.sh "${{RPM_ROOT}}" + + # (optional) custom config"# + )?; + + if let Some(_) = &config.copy_files { + writeln!(file, "\tcopy_files")?; + } + if let Some(_) = &config.grub { + writeln!(file, "\tgrub_config")?; + } + if let Some(_) = &config.chroot_script { + writeln!(file, "\tchroot_script")?; + } + + writeln!( + file, + r#" + ROOT_PASSWD="${{ROOT_PASSWD}}" BOOT_MODE="${{BOOT_MODE}}" chroot "${{RPM_ROOT}}" bash /set_in_chroot.sh + rm "${{RPM_ROOT}}/set_in_chroot.sh" +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_os_tar_from_repo(file: &mut File, info: &RepoInfo, config: &Config) -> Result<()> { + gen_prepare_yum(file)?; + gen_install_packages(file, info.arch.as_ref().unwrap(), info.legacy_bios)?; + gen_install_misc(file, info.legacy_bios, config)?; + + writeln!( + file, + r#"function create_os_tar_from_repo() {{ + install_packages + install_misc + unmount_dir "${{RPM_ROOT}}" + tar -C "${{RPM_ROOT}}" -cf "${{SCRIPTS_DIR}}"/os.tar . +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_img(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { + let (first, second, third, img_size) = if let Some(disk_partition) = &config.disk_partition { + let first = disk_partition.first; + let second = disk_partition.second; + let third = disk_partition.third; + let img_size = disk_partition.img_size; + if first + second + third + 2100 > img_size * 1024 { + bail!("Image size({}G) is not enough for partitions, please check input", img_size) + } + (first, first + second, first + second + third, img_size) + } else { + (60, 2160, 4260, 20) + }; + + writeln!( + file, + r#"function create_img() {{ + rm -f "${{SCRIPTS_DIR}}"/system.img "${{SCRIPTS_DIR}}/update.img" + qemu-img create "${{SCRIPTS_DIR}}/system.img" {}G"#, + img_size + )?; + + if legacy_bios { + writeln!( + file, + r#" local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/grub2 + parted "${{SCRIPTS_DIR}}/system.img" -s mklabel msdos + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 1MiB {}MiB"#, + first + )?; + } else { + writeln!( + file, + r#" local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/efi + parted "${{SCRIPTS_DIR}}/system.img" -s mklabel gpt + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary fat32 1MiB {}MiB"#, + first + )?; + } + + writeln!( + file, + r#" parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB {}MiB + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB {}MiB + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB 100%"#, + first, second, second, third, third + )?; + + writeln!( + file, + r#" local device + device=$(losetup -f) + losetup "${{device}}" "${{SCRIPTS_DIR}}"/system.img + + mkdir -p "${{TMP_MOUNT_PATH}}" + + init_part "${{SCRIPTS_DIR}}"/system.img2 ROOT-A "${{TMP_MOUNT_PATH}}" + + mkdir -p "${{BOOT_PATH}}" + chmod 755 "${{BOOT_PATH}}""# + )?; + + if legacy_bios { + writeln!( + file, + r#" init_part "${{SCRIPTS_DIR}}"/system.img1 GRUB2 "${{BOOT_PATH}}" + tar -x -C "${{TMP_MOUNT_PATH}}" -f "${{SCRIPTS_DIR}}"/os.tar + sed -i "s/insmod part_gpt/insmod part_msdos/g; \ +s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ +s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ +"${{TMP_MOUNT_PATH}}"/boot/grub2/grub.cfg"# + )?; + } else { + writeln!( + file, + r#" init_part "${{SCRIPTS_DIR}}"/system.img1 BOOT "${{BOOT_PATH}}" + tar -x -C "${{TMP_MOUNT_PATH}}" -f "${{SCRIPTS_DIR}}"/os.tar"# + )?; + } + + writeln!( + file, + r#" sync + cp "${{SCRIPTS_DIR}}"/bootloader.sh "${{TMP_MOUNT_PATH}}" + mount_proc_dev_sys "${{TMP_MOUNT_PATH}}" + DEVICE="${{device}}" BOOT_MODE="${{BOOT_MODE}}" chroot "${{TMP_MOUNT_PATH}}" bash bootloader.sh + rm -rf "${{TMP_MOUNT_PATH}}"/bootloader.sh + sync + + dd if=/dev/disk/by-label/ROOT-A of="${{SCRIPTS_DIR}}"/update.img bs=8M + sync + unmount_dir "${{TMP_MOUNT_PATH}}" + init_part "${{SCRIPTS_DIR}}"/system.img3 ROOT-B "${{TMP_MOUNT_PATH}}" + umount "${{TMP_MOUNT_PATH}}" + + init_part "${{SCRIPTS_DIR}}"/system.img4 PERSIST "${{TMP_MOUNT_PATH}}" + mkdir "${{TMP_MOUNT_PATH}}"/{{var,etc,etcwork}}"# + )?; + + if let Some(persist_mkdir) = &config.persist_mkdir { + for name in &persist_mkdir.name { + writeln!(file, "\tmkdir \"${{TMP_MOUNT_PATH}}\"/{}", name)?; + } + } + + writeln!( + file, + r#" mkdir -p "${{TMP_MOUNT_PATH}}"/etc/KubeOS/certs + umount "${{TMP_MOUNT_PATH}}" + + losetup -D + parted "${{SCRIPTS_DIR}}"/system.img -- set 1 boot on + qemu-img convert "${{SCRIPTS_DIR}}"/system.img -O qcow2 "${{SCRIPTS_DIR}}"/system.qcow2 +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_vm_repo_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_vm_repo_img() {{ + create_os_tar_from_repo + create_img +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_vm_repo_img"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_pxe_repo_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_pxe_repo_img() {{ + rm -rf "${{SCRIPTS_DIR}}"/initramfs.img "${{SCRIPTS_DIR}}"/kubeos.tar + create_os_tar_from_repo + tar -xvf "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/initramfs.img + mv "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/kubeos.tar +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_pxe_repo_img"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_docker_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_docker_img() {{ + create_os_tar_from_repo + docker build -t "${{DOCKER_IMG}}" -f "${{SCRIPTS_DIR}}"/Dockerfile . +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_docker_img"# + )?; + Ok(()) +} + +// docker +pub(crate) fn gen_docker_vars(file: &mut File, image_name: &str) -> Result<()> { + writeln!( + file, + r#" +IMAGE_NAME="{}" +BOOT_MODE=efi +"#, + image_name + )?; + Ok(()) +} + +pub(crate) fn gen_create_os_tar_from_docker(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_os_tar_from_docker() {{ + container_id=$(docker create "${{DOCKER_IMG}}") + echo "$container_id" + docker cp "$container_id":/os.tar "${{SCRIPTS_DIR}}" + docker rm "$container_id" +}} +"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_vm_docker_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_vm_docker_img() {{ + create_os_tar_from_docker + create_img +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_vm_docker_img"# + )?; + Ok(()) +} + +pub(crate) fn gen_create_pxe_docker_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_pxe_docker_img() {{ + rm -rf "${{SCRIPTS_DIR}}"/initramfs.img "${{SCRIPTS_DIR}}"/kubeos.tar + create_os_tar_from_docker + tar -xvf "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/initramfs.img + mv "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/kubeos.tar +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_pxe_docker_img"# + )?; + Ok(()) +} + +// admin +pub(crate) fn gen_admin_vars(file: &mut File, docker_img: &str, dockerfile: &PathBuf) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"set -e + +DOCKER_IMG={} +DOCKERFILE={} +ADMIN_CONTAINER_DIR="${{SCRIPTS_DIR}}"/admin-container +"#, + dockerfile.to_str().unwrap(), + docker_img + )?; + Ok(()) +} + +pub(crate) fn gen_create_admin_img(file: &mut File) -> Result<()> { + writeln!( + file, + r#"function create_admin_img() {{ + local kubeos_root_dir=$(dirname $(dirname $(dirname "${{SCRIPTS_DIR}}"))) + cp "${{kubeos_root_dir}}"/bin/hostshell "${{ADMIN_CONTAINER_DIR}}" + docker build -t "${{DOCKER_IMG}}" -f "${{DOCKERFILE}}" "${{ADMIN_CONTAINER_DIR}}" + rm -rf "${{ADMIN_CONTAINER_DIR}}"/hostshell +}} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_admin_img"# + )?; + Ok(()) +} +/* endregion */ + +/* region: set_in_chroot.sh */ +pub(crate) fn gen_add_users(file: &mut File, users: &Vec) -> Result<()> { + writeln!(file, "# add users")?; + for user in users { + let name = &user.name; + let passwd = &user.passwd; + let groups = match user.groups.clone() { + Some(groups) => groups, + None => vec![name.clone()], + }; + for group in &groups { + writeln!( + file, + r#"if ! getent group "{}" > /dev/null 2>&1; then + groupadd "{}" +fi"#, + group, group + )?; + } + write!(file, "useradd -m -g {}", &groups[0])?; + if groups.len() > 1 { + let additional_groups = &groups[1..].join(","); + write!(file, " -G {}", additional_groups)?; + } + writeln!(file, " -s /bin/bash \"{}\"", &name)?; + writeln!(file, "echo \"{}:{}\" | chpasswd", name, passwd)?; + if let Some(sudo) = &user.sudo { + writeln!( + file, + r#"if visudo -c; then + echo -e "{} {}" | tee -a /etc/sudoers +else + echo "Sudoers file syntax check failed. Please fix the sudoers file manually." + exit 5 +fi"#, + name, sudo + )?; + } + } + Ok(()) +} + +pub(crate) fn gen_systemd_services(file: &mut File, systemd_services: &SystemdService) -> Result<()> { + writeln!(file, "# systemd")?; + for service_name in &systemd_services.name { + writeln!(file, "systemctl enable {}", service_name)?; + } + Ok(()) +} + +pub(crate) fn gen_set_in_chroot(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"ln -s /usr/lib/systemd/system/os-agent.service /usr/lib/systemd/system/multi-user.target.wants/os-agent.service +ln -s /usr/lib/systemd/system/kubelet.service /usr/lib/systemd/system/multi-user.target.wants/kubelet.service"# + )?; + if legacy_bios { + writeln!( + file, + "ln -s /usr/lib/systemd/system/boot-grub2.mount /lib/systemd/system/local-fs.target.wants/boot-grub2.mount" + )?; + } else { + writeln!( + file, + "ln -s /usr/lib/systemd/system/boot-efi.mount /lib/systemd/system/local-fs.target.wants/boot-efi.mount" + )?; + } + writeln!(file, r#"ln -s /usr/lib/systemd/system/etc.mount /lib/systemd/system/local-fs.target.wants/etc.mount"#)?; + + if let Some(users) = &config.users { + gen_add_users(file, users)?; + } + if let Some(systemd_services) = &config.systemd_service { + gen_systemd_services(file, systemd_services)?; + } + + writeln!( + file, + r#" +str=$(sed -n '/^root:/p' /etc/shadow | awk -F "root:" '{{print $2}}') +umask 0666 +mv /etc/shadow /etc/shadow_bak +sed -i '/^root:/d' /etc/shadow_bak +echo "root:""${{ROOT_PASSWD}}""${{str:1}}" > /etc/shadow +cat /etc/shadow_bak >> /etc/shadow +rm -rf /etc/shadow_bak + +dracut -f -v --add bootup /initramfs.img --kver "$(ls /lib/modules)" +rm -rf /usr/lib/dracut/modules.d/00bootup"# + )?; + + Ok(()) +} +/* endregion */ + +/* region: bootloader.sh */ +pub(crate) fn gen_bootloader(file: &mut File, arch: &str, legacy_bios: bool) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"set -eu +set -o pipefail +set -x + +function install_grub2 () {{"# + )?; + + if arch == "aarch64" || (arch == "x86_64" && !legacy_bios) { + writeln!( + file, + r#" cp -r /usr/lib/grub/x86_64-efi boot/efi/EFI/openEuler + eval "grub2-mkimage -d /usr/lib/grub/x86_64-efi -O x86_64-efi --output=/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" + + mkdir -p /boot/efi/EFI/BOOT/ + cp -f /boot/efi/EFI/openEuler/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI +}} +"# + )?; + } else { + writeln!( + file, + r#" GRUBNAME=$(which grub2-install) + echo "Installing GRUB2..." + FORCE_OPT=${{FORCE_OPT:-"--force"}} + TARGET_OPT=${{TARGET_OPT:-"--target=i386-pc"}} + + $GRUBNAME --modules="biosdisk part_msdos" "$FORCE_OPT" "$TARGET_OPT" "$DEVICE" +}} +"# + )?; + } + + writeln!( + file, + r#"install_grub2 +"# + )?; + Ok(()) +} +/* endregion */ + +/* region: rpmlist */ +pub(crate) fn gen_rpm_list(file: &mut File, rpmlist: &Vec) -> Result<()> { + for rpm in rpmlist { + writeln!(file, "{}", rpm)?; + } + Ok(()) +} +/* endregion */ + +/* region: grub.cfg */ +pub(crate) fn gen_grub_cfg(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"set pager=1 + +if [ -f ${{config_directory}}/grubenv ]; then + load_env -f ${{config_directory}}/grubenv +elif [ -s $prefix/grubenv ]; then + load_env +fi +if [ "${{next_entry}}" ] ; then + set default="${{next_entry}}" + set next_entry= + save_env next_entry + set boot_once=true +else + set default="${{saved_entry}}" +fi + +if [ x"${{feature_menuentry_id}}" = xy ]; then + menuentry_id_option="--id" +else + menuentry_id_option="" +fi + +export menuentry_id_option + +if [ "${{prev_saved_entry}}" ]; then + set saved_entry="${{prev_saved_entry}}" + save_env saved_entry + set prev_saved_entry= + save_env prev_saved_entry + set boot_once=true +fi + +function savedefault {{ + if [ -z "${{boot_once}}" ]; then + saved_entry="${{chosen}}" + save_env saved_entry + fi +}} + +function load_video {{ + if [ x$feature_all_video_module = xy ]; then + insmod all_video + else + insmod efi_gop + insmod efi_uga + insmod ieee1275_fb + insmod vbe + insmod vga + insmod video_bochs + insmod video_cirrus + fi +}} + +terminal_output console +if [ x$feature_timeout_style = xy ] ; then + set timeout_style=menu + set timeout=5 +# Fallback normal timeout code in case the timeout_style feature is +# unavailable. +else + set timeout=5 +fi +set superusers="root" +### END /etc/grub.d/00_header ### + +### BEGIN /etc/grub.d/01_users ### +if [ -f ${{prefix}}/user.cfg ]; then + source ${{prefix}}/user.cfg + if [ -n "${{GRUB2_PASSWORD}}" ]; then + set superusers="root" + export superusers + password_pbkdf2 root ${{GRUB2_PASSWORD}} + fi +fi +### END /etc/grub.d/01_users ### + +### BEGIN /etc/grub.d/10_linux ### +menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' {{ + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt2' + linux /boot/vmlinuz root=/dev/vda2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +}} + +menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' {{ + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt3' + linux /boot/vmlinuz root=/dev/vda3 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +}} + +### END /etc/grub.d/10_linux ### + +### BEGIN /etc/grub.d/10_reset_boot_success ### +# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry +if [ "${{boot_success}}" = "1" -o "${{boot_indeterminate}}" = "1" ]; then + set menu_hide_ok=1 +else + set menu_hide_ok=0 +fi +# Reset boot_indeterminate after a successful boot +if [ "${{boot_success}}" = "1" ] ; then + set boot_indeterminate=0 +# Avoid boot_indeterminate causing the menu to be hidden more then once +elif [ "${{boot_indeterminate}}" = "1" ]; then + set boot_indeterminate=2 +fi +# Reset boot_success for current boot +set boot_success=0 +save_env boot_success boot_indeterminate +### END /etc/grub.d/10_reset_boot_success ### + +### BEGIN /etc/grub.d/12_menu_auto_hide ### +if [ x$feature_timeout_style = xy ] ; then + if [ "${{menu_show_once}}" ]; then + unset menu_show_once + save_env menu_show_once + set timeout_style=menu + set timeout=60 + elif [ "${{menu_auto_hide}}" -a "${{menu_hide_ok}}" = "1" ]; then + set orig_timeout_style=${{timeout_style}} + set orig_timeout=${{timeout}} + if [ "${{fastboot}}" = "1" ]; then + # timeout_style=menu + timeout=0 avoids the countdown code keypress check + set timeout_style=menu + set timeout=0 + else + set timeout_style=hidden + set timeout=1 + fi + fi +fi +### END /etc/grub.d/12_menu_auto_hide ### + +### BEGIN /etc/grub.d/20_linux_xen ### +### END /etc/grub.d/20_linux_xen ### + +### BEGIN /etc/grub.d/20_ppc_terminfo ### +### END /etc/grub.d/20_ppc_terminfo ### + +### BEGIN /etc/grub.d/30_uefi-firmware ### +### END /etc/grub.d/30_uefi-firmware ### + +### BEGIN /etc/grub.d/40_custom ### +# This file provides an easy way to add custom menu entries. Simply type the +# menu entries you want to add after this comment. Be careful not to change +# the 'exec tail' line above. +### END /etc/grub.d/40_custom ### + +### BEGIN /etc/grub.d/41_custom ### +if [ -f ${{config_directory}}/custom.cfg ]; then + source ${{config_directory}}/custom.cfg +elif [ -z "${{config_directory}}" -a -f $prefix/custom.cfg ]; then + source $prefix/custom.cfg; +fi +### END /etc/grub.d/41_custom ### +"# + )?; + Ok(()) +} +/* endregion */ + +/* region: 00bootup */ +// 00bootup/global.cfg +pub(crate) fn gen_global_cfg(file: &mut File, pxe_config: &PxeConfig) -> Result<()> { + writeln!( + file, + r#"# rootfs file name +rootfs_name={} + +# select the target disk to install kubeOS +disk={} + +# pxe server ip address where stores the rootfs on the http server +server_ip={} +# target machine ip +local_ip={} +# target machine route +route_ip={} +# target machine netmask +netmask={} +# target machine netDevice name +net_name={} +"#, + pxe_config.rootfs_name, + pxe_config.disk, + pxe_config.server_ip, + pxe_config.local_ip, + pxe_config.route_ip, + pxe_config.netmask, + pxe_config.net_name + )?; + Ok(()) +} + +// 00bootup/module-setup.sh +pub(crate) fn gen_module_setup(file: &mut File) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"check() {{ + return 0 +}} + +depends() {{ + echo systemd +}} + +install() {{ + inst_multiple -o grub2-mkimage mkfs.ext4 mkfs.vfat lsblk tar cpio gunzip lspci parted dhclient ifconfig curl hwinfo head tee arch df awk route + inst_hook mount 00 "$moddir/mount.sh" + inst_simple "$moddir/mount.sh" "/mount.sh" + inst_simple "$moddir/Global.cfg" "/Global.cfg" +}} + +installkernel() {{ + hostonly='' + instmods='drivers/ata drivers/nvme drivers/scsi drivers/net fs/fat fs/nls' +}} +"# + )?; + Ok(()) +} + +// 00bootup/mount.sh +pub(crate) fn gen_mount(file: &mut File) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; + + writeln!( + file, + r#"arch=$(arch) +min_size=8 +log=/install.log + +source ./Global.cfg + +function CheckSpace() {{ + local disk_ava + disk_ava="$(parted -l | grep "${{disk}}" | awk '{{print $3}}')" + if echo "${{disk_ava}}" | grep "[GT]B$"; then + if echo "${{disk_ava}}" | grep GB$; then + disk_ava="$(echo "${{disk_ava}}" | awk -F G '{{print $1}}' | awk -F . '{{print $1}}')" + if [ "${{disk_ava}}" -lt ${{min_size}} ]; then + echo "The available disk space is not enough, at least ${{min_size}}GB." | tee -a ${{log}} + return 1 + fi + fi + else + echo "The available disk space is not enough, at least ${{min_size}}G." | tee -a ${{log}} + return 1 + fi + + return 0 +}} + +function mount_proc_dev_sys() {{ + local tmp_root=$1 + mount -t proc none "${{tmp_root}}/proc" + mount --bind /dev "${{tmp_root}}/dev" + mount --bind /dev/pts "${{tmp_root}}/dev/pts" + mount -t sysfs none "${{tmp_root}}/sys" +}} + +function GetDisk() {{ + mapfile -t disks < <(hwinfo --disk --short 2>&1 | grep -vi "^disk" | awk '{{print $1}}') + if [ ${{#disks[*]}} -gt 0 ]; then + if [ -n "${{disk}}" ] && echo "${{disks[@]}}" | grep -wq "${{disk}}" ; then + echo "${{disk}} exists, start partition" | tee -a ${{log}} + else + echo "disk not exist, please choose correct disk" | tee -a ${{log}} + fi + else + echo "no disk found" | tee -a ${{log}} + return 1 + fi + CheckSpace + local status=$? + if [ $status -ne 0 ]; then + echo "no enough space on ${{disk}}" | tee -a ${{log}} + return 1 + fi + + return 0 +}} + +function PartitionAndFormatting() {{ + echo "Partitioning and formatting disk $disk..." + # partition and format + parted "${{disk}}" -s mklabel gpt >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + parted "${{disk}}" -s mkpart primary fat16 1M 100M >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + parted "${{disk}}" -s mkpart primary ext4 100M 2600M >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + parted "${{disk}}" -s mkpart primary ext4 2600M 5100M >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + parted "${{disk}}" -s mkpart primary ext4 5100M 100% >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + parted "${{disk}}" -s set 1 boot on >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "partition failed" | tee -a ${{log}} + return 1 + fi + + mkfs.vfat -n "BOOT" "${{disk}}"1 >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "format failed" | tee -a ${{log}} + return 1 + fi + + mkfs.ext4 -L "ROOT-A" "${{disk}}"2 >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "format failed" | tee -a ${{log}} + return 1 + fi + + mkfs.ext4 -L "ROOT-B" "${{disk}}"3 >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "format failed" | tee -a ${{log}} + return 1 + fi + + mkfs.ext4 -L "PERSIST" "${{disk}}"4 >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "format failed" | tee -a ${{log}} + return 1 + fi + + return 0 +}} + +function InitNetwork() {{ + echo "Initializing network..." + mapfile -t netNames < <(ifconfig -a | awk '{{print $1}}' | grep : | grep '^e' | awk -F: '{{print $1}}') + if [ ${{#netNames[*]}} -gt 0 ]; then + if [ -n "${{net_name}}" ] && echo "${{netNames[@]}}" | grep -wq "${{net_name}}" ; then + echo "${{net_name}} exists, start set ip" | tee -a ${{log}} + else + echo "net_name not exist, choose default net" | tee -a ${{log}} + net_name=${{netNames[0]}} + fi + else + echo "no net Device found" | tee -a ${{log}} + return 1 + fi + + ifconfig "${{net_name}}" up + local status=$? + if [ $status -ne 0 ]; then + echo "load net card failed" | tee -a ${{log}} + return 1 + fi + sleep 3 + + ifconfig "${{net_name}}" "${{local_ip}}" netmask "${{netmask}}" >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "ip set failed" | tee -a ${{log}} + return 1 + fi + sleep 3 + + route add default gw "${{route_ip}}" >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "add route failed" | tee -a ${{log}} + return 1 + fi + sleep 3 + return 0 +}} + +function MountRoot() {{ + echo "Mounting rootfs..." + # mount rootfs + mount "${{disk}}"2 /sysroot >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "mount rootfs failed" | tee -a ${{log}} + return 1 + fi + + return 0 +}} + +function MountPersist() {{ + echo "Mounting persist" + mount "${{disk}}"4 /sysroot/persist >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "mount persist failed" | tee -a ${{log}} + return 1 + fi + mkdir /sysroot/persist/{{var,etc,etcwork}} + mkdir -p /sysroot/persist/etc/KubeOS/certs + return 0 +}} + +function MountBoot() {{ + echo "Mounting boot" + mkdir -p /sysroot/boot/efi + mount "${{disk}}"1 /sysroot/boot/efi >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "mount boot failed" | tee -a ${{log}} + return 1 + fi + return 0 +}} + +function GetRootfs() {{ + echo "Downloading rootfs..." + + curl -o /"${{rootfs_name}}" http://"${{server_ip}}"/"${{rootfs_name}}" + if [ ! -e "/${{rootfs_name}}" ]; then + echo "download rootfs failed" | tee -a ${{log}} + return 1 + fi + + tar -xf /"${{rootfs_name}}" -C /sysroot + local status=$? + if [ $status -ne 0 ]; then + echo "decompose rootfs failed" | tee -a ${{log}} + return 1 + fi + + rm -rf "${{rootfs_name:?}}" + mount -o remount,ro "${{disk}}"2 /sysroot >> ${{log}} 2>&1 + return 0 +}} + +function Inst_Grub2_x86() {{ + # copy the files that boot need + cp -r /sysroot/usr/lib/grub/x86_64-efi /sysroot/boot/efi/EFI/openEuler + eval "grub2-mkimage -d /sysroot/usr/lib/grub/x86_64-efi -O x86_64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "grub2-mkimage on x86 failed" | tee -a ${{log}} + return 1 + fi + + mkdir -p /sysroot/boot/efi/EFI/BOOT/ + cp -f /sysroot/boot/efi/EFI/openEuler/grubx64.efi /sysroot/boot/efi/EFI/BOOT/BOOTX64.EFI + + return 0 +}} + +function Inst_Grub2_aarch64() {{ + cp -r /sysroot/usr/lib/grub/arm64-efi /sysroot/boot/efi/EFI/openEuler/ + eval "grub2-mkimage -d /sysroot/usr/lib/grub/arm64-efi -O arm64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${{log}} 2>&1 + local status=$? + if [ $status -ne 0 ]; then + echo "grub2-mkimage on aarch64 failed" | tee -a ${{log}} + return 1 + fi + + mkdir -p /sysroot/boot/efi/EFI/BOOT/ + cp -f /sysroot/boot/efi/EFI/openEuler/grubaa64.efi /sysroot/boot/efi/EFI/BOOT/BOOTAA64.EFI + + return 0 +}} + +function SetBoot() {{ + # mount boot + echo "Setting boot" + + if [ "$arch" == "x86_64" ]; then + Inst_Grub2_x86 + local status=$? + if [ $status -ne 0 ]; then + echo "install grub on x86 failed" | tee -a ${{log}} + return 1 + fi + fi + + if [ "$arch" == "aarch64" ]; then + Inst_Grub2_aarch64 + local status=$? + if [ $status -ne 0 ]; then + echo "install grub on aarch64 failed" | tee -a ${{log}} + return 1 + fi + fi + sed -i 's#/dev/sda#'"${{disk}}"'#g' /sysroot/boot/efi/EFI/openEuler/grub.cfg + + return 0 +}} + +function Bootup_Main() {{ + # get disk + echo "Checking disk info..." | tee -a ${{log}} + GetDisk + local status=$? + if [ $status -ne 0 ]; then + echo "Checking disk info failed" | tee -a ${{log}} + return 1 + fi + + # partition and format disk + echo "Partion and formatting..." | tee -a ${{log}} + PartitionAndFormatting + local status=$? + if [ $status -ne 0 ]; then + echo "Partition and formatting disk failed" | tee -a ${{log}} + return 1 + fi + + # init network + echo "Initializing network..." | tee -a ${{log}} + InitNetwork + local status=$? + if [ $status -ne 0 ]; then + echo "Initializing network failed" | tee -a ${{log}} + return 1 + fi + + # mount partitions + + # mount boot + echo "Mounting root..." | tee -a ${{log}} + MountRoot + local status=$? + if [ $status -ne 0 ]; then + echo "Mounting root failed" | tee -a ${{log}} + return 1 + fi + + echo "Mounting boot..." | tee -a ${{log}} + MountBoot + local status=$? + if [ $status -ne 0 ]; then + echo "Mounting boot failed" | tee -a ${{log}} + return 1 + fi + + # download rootfs + echo "Downloading rootfs..." | tee -a ${{log}} + GetRootfs + local status=$? + if [ $status -ne 0 ]; then + echo "Downloading rootfs failed" | tee -a ${{log}} + return 1 + fi + mount_proc_dev_sys /sysroot + # set boot + echo "Setting boot..." | tee -a ${{log}} + SetBoot + local status=$? + if [ $status -ne 0 ]; then + echo "Setting boot failed" | tee -a ${{log}} + return 1 + fi + # mount persist + echo "Mounting persist..." | tee -a ${{log}} + MountPersist + local status=$? + if [ $status -ne 0 ]; then + echo "Mounting persist failed" | tee -a ${{log}} + return 1 + fi + return 0 +}} + +Bootup_Main +ret=$? +if [ ${{ret}} -eq 0 ]; then + echo "kubeOS install success! switch to root" | tee -a ${{log}} + cp ${{log}} /sysroot/persist +else + echo "kubeOS install failed, see install.log" | tee -a ${{log}} +fi + +"# + )?; + Ok(()) +} +/* endregion */ + +/* region: dockerfile */ +pub(crate) fn gen_dockerfile(file: &mut File) -> Result<()> { + writeln!( + file, + r#"FROM scratch +COPY os.tar / +CMD ["/bin/sh"] +"# + )?; + Ok(()) +} +/* endregion */ + +/* region: admin-container */ +// admin-container/dockerfile +pub(crate) fn gen_admin_dockerfile(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"FROM openeuler-22.03-lts +MAINTAINER + +RUN yum -y install openssh-clients util-linux + +ADD ./sysmaster-0.2.3-1.oe2203.aarch64.rpm /home +RUN rpm -ivh /home/sysmaster-0.2.3-1.oe2203.aarch64.rpm + +COPY ./hostshell /usr/bin/ +COPY ./set-ssh-pub-key.sh /usr/local/bin +COPY ./set-ssh-pub-key.service /usr/lib/sysmaster + +EXPOSE 22 +# set sshd.service and set-ssh-pub-key.service pulled up by default +RUN sed -i 's/sysinit.target/sysinit.target;sshd.service;set-ssh-pub-key.service/g' /usr/lib/sysmaster/basic.target + +CMD ["/usr/lib/sysmaster/init"] +"# + )?; + Ok(()) +} + +// admin-container/set-ssh-pub-key.service +pub(crate) fn gen_set_ssh_pub_key_service(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description="set ssh authorized keys according to the secret which is set by user" + +[Service] +ExecStart="/usr/local/bin/set-ssh-pub-key.sh" +"# + )?; + Ok(()) +} + +// admin-container/set-ssh-pub-key.sh +pub(crate) fn gen_set_ssh_pub_key(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"ssh_pub=$(cat /etc/secret-volume/ssh-pub-key) +ssh_dir="/root/.ssh" +authorized_file="$ssh_dir/authorized_keys" + +if [ ! -d "$ssh_dir" ]; then + mkdir "$ssh_dir" + chmod 700 "$ssh_dir" +fi + +if [ ! -f "$authorized_file" ]; then + touch "$authorized_file" + chmod 600 "$authorized_file" +fi + +echo "$ssh_pub" >> "$authorized_file" +"# + )?; + Ok(()) +} +/* endregion */ + +/* region: misc-files */ +// misc-files/boot-efi.mount +pub(crate) fn gen_boot_efi_mount(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=grub2 Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/BOOT +Where=/boot/efi +Type=vfat +Options=defaults + +[Install] +WantedBy=local-fs.target +"# + )?; + Ok(()) +} + +// misc-files/boot-grub2.mount +pub(crate) fn gen_boot_grub2_mount(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=grub2 Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/GRUB2 +Where=/boot/grub2 +Type=ext4 +Options=defaults + +[Install] +WantedBy=local-fs.target +"# + )?; + Ok(()) +} + +// misc-files/etc.mount +pub(crate) fn gen_etc_mount(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=etc Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +Wants=persist.mount +After=persist.mount + +[Mount] +What=overlay +Where=/etc +Type=overlay +Options=upperdir=/persist/etc,lowerdir=/etc,workdir=/persist/etcwork + +[Install] +WantedBy=local-fs.target +"# + )?; + Ok(()) +} + +// misc-files/os-agent.service +pub(crate) fn gen_os_agent_service(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=Agent For KubeOS + +[Service] +Environment=GOTRACEBACK=crash +ExecStart=/usr/bin/os-agent +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target +"# + )?; + Ok(()) +} + +// misc-files/os-release +pub(crate) fn gen_os_release(file: &mut File) -> Result<()> { + writeln!( + file, + r#"NAME=KubeOS +ID=KubeOS +"# + )?; + Ok(()) +} + +// misc-files/persist.mount +pub(crate) fn gen_persist_mount(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=PERSIST Dir (/persist) +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/PERSIST +Where=/persist +Type=ext4 +Options=defaults + +[Install] +WantedBy=local-fs.target +"# + )?; + Ok(()) +} + +// misc-files/var.mount +pub(crate) fn gen_var_mount(file: &mut File) -> Result<()> { + gen_copyright(file)?; + + writeln!( + file, + r#"[Unit] +Description=var Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +Wants=persist.mount +After=persist.mount + +[Mount] +What=/persist/var +Where=/var +Type=node +Options=bind + +[Install] +WantedBy=local-fs.target +"# + )?; + Ok(()) +} +/* endregion */ diff --git a/KubeOS-Rust/kbimg/src/utils.rs b/KubeOS-Rust/kbimg/src/utils.rs new file mode 100644 index 00000000..c7ea5505 --- /dev/null +++ b/KubeOS-Rust/kbimg/src/utils.rs @@ -0,0 +1,126 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{fs, os::unix::fs::PermissionsExt, path::PathBuf, process::Command}; + +use anyhow::bail; + +use crate::commands::PxeConfig; + +pub(crate) fn execute_scripts(script: PathBuf) -> anyhow::Result<()> { + if !script.exists() { + bail!("Script does not exist: {:?}", script); + } + let status = Command::new("bash").arg(&script).status()?; + if !status.success() { + bail!("Failed to execute script: {}\n", script.display()); + } + Ok(()) +} + +pub(crate) fn set_permissions(path: &str, permission_value: u32) -> anyhow::Result<()> { + let metadata = fs::metadata(path)?; + let mut permissions = metadata.permissions(); + permissions.set_mode(permission_value); + fs::set_permissions(path, permissions)?; + Ok(()) +} + +/// Check if the input parameter is valid +pub(crate) fn is_valid_param + std::fmt::Debug>(param: S) -> bool { + let special_chars = vec!["|", ";", "&", "&&", "||", ">", ">>", "<", ",", "#", "!", "$"]; + !param.as_ref().chars().any(|c| special_chars.contains(&c.to_string().as_str())) +} + +/// Check if the path exists and is indeed a file +pub(crate) fn is_file_valid(msg: &str, path: &PathBuf) -> anyhow::Result<()> { + if !path.exists() { + bail!("{} does not exist: {:?}", msg, path); + } + if !path.is_file() { + bail!("{} exists but is not a file: {:?}", msg, path); + } + Ok(()) +} + +/// Check if addr is valid +pub(crate) fn is_addr_valid(addr: &str) -> bool { + let ip_pattern = regex::Regex::new(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$").unwrap(); + if !ip_pattern.is_match(addr) { + return false; + } + + for quad in addr.split('.') { + if let Ok(num) = quad.parse::() { + if num <= 255 { + continue; + } + } + return false; + } + + true +} + +/// Check pxe config +pub(crate) fn check_pxe_conf_valid(pxe_config: &PxeConfig) -> anyhow::Result<()> { + if !is_addr_valid(&pxe_config.server_ip) { + bail!("address {} is invalid, please check input", &pxe_config.server_ip) + } + if !is_addr_valid(&pxe_config.local_ip) { + bail!("address {} is invalid, please check input", &pxe_config.local_ip) + } + if !is_addr_valid(&pxe_config.route_ip) { + bail!("address {} is invalid, please check input", &pxe_config.route_ip) + } + if !is_addr_valid(&pxe_config.netmask) { + bail!("address {} is invalid, please check input", &pxe_config.netmask) + } + Ok(()) +} + +/// Get architecture +pub(crate) fn get_arch() -> String { + let output = std::process::Command::new("arch").output().expect("Failed to execute `arch` command"); + String::from_utf8_lossy(&output.stdout).trim().to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_is_valid_param() { + init(); + assert_eq!(is_valid_param("test"), true); + assert_eq!(is_valid_param("test|test"), false); + assert_eq!(is_valid_param("test;test"), false); + assert_eq!(is_valid_param("test&test"), false); + assert_eq!(is_valid_param("test&&test"), false); + assert_eq!(is_valid_param("test||test"), false); + assert_eq!(is_valid_param("test>test"), false); + assert_eq!(is_valid_param("test>>test"), false); + assert_eq!(is_valid_param("test Self { let mut os = OS::set_os_default(); os.spec.osversion = String::from("KubeOS v2"); @@ -676,6 +677,10 @@ impl Default for OSSpec { clientkey: Some(String::from("")), sysconfigs: Some(Configs { version: Some(String::from("v1")), configs: None }), upgradeconfigs: Some(Configs { version: Some(String::from("v1")), configs: None }), + nodeselector:None, + timeinterval:None, + timewindow:None, + executionmode:None, } } } diff --git a/KubeOS-Rust/proxy/src/controller/controller.rs b/KubeOS-Rust/proxy/src/controller/controller.rs index 80a85d1c..40405b2d 100644 --- a/KubeOS-Rust/proxy/src/controller/controller.rs +++ b/KubeOS-Rust/proxy/src/controller/controller.rs @@ -30,8 +30,8 @@ use super::{ crd::{Configs, Content, OSInstance, OS}, utils::{check_version, get_config_version, ConfigOperation, ConfigType}, values::{ - LABEL_UPGRADING, NODE_STATUS_CONFIG, NODE_STATUS_IDLE, OPERATION_TYPE_ROLLBACK, OPERATION_TYPE_UPGRADE, - REQUEUE_ERROR, REQUEUE_NORMAL, + LABEL_UPGRADING, NODE_STATUS_CONFIG, NODE_STATUS_IDLE, OPERATION_TYPE_ROLLBACK, OPERATION_TYPE_UPGRADE, + OSINSTANCE_NAMESPACE, REQUEUE_ERROR, REQUEUE_NORMAL,LABEL_CONFIGURING, NO_REQUEUE, }, }; @@ -46,10 +46,20 @@ pub async fn reconcile( let namespace: String = os_cr .namespace() .ok_or(Error::MissingObjectKey { resource: "os".to_string(), value: "namespace".to_string() })?; - proxy_controller.check_osi_exisit(&namespace, &node_name).await?; - let controller_res = proxy_controller.get_resources(&namespace, &node_name).await?; + proxy_controller.check_osi_exisit(&node_name).await?; + let controller_res = proxy_controller.get_resources(&node_name).await?; let node = controller_res.node; let mut osinstance = controller_res.osinstance; + if let Some(namespacedname) = osinstance.spec.namespacedname.as_ref(){ + debug!("osinstance correspending os name is {}, namespace is {}",namespacedname.name,namespacedname.namespace); + if !(namespacedname.name == os_cr.name() && namespacedname.namespace == namespace){ + debug!("current os cr name:{}, namespace:{} is not belong to this node",os_cr.name(),namespace); + return Ok(NO_REQUEUE) + } + }else { + return Ok(REQUEUE_NORMAL) + } + let node_os_image = &node .status .as_ref() @@ -151,8 +161,8 @@ impl ProxyController { } impl ProxyController { - async fn check_osi_exisit(&self, namespace: &str, node_name: &str) -> Result<(), Error> { - let osi_api: Api = Api::namespaced(self.k8s_client.clone(), namespace); + async fn check_osi_exisit(&self, node_name: &str) -> Result<(), Error> { + let osi_api: Api = Api::namespaced(self.k8s_client.clone(), OSINSTANCE_NAMESPACE); match osi_api.get(node_name).await { Ok(osi) => { debug!("osinstance is exist {:?}", osi.name()); @@ -160,15 +170,15 @@ impl ProxyController { }, Err(kube::Error::Api(ErrorResponse { reason, .. })) if &reason == "NotFound" => { info!("Create OSInstance {}", node_name); - self.controller_client.create_osinstance(node_name, namespace).await?; + self.controller_client.create_osinstance(node_name, OSINSTANCE_NAMESPACE).await?; Ok(()) }, Err(err) => Err(Error::KubeClient { source: err }), } } - async fn get_resources(&self, namespace: &str, node_name: &str) -> Result { - let osi_api: Api = Api::namespaced(self.k8s_client.clone(), namespace); + async fn get_resources(&self, node_name: &str) -> Result { + let osi_api: Api = Api::namespaced(self.k8s_client.clone(), OSINSTANCE_NAMESPACE); let osinstance_cr = osi_api.get(node_name).await?; let node_api: Api = Api::all(self.k8s_client.clone()); let node_cr = node_api.get(node_name).await?; @@ -188,6 +198,10 @@ impl ProxyController { if labels.contains_key(LABEL_UPGRADING) { labels.remove(LABEL_UPGRADING); node = node_api.replace(&node.name(), &PostParams::default(), &node).await?; + }else if labels.contains_key(LABEL_CONFIGURING) { + labels.remove(LABEL_CONFIGURING); + node = node_api.replace(&node.name(), &PostParams::default(), &node).await?; + } if let Some(node_spec) = &node.spec { if let Some(node_unschedulable) = node_spec.unschedulable { @@ -228,6 +242,7 @@ impl ProxyController { value: String::from("namespace"), })?; osinstance.spec.nodestatus = NODE_STATUS_IDLE.to_string(); + osinstance.spec.namespacedname = None; self.controller_client.update_osinstance_spec(&osinstance.name(), &namespace, &osinstance.spec).await?; } Ok(()) diff --git a/KubeOS-Rust/proxy/src/controller/crd.rs b/KubeOS-Rust/proxy/src/controller/crd.rs index 41f333e8..36e14d59 100644 --- a/KubeOS-Rust/proxy/src/controller/crd.rs +++ b/KubeOS-Rust/proxy/src/controller/crd.rs @@ -32,6 +32,11 @@ pub struct OSSpec { pub clientkey: Option, pub sysconfigs: Option, pub upgradeconfigs: Option, + pub nodeselector:Option, + pub timewindow: Option, + pub timeinterval: Option, + pub executionmode:Option, + } #[derive(CustomResource, Debug, Clone, Deserialize, Serialize, JsonSchema)] @@ -46,6 +51,7 @@ pub struct OSSpec { )] pub struct OSInstanceSpec { pub nodestatus: String, + pub namespacedname: Option, pub sysconfigs: Option, pub upgradeconfigs: Option, } @@ -75,3 +81,14 @@ pub struct Content { pub value: Option, pub operation: Option, } + +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct NamespacedName{ + pub namespace: String, + pub name: String, +} +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct TimeWindow{ + pub starttime:String, + pub endtime:String, +} \ No newline at end of file diff --git a/KubeOS-Rust/proxy/src/controller/values.rs b/KubeOS-Rust/proxy/src/controller/values.rs index dec905a9..2edb3dcb 100644 --- a/KubeOS-Rust/proxy/src/controller/values.rs +++ b/KubeOS-Rust/proxy/src/controller/values.rs @@ -15,9 +15,11 @@ use tokio::time::Duration; pub const LABEL_OSINSTANCE: &str = "upgrade.openeuler.org/osinstance-node"; pub const LABEL_UPGRADING: &str = "upgrade.openeuler.org/upgrading"; +pub const LABEL_CONFIGURING: &str = "upgrade.openeuler.org/configuring"; pub const OSINSTANCE_API_VERSION: &str = "upgrade.openeuler.org/v1alpha1"; pub const OSINSTANCE_KIND: &str = "OSInstance"; +pub const OSINSTANCE_NAMESPACE: &str = "default"; pub const NODE_STATUS_IDLE: &str = "idle"; pub const NODE_STATUS_UPGRADE: &str = "upgrade"; @@ -29,5 +31,5 @@ pub const OPERATION_TYPE_ROLLBACK: &str = "rollback"; pub const SOCK_PATH: &str = "/run/os-agent/os-agent.sock"; pub const REQUEUE_NORMAL: ReconcilerAction = ReconcilerAction { requeue_after: Some(Duration::from_secs(15)) }; - pub const REQUEUE_ERROR: ReconcilerAction = ReconcilerAction { requeue_after: Some(Duration::from_secs(1)) }; +pub const NO_REQUEUE: ReconcilerAction = ReconcilerAction { requeue_after: None }; diff --git a/Makefile b/Makefile index d4cd71e3..dc1ac920 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ all: proxy operator agent hostshell rust-kubeos # Build binary proxy: - ${GO_BUILD_CGO} ${LD_FLAGS} -o bin/proxy cmd/proxy/main.go + ${GO_BUILD_CGO} ${LD_FLAGS} -o bin/proxy cmd/proxy/main.go strip bin/proxy operator: @@ -76,9 +76,16 @@ rust-proxy: rust-agent: cd KubeOS-Rust && ${RUSTFLAGS} cargo build --profile release --target-dir ../bin/rust --package os-agent +rust-kbimg: + cd KubeOS-Rust && ${RUSTFLAGS} cargo build --profile release --target-dir ../bin/rust --package kbimg + +# clean binary +clean: + rm -rf bin + # Install CRDs into a cluster install: manifests - kubectl apply -f confg/crd + kubectl apply -f config/crd # Uninstall CRDs from a cluster uninstall: manifests @@ -111,7 +118,7 @@ generate: controller-gen $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." # Build the docker image -docker-build: operator proxy +docker-build: operator rust-proxy docker build --target operator -t ${IMG_OPERATOR} . docker build --target proxy -t ${IMG_PROXY} . diff --git a/api/v1alpha1/os_types.go b/api/v1alpha1/os_types.go index f9474b72..bde86d25 100644 --- a/api/v1alpha1/os_types.go +++ b/api/v1alpha1/os_types.go @@ -24,10 +24,12 @@ type OSSpec struct { CheckSum string `json:"checksum"` FlagSafe bool `json:"flagSafe"` MTLS bool `json:"mtls"` + // +kubebuilder:validation:Enum=docker;disk;containerd ImageType string `json:"imagetype"` ContainerImage string `json:"containerimage"` - OpsType string `json:"opstype"` - EvictPodForce bool `json:"evictpodforce"` + // +kubebuilder:validation:Enum=upgrade;config;rollback + OpsType string `json:"opstype"` + EvictPodForce bool `json:"evictpodforce"` // +kubebuilder:validation:Optional CaCert string `json:"cacert"` // +kubebuilder:validation:Optional @@ -38,6 +40,18 @@ type OSSpec struct { SysConfigs SysConfigs `json:"sysconfigs"` // +kubebuilder:validation:Optional UpgradeConfigs SysConfigs `json:"upgradeconfigs"` + // +kubebuilder:validation:Optional + // +kubebuilder:default:=no-label + NodeSelector string `json:"nodeselector"` + // +kubebuilder:validation:Optional + TimeWindow TimeWindow `json:"timewindow"` + // +kubebuilder:validation:Optional + // +kubebuilder:default:=15 + TimeInterval int `json:"timeinterval"` + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Enum=serial;parallel + // +kubebuilder:default:=parallel + ExecutionMode string `json:"executionmode"` } // +kubebuilder:subresource:status @@ -88,6 +102,11 @@ type Content struct { Operation string `json:"operation"` } +type TimeWindow struct { + StartTime string `json:"starttime"` + EndTime string `json:"endtime"` +} + // +kubebuilder:subresource:status // +kubebuilder:object:root=true @@ -114,11 +133,21 @@ type OSInstanceSpec struct { // +kubebuilder:validation:Optional NodeStatus string `json:"nodestatus"` // +kubebuilder:validation:Optional + NamespacedName NamespacedName `json:"namespacedname"` + // +kubebuilder:validation:Optional SysConfigs SysConfigs `json:"sysconfigs"` // +kubebuilder:validation:Optional UpgradeConfigs SysConfigs `json:"upgradeconfigs"` } +// NamespacedName defines name and namespace of os corresponding to osinstance +type NamespacedName struct { + // +kubebuilder:validation:Optional + Name string `json:"name"` + // +kubebuilder:validation:Optional + Namespace string `json:"namespace"` +} + // +kubebuilder:object:root=true // OSInstanceList is a list of OSInstance diff --git a/cmd/operator/controllers/operation.go b/cmd/operator/controllers/operation.go new file mode 100644 index 00000000..5ac3d6d4 --- /dev/null +++ b/cmd/operator/controllers/operation.go @@ -0,0 +1,282 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +// Package controllers contains the Reconcile of operator +package controllers + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + upgradev1 "openeuler.org/KubeOS/api/v1alpha1" + "openeuler.org/KubeOS/pkg/common" + "openeuler.org/KubeOS/pkg/values" +) + +type operation interface { + updateNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + nodes []corev1.Node, limit int) (int, []error) + updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + node *corev1.Node, osInstance *upgradev1.OSInstance) error + getOpsLabel() opsLabel +} + +type upgradeOps struct { + label opsLabel +} + +func (u upgradeOps) getOpsLabel() opsLabel { + return u.label +} + +func (u upgradeOps) updateNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + nodes []corev1.Node, limit int) (int, []error) { + var count = 0 + var errList []error + for _, node := range nodes { + if count >= limit { + break + } + osVersionNode := node.Status.NodeInfo.OSImage + if os.Spec.OSVersion != osVersionNode { + log.Info("Upgrading node " + node.Name) + var osInstance upgradev1.OSInstance + if err := r.Get(ctx, types.NamespacedName{Namespace: values.OsiNamespace, Name: node.Name}, &osInstance); err != nil { + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "osInstance not found "+node.Name, ", maybe the os-proxy initialization is not complete. "+ + "Restart the reconcile and wait until it is complete.") + return count, []error{err} + } + log.Error(err, "failed to get osInstance "+node.Name+"skip this node") + errList = append(errList, err) + continue + } + if err := u.updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { + log.Error(err, "failed to update node and osinstance ,skip this node ") + errList = append(errList, err) + continue + } + count++ + } + } + if count == 0 && os.Spec.ExecutionMode == ExecutionModeSerial { + if errList = deleteSerialLabel(ctx, r, nodes); len(errList) != 0 { + log.Error(nil, "failed to delete nodes serial label") + } + } + if len(errList) > 0 { + return count, errList + } + return count, nil + +} + +func deleteSerialLabel(ctx context.Context, r common.ReadStatusWriter, nodes []corev1.Node) []error { + var errList []error + for _, node := range nodes { + if _, ok := node.Labels[values.LabelSerial]; ok { + delete(node.Labels, values.LabelSerial) + if err := r.Update(ctx, &node); err != nil { + log.Error(err, "unable to delete serial label ", "node", node.Name+", skip this node") + errList = append(errList, err) + } + } + } + if len(errList) > 0 { + return errList + } + return nil +} + +func (u upgradeOps) updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + node *corev1.Node, osInstance *upgradev1.OSInstance) error { + if osInstance.Spec.UpgradeConfigs.Version != os.Spec.UpgradeConfigs.Version { + if err := deepCopySpecConfigs(os, osInstance, values.UpgradeConfigName); err != nil { + return err + } + } + if osInstance.Spec.SysConfigs.Version != os.Spec.SysConfigs.Version { + if err := deepCopySpecConfigs(os, osInstance, values.SysConfigName); err != nil { + return err + } + // exchange "grub.cmdline.current" and "grub.cmdline.next" + for i, config := range osInstance.Spec.SysConfigs.Configs { + if config.Model == "grub.cmdline.current" { + osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.next" + } + if config.Model == "grub.cmdline.next" { + osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.current" + } + } + } + osInstance.Spec.NodeStatus = values.NodeStatusUpgrade.String() + osInstance.Spec.NamespacedName = upgradev1.NamespacedName{Name: os.Name, Namespace: os.Namespace} + log.V(1).Info("Wait to update osinstance name:" + osInstance.Name + " node name is " + node.Name) + if err := r.Update(ctx, osInstance); err != nil { + log.Error(err, "unable to update", "osInstance", osInstance.Name) + return err + } + log.Info("Update osinstance spec successfully") + + node.Labels[values.LabelUpgrading] = "" + if err := r.Update(ctx, node); err != nil { + log.Error(err, "unable to label", "node", node.Name) + return err + } + log.Info("Add node upgrading label " + values.LabelUpgrading + " successfully") + return nil +} + +type configOps struct { + label opsLabel +} + +func (c configOps) getOpsLabel() opsLabel { + return c.label +} + +func (c configOps) updateNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + nodes []corev1.Node, limit int) (int, []error) { + var count = 0 + var errList []error + for _, node := range nodes { + if count >= limit { + break + } + var osInstance upgradev1.OSInstance + if err := r.Get(ctx, types.NamespacedName{Namespace: os.GetObjectMeta().GetNamespace(), Name: node.Name}, &osInstance); err != nil { + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "osInstance not found "+node.Name, ", maybe the os-proxy initialization is not complete. "+ + "Restart the reconcile and wait until it is complete.") + return count, []error{err} + } + log.Error(err, "failed to get osInstance "+node.Name+", skip this node") + errList = append(errList, err) + continue + } + if os.Spec.SysConfigs.Version != osInstance.Spec.SysConfigs.Version { + log.Info("Configuring node " + node.Name) + if err := c.updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { + log.Error(err, "failed to update node and osinstance ,skip this node ") + errList = append(errList, err) + continue + } + count++ + } + } + if count == 0 && os.Spec.ExecutionMode == ExecutionModeSerial { + if errList = deleteSerialLabel(ctx, r, nodes); len(errList) != 0 { + log.Error(nil, "failed to delete nodes serial label") + } + } + if len(errList) > 0 { + return count, errList + } + return count, errList + +} + +func (c configOps) updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + node *corev1.Node, osInstance *upgradev1.OSInstance) error { + if err := deepCopySpecConfigs(os, osInstance, values.SysConfigName); err != nil { + return err + } + osInstance.Spec.NodeStatus = values.NodeStatusConfig.String() + osInstance.Spec.NamespacedName = upgradev1.NamespacedName{Name: os.Name, Namespace: os.Namespace} + log.V(1).Info("Wait to update osinstance name:" + osInstance.Name + " node name is " + node.Name) + if err := r.Update(ctx, osInstance); err != nil { + log.Error(err, "unable to update", "osInstance", osInstance.Name) + return err + } + log.Info("Update osinstance spec successfully") + + node.Labels[values.LabelConfiguring] = "" + if err := r.Update(ctx, node); err != nil { + log.Error(err, "unable to label", "node", node.Name) + return err + } + log.Info("Add node configuring label " + values.LabelConfiguring + " successfully") + return nil +} + +type serialOps struct { + label opsLabel +} + +func (s serialOps) getOpsLabel() opsLabel { + return s.label +} + +func (s serialOps) updateNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + nodes []corev1.Node, limit int) (int, []error) { + var count int + var errList []error + for _, node := range nodes { + if count >= limit { + break + } + var osInstance upgradev1.OSInstance + if err := r.Get(ctx, types.NamespacedName{Namespace: os.GetObjectMeta().GetNamespace(), Name: node.Name}, &osInstance); err != nil { + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "osInstance not found "+node.Name, ", maybe the os-proxy initialization is not complete. "+ + "Restart the reconcile and wait until it is complete.") + return count, []error{err} + } + log.Error(err, "failed to get osInstance "+node.Name+", skip this node") + errList = append(errList, err) + continue + } + switch s.getOpsLabel().label { + case values.LabelUpgrading: + if os.Spec.OSVersion != node.Status.NodeInfo.OSImage { + log.Info("Add Serial Label to node " + node.Name) + if err := s.updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { + log.Error(err, "failed to update node and osinstance ,skip this node ") + errList = append(errList, err) + continue + } + count++ + } + case values.LabelConfiguring: + if os.Spec.SysConfigs.Version != osInstance.Spec.SysConfigs.Version { + log.Info("Add Serial Label to node " + node.Name) + if err := s.updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { + log.Error(err, "failed to update node and osinstance ,skip this node ") + errList = append(errList, err) + continue + } + count++ + } + default: + log.Error(nil, "ops "+s.getOpsLabel().label+" cannot be recognized") + return count, []error{fmt.Errorf("ops " + s.getOpsLabel().label + " cannot be recognized")} + } + } + if len(errList) == 0 { + return count, nil + } + return count, errList +} +func (s serialOps) updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, + node *corev1.Node, osInstance *upgradev1.OSInstance) error { + node.Labels[values.LabelSerial] = "" + if err := r.Update(ctx, node); err != nil { + log.Error(err, "unable to label", "node", node.Name) + return err + } + log.Info("Add node serial label " + values.LabelSerial + " successfully") + return nil +} diff --git a/cmd/operator/controllers/os_controller.go b/cmd/operator/controllers/os_controller.go index e04d59b1..9e2e8e49 100644 --- a/cmd/operator/controllers/os_controller.go +++ b/cmd/operator/controllers/os_controller.go @@ -17,6 +17,8 @@ import ( "context" "encoding/json" "fmt" + "strconv" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -56,40 +58,71 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re // Reconcile compares the actual state with the desired and updates the status of the resources e.g. nodes func Reconcile(ctx context.Context, r common.ReadStatusWriter, req ctrl.Request) (ctrl.Result, error) { - os, nodeNum, err := getAndUpdateOS(ctx, r, req.NamespacedName) + log.V(1).Info("start Reconcile of " + req.Name + " with namespace is " + req.Namespace) + os, err := getOSCr(ctx, r, req.NamespacedName) if err != nil { if errors.IsNotFound(err) { return values.NoRequeue, nil } return values.RequeueNow, err } + isWithinTimeWindow, err := isWithinTimeWindow(os.Spec.TimeWindow.StartTime, os.Spec.TimeWindow.EndTime) + if err != nil { + return values.RequeueNow, err + } + if !isWithinTimeWindow { + log.V(1).Info("not in time window, the start time is " + os.Spec.TimeWindow.StartTime + + " , the end time " + os.Spec.TimeWindow.EndTime) + return values.Requeue, nil + } ops := os.Spec.OpsType + var opsInsatnce operation switch ops { case "upgrade", "rollback": - limit, err := checkUpgrading(ctx, r, min(os.Spec.MaxUnavailable, nodeNum)) // adjust maxUnavailable if need - if err != nil { - return values.RequeueNow, err - } - if needRequeue, err := assignUpgrade(ctx, r, os, limit, req.Namespace); err != nil { - return values.RequeueNow, err - } else if needRequeue { - return values.Requeue, nil + opsInsatnce = upgradeOps{ + label: opsLabel{ + label: values.LabelUpgrading, + op: selection.DoesNotExist, + }, } case "config": - limit, err := checkConfig(ctx, r, min(os.Spec.MaxUnavailable, nodeNum)) + opsInsatnce = configOps{ + label: opsLabel{ + label: values.LabelConfiguring, + op: selection.DoesNotExist, + }, + } + default: + log.Error(nil, "operation "+ops+" cannot be recognized") + return values.Requeue, nil + } + commonNodesReq, err := newCommonsNodesRequirement(os.Spec.NodeSelector, + selection.Equals).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, err + } + allNodes, err := getNodes(ctx, r, 0, commonNodesReq...) + if err != nil { + return values.RequeueNow, err + } + switch os.Spec.ExecutionMode { + case ExecutionModeParallel: + result, err := excuteParallelOperation(ctx, r, os, opsInsatnce, len(allNodes)) if err != nil { - return values.RequeueNow, err + return values.RequeueNow, nil } - if needRequeue, err := assignConfig(ctx, r, os.Spec.SysConfigs, os.Spec.SysConfigs.Version, limit); err != nil { + return result, nil + case ExecutionModeSerial: + result, err := excuteSerialOperation(ctx, r, os, opsInsatnce, len(allNodes)) + if err != nil { return values.RequeueNow, err - } else if needRequeue { - return values.Requeue, nil } + return result, nil default: - log.Error(nil, "operation "+ops+" cannot be recognized") + log.Error(nil, "excutionMode "+os.Spec.ExecutionMode+" cannot be recognized") + return values.Requeue, nil } - return values.Requeue, nil } // SetupWithManager sets up the controller with the Manager. @@ -129,136 +162,17 @@ func (r *OSReconciler) DeleteOSInstance(e event.DeleteEvent, q workqueue.RateLim } } -func getAndUpdateOS(ctx context.Context, r common.ReadStatusWriter, name types.NamespacedName) (os upgradev1.OS, - nodeNum int, err error) { - if err = r.Get(ctx, name, &os); err != nil { +func getOSCr(ctx context.Context, r common.ReadStatusWriter, name types.NamespacedName) (upgradev1.OS, error) { + var os upgradev1.OS + if err := r.Get(ctx, name, &os); err != nil { log.Error(err, "unable to fetch OS") - return - } - - requirement, err := labels.NewRequirement(values.LabelMaster, selection.DoesNotExist, nil) - if err != nil { - log.Error(err, "unable to create requirement "+values.LabelMaster) - return - } - nodesItems, err := getNodes(ctx, r, 0, *requirement) - if err != nil { - log.Error(err, "get slave nodes fail") - return - } - nodeNum = len(nodesItems) - return -} - -func assignUpgrade(ctx context.Context, r common.ReadStatusWriter, os upgradev1.OS, limit int, - nameSpace string) (bool, error) { - requirement, err := labels.NewRequirement(values.LabelUpgrading, selection.DoesNotExist, nil) - if err != nil { - log.Error(err, "unable to create requirement "+values.LabelUpgrading) - return false, err - } - reqMaster, err := labels.NewRequirement(values.LabelMaster, selection.DoesNotExist, nil) - if err != nil { - log.Error(err, "unable to create requirement "+values.LabelMaster) - return false, err - } - - nodes, err := getNodes(ctx, r, limit+1, *requirement, *reqMaster) // one more to see if all nodes updated - if err != nil { - return false, err - } - - // Upgrade OS for selected nodes - count, err := upgradeNodes(ctx, r, &os, nodes, limit) - if err != nil { - return false, err - } - - return count >= limit, nil -} - -func upgradeNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, - nodes []corev1.Node, limit int) (int, error) { - var count int - for _, node := range nodes { - if count >= limit { - break - } - osVersionNode := node.Status.NodeInfo.OSImage - if os.Spec.OSVersion != osVersionNode { - var osInstance upgradev1.OSInstance - if err := r.Get(ctx, types.NamespacedName{Namespace: os.GetObjectMeta().GetNamespace(), Name: node.Name}, &osInstance); err != nil { - if err = client.IgnoreNotFound(err); err != nil { - log.Error(err, "failed to get osInstance "+node.Name) - return count, err - } - continue - } - if err := updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { - continue - } - count++ - } - } - return count, nil -} - -func updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, - node *corev1.Node, osInstance *upgradev1.OSInstance) error { - if osInstance.Spec.UpgradeConfigs.Version != os.Spec.UpgradeConfigs.Version { - if err := deepCopySpecConfigs(os, osInstance, values.UpgradeConfigName); err != nil { - return err - } + return upgradev1.OS{}, err } - if osInstance.Spec.SysConfigs.Version != os.Spec.SysConfigs.Version { - if err := deepCopySpecConfigs(os, osInstance, values.SysConfigName); err != nil { - return err - } - // exchange "grub.cmdline.current" and "grub.cmdline.next" - for i, config := range osInstance.Spec.SysConfigs.Configs { - if config.Model == "grub.cmdline.current" { - osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.next" - } - if config.Model == "grub.cmdline.next" { - osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.current" - } - } + if err := checkNodeSelector(ctx, r, os); err != nil { + log.Error(err, "nodeselector conficts") + return upgradev1.OS{}, err } - osInstance.Spec.NodeStatus = values.NodeStatusUpgrade.String() - if err := r.Update(ctx, osInstance); err != nil { - log.Error(err, "unable to update", "osInstance", osInstance.Name) - return err - } - node.Labels[values.LabelUpgrading] = "" - if err := r.Update(ctx, node); err != nil { - log.Error(err, "unable to label", "node", node.Name) - return err - } - return nil -} - -func assignConfig(ctx context.Context, r common.ReadStatusWriter, sysConfigs upgradev1.SysConfigs, - configVersion string, limit int) (bool, error) { - osInstances, err := getIdleOSInstances(ctx, r, limit+1) // one more to see if all node updated - if err != nil { - return false, err - } - var count = 0 - for _, osInstance := range osInstances { - if count >= limit { - break - } - configVersionNode := osInstance.Spec.SysConfigs.Version - if configVersion != configVersionNode { - count++ - osInstance.Spec.SysConfigs = sysConfigs - osInstance.Spec.NodeStatus = values.NodeStatusConfig.String() - if err = r.Update(ctx, &osInstance); err != nil { - log.Error(err, "unable update osInstance ", "osInstanceName ", osInstance.Name) - } - } - } - return count >= limit, nil + return os, nil } func getNodes(ctx context.Context, r common.ReadStatusWriter, limit int, @@ -272,48 +186,29 @@ func getNodes(ctx context.Context, r common.ReadStatusWriter, limit int, return nodeList.Items, nil } -func getIdleOSInstances(ctx context.Context, r common.ReadStatusWriter, limit int) ([]upgradev1.OSInstance, error) { - var osInstanceList upgradev1.OSInstanceList - opt := []client.ListOption{ - client.MatchingFields{values.OsiStatusName: values.NodeStatusIdle.String()}, - &client.ListOptions{Limit: int64(limit)}, - } - if err := r.List(ctx, &osInstanceList, opt...); err != nil { - log.Error(err, "unable to list nodes with requirements") - return nil, err - } - return osInstanceList.Items, nil -} - -func getConfigOSInstances(ctx context.Context, r common.ReadStatusWriter) ([]upgradev1.OSInstance, error) { - var osInstanceList upgradev1.OSInstanceList - if err := r.List(ctx, &osInstanceList, - client.MatchingFields{values.OsiStatusName: values.NodeStatusConfig.String()}); err != nil { - log.Error(err, "unable to list nodes with requirements") - return nil, err - } - return osInstanceList.Items, nil -} - -func checkUpgrading(ctx context.Context, r common.ReadStatusWriter, maxUnavailable int) (int, error) { - requirement, err := labels.NewRequirement(values.LabelUpgrading, selection.Exists, nil) - if err != nil { - log.Error(err, "unable to create requirement "+values.LabelUpgrading) - return 0, err - } - nodes, err := getNodes(ctx, r, 0, *requirement) +func calNodeLimit(ctx context.Context, r common.ReadStatusWriter, + maxUnavailable int, requirements []labels.Requirement) (int, error) { + nodes, err := getNodes(ctx, r, 0, requirements...) if err != nil { return 0, err } return maxUnavailable - len(nodes), nil } - -func checkConfig(ctx context.Context, r common.ReadStatusWriter, maxUnavailable int) (int, error) { - osInstances, err := getConfigOSInstances(ctx, r) +func assignOperation(ctx context.Context, r common.ReadStatusWriter, os upgradev1.OS, limit int, + opsInstance operation, requirements []labels.Requirement) (int, error) { + if limit == 0 { + return 0, nil + } + nodes, err := getNodes(ctx, r, limit+1, requirements...) // one more to see if all nodes updated if err != nil { return 0, err } - return maxUnavailable - len(osInstances), nil + log.V(1).Info("get wait to check nodes is " + strconv.Itoa(len(nodes))) + count, errLists := opsInstance.updateNodes(ctx, r, &os, nodes, limit) + if len(errLists) != 0 { + return 0, fmt.Errorf("update nodes and osinstance error") + } + return count, nil } func min(a, b int) int { @@ -347,3 +242,118 @@ func deepCopySpecConfigs(os *upgradev1.OS, osinstance *upgradev1.OSInstance, con } return nil } + +// Check whether the nodeselector conflicts with other nodeselector in OS CRs. If the nodeselector is empty, return the list of other nodeselectors. +func checkNodeSelector(ctx context.Context, r common.ReadStatusWriter, os upgradev1.OS) error { + var osList upgradev1.OSList + if err := r.List(ctx, &osList, &client.ListOptions{}); err != nil { + log.Error(err, "unable to list nodes with requirements") + return err + } + var sameNodeSelectorList []types.NamespacedName + for _, osItem := range osList.Items { + // Exclude current os, controller-runtime not supports multiple indexs as listoptions in current version, + // so cannot list os without current os use List function + if osItem.Name == os.Name && osItem.Namespace == os.Namespace { + continue + } + if os.Spec.NodeSelector == osItem.Spec.NodeSelector { + sameNodeSelectorList = append(sameNodeSelectorList, types.NamespacedName{ + Namespace: osItem.Namespace, + Name: osItem.Name, + }) + } + } + // If a node label corresponds to multiple OS CRs, upgrade or configuration information may conflict. + // As a result, an error is reported and returned when there are one-to-many relationships. + if len(sameNodeSelectorList) > 0 { + errorMessage := sameNodeSelectorList[0].String() + for i := 1; i < len(sameNodeSelectorList); i++ { + errorMessage = errorMessage + " , " + sameNodeSelectorList[i].String() + } + log.Error(nil, "OS CR "+os.Name+" in namespace "+os.Namespace+" has same nodeselector with "+errorMessage) + return fmt.Errorf("OS CR %s in namespace %s has same nodeselector with %s", os.Name, os.Namespace, errorMessage) + } + return nil +} + +func setTimeInterval(timeInterval int) ctrl.Result { + return ctrl.Result{Requeue: true, RequeueAfter: time.Duration(timeInterval) * time.Second} +} + +func excuteParallelOperation(ctx context.Context, r common.ReadStatusWriter, os upgradev1.OS, + opsInsatnce operation, nodeNum int) (ctrl.Result, error) { + opsLabel := opsInsatnce.getOpsLabel() + opsLabel.op = selection.Exists + opsNodesReq, err := newopsNodesRequirement(os.Spec.NodeSelector, + selection.Equals, opsLabel).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, nil + } + limit, err := calNodeLimit(ctx, r, min(os.Spec.MaxUnavailable, nodeNum), opsNodesReq) // adjust maxUnavailable if need + if err != nil { + return values.RequeueNow, nil + } + opsLabel.op = selection.DoesNotExist + noOpsNodesReq, err := newopsNodesRequirement(os.Spec.NodeSelector, + selection.Equals, opsLabel).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, nil + } + if _, err := assignOperation(ctx, r, os, limit, opsInsatnce, noOpsNodesReq); err != nil { + return values.RequeueNow, nil + } + return setTimeInterval(os.Spec.TimeInterval), nil +} + +func excuteSerialOperation(ctx context.Context, r common.ReadStatusWriter, os upgradev1.OS, + opsInsatnce operation, nodeNum int) (ctrl.Result, error) { + opsLabel := opsInsatnce.getOpsLabel() + opsLabel.op = selection.Exists + opsNodesReq, err := newopsNodesRequirement(os.Spec.NodeSelector, + selection.Equals, opsLabel).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, nil + } + opsNodeNum, err := getNodes(ctx, r, 0, opsNodesReq...) + if err != nil { + return values.RequeueNow, nil + } + if len(opsNodeNum) > 0 { + return values.Requeue, nil + } + + serialNodesRequirement, err := newSerialNodesRequirement(os.Spec.NodeSelector, + selection.Equals, selection.Exists).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, nil + } + serialNodeLimit, err := calNodeLimit(ctx, r, min(os.Spec.MaxUnavailable, nodeNum), serialNodesRequirement) + if err != nil { + return values.RequeueNow, nil + } + + noSerialNodesRequirement, err := newSerialNodesRequirement(os.Spec.NodeSelector, + selection.Equals, selection.DoesNotExist).createNodeRequirement(ctx, r) + if err != nil { + return values.RequeueNow, nil + } + // add serial label to node + serialOpsInstance := serialOps{ + label: opsInsatnce.getOpsLabel(), + } + if _, err := assignOperation(ctx, r, os, serialNodeLimit, serialOpsInstance, noSerialNodesRequirement); err != nil { + return values.RequeueNow, nil + } + + serialLimit := 1 // 1 is the number of operation nodes when excution mode in serial + count, err := assignOperation(ctx, r, os, serialLimit, opsInsatnce, serialNodesRequirement) + if err != nil { + return values.RequeueNow, nil + } + if count > 0 { + return values.Requeue, nil + } + return setTimeInterval(os.Spec.TimeInterval), nil + +} diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go index 8c5d1981..38bf4e61 100644 --- a/cmd/operator/controllers/os_controller_test.go +++ b/cmd/operator/controllers/os_controller_test.go @@ -455,6 +455,14 @@ var _ = Describe("OsController", func() { }, timeout, interval).Should(BeTrue()) Expect(configedOSIns1.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) Expect(configedOSIns1.Spec.SysConfigs.Version).Should(Equal("v2")) + existingNode1 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode1) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok := existingNode1.Labels[values.LabelConfiguring] + Expect(ok).Should(Equal(true)) configedOSIns2 := &upgradev1.OSInstance{} Eventually(func() bool { @@ -463,6 +471,14 @@ var _ = Describe("OsController", func() { }, timeout, interval).Should(BeTrue()) Expect(configedOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) Expect(configedOSIns2.Spec.SysConfigs.Version).Should(Equal("v2")) + existingNode2 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode2) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok = existingNode2.Labels[values.LabelConfiguring] + Expect(ok).Should(Equal(true)) }) }) @@ -749,6 +765,419 @@ var _ = Describe("OsController", func() { Expect(createdOS.Spec.SysConfigs.Configs[1]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}})) }) }) + + Context("When we want to upgrade node with nodes having NodeSelector label", func() { + It("Should only update node with NodeSelector label", func() { + ctx := context.Background() + // create Node1 + node1Name = "test-node-" + uuid.New().String() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, + Labels: map[string]string{ + "beta.kubernetes.io/os": "linux", + "upgrade.openeuler.org/node-selector": "openeuler", + }, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + OSImage: "KubeOS v1", + }, + }, + } + err := k8sClient.Create(ctx, node1) + Expect(err).ToNot(HaveOccurred()) + existingNode := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) + return err == nil + }, timeout, interval).Should(BeTrue()) + + // create OSInstance1 + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", + APIVersion: "upgrade.openeuler.org/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, + Labels: map[string]string{ + values.LabelOSinstance: node1Name, + }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{ + Version: "v1", + Configs: []upgradev1.SysConfig{}, + }, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + NodeStatus: values.NodeStatusIdle.String(), + }, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + + osInsCRLookupKey1 := types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey1, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) + + // create Node2 + node2Name := "test-node-" + uuid.New().String() + node2 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: node2Name, + Namespace: testNamespace, + Labels: map[string]string{ + "beta.kubernetes.io/os": "linux", + }, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + OSImage: "KubeOS v1", + }, + }, + } + err = k8sClient.Create(ctx, node2) + Expect(err).ToNot(HaveOccurred()) + existingNode = &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode) + return err == nil + }, timeout, interval).Should(BeTrue()) + + // create OSInstance2 + OSIns = &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", + APIVersion: "upgrade.openeuler.org/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: node2Name, + Namespace: testNamespace, + Labels: map[string]string{ + values.LabelOSinstance: node2Name, + }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + NodeStatus: values.NodeStatusIdle.String(), + }, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + + // Check that the corresponding OSIns CR has been created + osInsCRLookupKey2 := types.NamespacedName{Name: node2Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node2Name)) + + OS := &upgradev1.OS{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "upgrade.openeuler.org/v1alpha1", + Kind: "OS", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: OSName, + Namespace: testNamespace, + }, + Spec: upgradev1.OSSpec{ + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", + FlagSafe: true, + MTLS: false, + EvictPodForce: true, + NodeSelector: "openeuler", + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", + Configs: []upgradev1.SysConfig{ + { + Model: "kernel.sysctl", + Contents: []upgradev1.Content{ + {Key: "key1", Value: "a"}, + {Key: "key2", Value: "b"}, + }, + }, + }, + }, + UpgradeConfigs: upgradev1.SysConfigs{ + Version: "v2", + Configs: []upgradev1.SysConfig{ + {Model: "kernel.sysctl.persist", + Contents: []upgradev1.Content{ + {Key: "key1", Value: "a"}, + {Key: "key2", Value: "b"}, + }, + }}, + }, + }, + } + Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) + + osCRLookupKey := types.NamespacedName{Name: OSName, Namespace: testNamespace} + createdOS := &upgradev1.OS{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osCRLookupKey, createdOS) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) + + time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + existingNode1 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode1) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok := existingNode1.Labels[values.LabelUpgrading] + Expect(ok).Should(Equal(true)) + + upgradeOSIns1 := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey1, upgradeOSIns1) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(upgradeOSIns1.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) + Expect(upgradeOSIns1.Spec.UpgradeConfigs.Version).Should(Equal("v2")) + Expect(upgradeOSIns1.Spec.SysConfigs.Version).Should(Equal("v2")) + + existingNode2 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode2) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok = existingNode2.Labels[values.LabelUpgrading] + Expect(ok).Should(Equal(false)) + + upgradeOSIns2 := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey2, upgradeOSIns2) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(upgradeOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusIdle.String())) + Expect(upgradeOSIns2.Spec.UpgradeConfigs.Version).Should(Equal("v1")) + Expect(upgradeOSIns2.Spec.SysConfigs.Version).Should(Equal("v1")) + }) + }) + + Context("When we want to config node with nodes having NodeSelector label", func() { + It("Should only config node with NodeSelector label", func() { + ctx := context.Background() + // create Node1 + node1Name = "test-node-" + uuid.New().String() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, + Labels: map[string]string{ + "beta.kubernetes.io/os": "linux", + "upgrade.openeuler.org/node-selector": "openeuler", + }, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + OSImage: "KubeOS v1", + }, + }, + } + err := k8sClient.Create(ctx, node1) + Expect(err).ToNot(HaveOccurred()) + existingNode := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) + return err == nil + }, timeout, interval).Should(BeTrue()) + + // create OSInstance1 + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", + APIVersion: "upgrade.openeuler.org/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, + Labels: map[string]string{ + values.LabelOSinstance: node1Name, + }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + NodeStatus: values.NodeStatusIdle.String(), + }, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + + osInsCRLookupKey1 := types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey1, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) + + // create Node2 + node2Name := "test-node-" + uuid.New().String() + node2 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: node2Name, + Namespace: testNamespace, + Labels: map[string]string{ + "beta.kubernetes.io/os": "linux", + }, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + OSImage: "KubeOS v1", + }, + }, + } + err = k8sClient.Create(ctx, node2) + Expect(err).ToNot(HaveOccurred()) + existingNode = &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode) + return err == nil + }, timeout, interval).Should(BeTrue()) + + // create OSInstance2 + OSIns = &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", + APIVersion: "upgrade.openeuler.org/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: node2Name, + Namespace: testNamespace, + Labels: map[string]string{ + values.LabelOSinstance: node2Name, + }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, + NodeStatus: values.NodeStatusIdle.String(), + }, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + + // Check that the corresponding OSIns CR has been created + osInsCRLookupKey2 := types.NamespacedName{Name: node2Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node2Name)) + + OS := &upgradev1.OS{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "upgrade.openeuler.org/v1alpha1", + Kind: "OS", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: OSName, + Namespace: testNamespace, + }, + Spec: upgradev1.OSSpec{ + OpsType: "config", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", + FlagSafe: true, + MTLS: false, + EvictPodForce: true, + NodeSelector: "openeuler", + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", + Configs: []upgradev1.SysConfig{ + { + Model: "kernel.sysctl", + Contents: []upgradev1.Content{ + {Key: "key1", Value: "a"}, + {Key: "key2", Value: "b"}, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) + + osCRLookupKey := types.NamespacedName{Name: OSName, Namespace: testNamespace} + createdOS := &upgradev1.OS{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osCRLookupKey, createdOS) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOS.Spec.SysConfigs.Version).Should(Equal("v2")) + + time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + existingNode1 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode1) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok := existingNode1.Labels[values.LabelConfiguring] + Expect(ok).Should(Equal(true)) + + upgradeOSIns1 := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey1, upgradeOSIns1) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(upgradeOSIns1.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) + Expect(upgradeOSIns1.Spec.SysConfigs.Version).Should(Equal("v2")) + + existingNode2 := &v1.Node{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), + types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode2) + return err == nil + }, timeout, interval).Should(BeTrue()) + _, ok = existingNode2.Labels[values.LabelConfiguring] + Expect(ok).Should(Equal(false)) + + upgradeOSIns2 := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey2, upgradeOSIns2) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(upgradeOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusIdle.String())) + Expect(upgradeOSIns2.Spec.SysConfigs.Version).Should(Equal("v1")) + }) + }) }) func Test_deepCopySpecConfigs(t *testing.T) { @@ -780,127 +1209,6 @@ func Test_deepCopySpecConfigs(t *testing.T) { } } -func Test_getConfigOSInstances(t *testing.T) { - type args struct { - ctx context.Context - r common.ReadStatusWriter - } - tests := []struct { - name string - args args - want []upgradev1.OSInstance - wantErr bool - }{ - { - name: "list error", - args: args{ - ctx: context.Background(), - r: &OSReconciler{}, - }, - want: nil, - wantErr: true, - }, - } - patchList := gomonkey.ApplyMethodSeq(&OSReconciler{}, "List", []gomonkey.OutputCell{ - {Values: gomonkey.Params{fmt.Errorf("list error")}}, - }) - defer patchList.Reset() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getConfigOSInstances(tt.args.ctx, tt.args.r) - if (err != nil) != tt.wantErr { - t.Errorf("getConfigOSInstances() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getConfigOSInstances() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_checkUpgrading(t *testing.T) { - type args struct { - ctx context.Context - r common.ReadStatusWriter - maxUnavailable int - } - tests := []struct { - name string - args args - want int - wantErr bool - }{ - { - name: "label error", - args: args{ - ctx: context.Background(), - r: &OSReconciler{}, - }, - want: 0, - wantErr: true, - }, - } - patchNewRequirement := gomonkey.ApplyFuncSeq(labels.NewRequirement, []gomonkey.OutputCell{ - {Values: gomonkey.Params{nil, fmt.Errorf("label error")}}, - {Values: gomonkey.Params{nil, nil}}, - }) - defer patchNewRequirement.Reset() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := checkUpgrading(tt.args.ctx, tt.args.r, tt.args.maxUnavailable) - if (err != nil) != tt.wantErr { - t.Errorf("checkUpgrading() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("checkUpgrading() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getIdleOSInstances(t *testing.T) { - type args struct { - ctx context.Context - r common.ReadStatusWriter - limit int - } - tests := []struct { - name string - args args - want []upgradev1.OSInstance - wantErr bool - }{ - { - name: "list error", - args: args{ - ctx: context.Background(), - r: &OSReconciler{}, - limit: 1, - }, - want: nil, - wantErr: true, - }, - } - patchList := gomonkey.ApplyMethodSeq(&OSReconciler{}, "List", []gomonkey.OutputCell{ - {Values: gomonkey.Params{fmt.Errorf("list error")}}, - }) - defer patchList.Reset() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getIdleOSInstances(tt.args.ctx, tt.args.r, tt.args.limit) - if (err != nil) != tt.wantErr { - t.Errorf("getIdleOSInstances() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getIdleOSInstances() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_getNodes(t *testing.T) { type args struct { ctx context.Context diff --git a/cmd/operator/controllers/requirements.go b/cmd/operator/controllers/requirements.go new file mode 100644 index 00000000..12db8e82 --- /dev/null +++ b/cmd/operator/controllers/requirements.go @@ -0,0 +1,199 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +// Package controllers contains the Reconcile of operator +package controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + + "openeuler.org/KubeOS/pkg/common" + "openeuler.org/KubeOS/pkg/values" +) + +const ( + AllNodeSelector = "all-label" + NoNodeSelector = "no-label" +) + +type labelRequirementCreator interface { + createLabelRequirement() ([]labels.Requirement, error) +} + +type masterLabel struct { + op selection.Operator +} + +func (ml masterLabel) createLabelRequirement() ([]labels.Requirement, error) { + requirement, err := labels.NewRequirement(values.LabelMaster, ml.op, nil) + if err != nil { + log.Error(err, "unable to create requirement "+values.LabelMaster) + return []labels.Requirement{}, err + } + return []labels.Requirement{*requirement}, nil +} + +type opsLabel struct { + label string + op selection.Operator +} + +func (ol opsLabel) createLabelRequirement() ([]labels.Requirement, error) { + requirement, err := labels.NewRequirement(ol.label, ol.op, nil) + if err != nil { + log.Error(err, "unable to create requirement "+ol.label) + return []labels.Requirement{}, err + } + return []labels.Requirement{*requirement}, nil +} + +type nodeSelectorLabel struct { + value string + op selection.Operator +} + +func (nl nodeSelectorLabel) createLabelRequirement() ([]labels.Requirement, error) { + if nl.value == AllNodeSelector { + return []labels.Requirement{}, nil + } + var requirements []labels.Requirement + // if nodeselector is "no-label", will get the nodes which not have label + if nl.value == NoNodeSelector { + requirement, err := labels.NewRequirement(values.LabelNodeSelector, selection.DoesNotExist, nil) + if err != nil { + log.Error(err, "unable to create requirement "+values.LabelNodeSelector) + return []labels.Requirement{}, err + } + requirements = append(requirements, *requirement) + return requirements, nil + } + requirement, err := labels.NewRequirement(values.LabelNodeSelector, nl.op, []string{nl.value}) + if err != nil { + log.Error(err, "unable to create requirement "+values.LabelNodeSelector) + return []labels.Requirement{}, err + } + requirements = append(requirements, *requirement) + return requirements, nil +} + +type serialLabel struct { + op selection.Operator +} + +func (sl serialLabel) createLabelRequirement() ([]labels.Requirement, error) { + requirement, err := labels.NewRequirement(values.LabelSerial, sl.op, nil) + if err != nil { + log.Error(err, "unable to create requirement "+values.LabelSerial) + return []labels.Requirement{}, err + } + return []labels.Requirement{*requirement}, nil +} + +func createRequirement(labelsList []labelRequirementCreator) ([]labels.Requirement, error) { + var requirements []labels.Requirement + for _, label := range labelsList { + requirement, err := label.createLabelRequirement() + if err != nil { + log.Error(err, "unable to create requirement "+values.LabelNodeSelector) + return []labels.Requirement{}, err + } + requirements = append(requirements, requirement...) + } + return requirements, nil +} + +type nodeRequirementCreator interface { + createNodeRequirement(ctx context.Context, r common.ReadStatusWriter, nodeSelector string) ([]labels.Requirement, error) +} + +type commonsNodesRequirement struct { + nodeSelector string + op selection.Operator +} + +func newCommonsNodesRequirement(nodeSelector string, op selection.Operator) commonsNodesRequirement { + return commonsNodesRequirement{ + nodeSelector: nodeSelector, + op: op, + } +} + +func (c commonsNodesRequirement) createNodeRequirement(ctx context.Context, r common.ReadStatusWriter) ([]labels.Requirement, error) { + labelList := []labelRequirementCreator{ + masterLabel{op: selection.DoesNotExist}, + nodeSelectorLabel{value: c.nodeSelector, op: c.op}, + } + requirements, err := createRequirement(labelList) + if err != nil { + return []labels.Requirement{}, err + } + return requirements, nil +} + +type opsNodesRequirement struct { + common commonsNodesRequirement + ops opsLabel +} + +func newopsNodesRequirement(nodeSelector string, nodeSelectorOp selection.Operator, ops opsLabel) opsNodesRequirement { + return opsNodesRequirement{ + common: newCommonsNodesRequirement(nodeSelector, nodeSelectorOp), + ops: ops, + } +} + +func (o opsNodesRequirement) createNodeRequirement(ctx context.Context, r common.ReadStatusWriter) ([]labels.Requirement, error) { + labelList := []labelRequirementCreator{ + o.ops, + } + requirements, err := createRequirement(labelList) + if err != nil { + return []labels.Requirement{}, err + } + commonRequirements, err := o.common.createNodeRequirement(ctx, r) + if err != nil { + return []labels.Requirement{}, err + } + requirements = append(requirements, commonRequirements...) + return requirements, nil +} + +type serialNodesRequirement struct { + common commonsNodesRequirement + serialOp selection.Operator +} + +func newSerialNodesRequirement(nodeSelector string, nodeSelectorOp selection.Operator, serialrOp selection.Operator) serialNodesRequirement { + return serialNodesRequirement{ + common: newCommonsNodesRequirement(nodeSelector, nodeSelectorOp), + serialOp: serialrOp, + } +} + +func (o serialNodesRequirement) createNodeRequirement(ctx context.Context, r common.ReadStatusWriter) ([]labels.Requirement, error) { + labelList := []labelRequirementCreator{ + serialLabel{op: o.serialOp}, + } + requirements, err := createRequirement(labelList) + if err != nil { + return []labels.Requirement{}, err + } + commonRequirements, err := o.common.createNodeRequirement(ctx, r) + if err != nil { + return []labels.Requirement{}, err + } + requirements = append(requirements, commonRequirements...) + return requirements, nil +} diff --git a/cmd/operator/controllers/times.go b/cmd/operator/controllers/times.go new file mode 100644 index 00000000..3a72cce9 --- /dev/null +++ b/cmd/operator/controllers/times.go @@ -0,0 +1,106 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +// Package controllers contains the Reconcile of operator + +package controllers + +import ( + "fmt" + "regexp" + "time" +) + +const ( + DATE_TIME = "2006-01-02 15:04:05" + TIME_ONLY = "15:04:05" + ExecutionModeSerial = "serial" + ExecutionModeParallel = "parallel" + oneDayTime = 24 * time.Hour +) + +func isWithinTimeWindow(start, end string) (bool, error) { + if start == "" && end == "" { + return true, nil + } + if start == "" || end == "" { + return false, fmt.Errorf("invalid TimeWindow: The start time and end time must be both empty or not empty") + } + layoutStart, err := checkTimeValid(start) + if err != nil { + return false, err + } + layoutEnd, err := checkTimeValid(end) + if err != nil { + return false, err + } + if layoutStart != layoutEnd { + return false, fmt.Errorf("invalid TimeWindow: Start Time should have same time format with End Time") + } + now := time.Now() + timeFormat := now.Format(layoutStart) + now, err = time.ParseInLocation(layoutStart, timeFormat, now.Location()) + startTime, err := time.ParseInLocation(layoutStart, start, now.Location()) + if err != nil { + return false, err + } + endTime, err := time.ParseInLocation(layoutStart, end, now.Location()) + if err != nil { + return false, err + } + if endTime.Equal(startTime) { + return false, fmt.Errorf("invalid TimeWindow: start time is equal to end time") + } + if endTime.Before(startTime) { + if layoutStart == DATE_TIME { + return false, fmt.Errorf("invalid TimeWindow: Start %s Time is after end time %s", + startTime.Format(layoutStart), endTime.Format(layoutEnd)) + } + endTime = endTime.Add(oneDayTime) + fmt.Printf("endtime time add 24 hour is %s\n", endTime.Format(layoutStart)) + if now.Before(startTime) { + now = now.Add(oneDayTime) + fmt.Printf("now time add 24 hour is %s\n", now.Format(layoutStart)) + } + + } + return now.After(startTime) && now.Before(endTime), nil +} + +func checkTimeValid(checkTime string) (string, error) { + reDateTime, err := regexp.Compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") + if err != nil { + return "", err + } + reTimeOnly, err := regexp.Compile("^\\d{2}:\\d{2}:\\d{2}$") + if err != nil { + return "", err + } + + if reDateTime.MatchString(checkTime) { + _, err := time.Parse(DATE_TIME, checkTime) + if err != nil { + return "", err + } + return DATE_TIME, nil + + } + if reTimeOnly.MatchString(checkTime) { + _, err := time.Parse(TIME_ONLY, checkTime) + if err != nil { + return "", err + } + return TIME_ONLY, nil + + } + return "", fmt.Errorf("invalid TimeWindow: invalid date format, please use date format YYYY-MM-DD HH:MM:SS, or only HH:MM:SS") +} diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 6b90b26b..79b23ec7 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -14,6 +14,7 @@ package main import ( "os" + "strings" "time" zaplogfmt "github.com/sykesm/zap-logfmt" @@ -38,6 +39,11 @@ var ( setupLog = ctrl.Log.WithName("setup") ) +const ( + DEBUGLOGLEVEL = "debug" + INFOLOGLEVEL = "info" +) + func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) @@ -51,7 +57,12 @@ func main() { encoder.AppendString(ts.UTC().Format(time.RFC3339Nano)) } logfmtEncoder := zaplogfmt.NewEncoder(configLog) - logger := zap.New(zap.UseDevMode(true), zap.WriteTo(os.Stdout), zap.Encoder(logfmtEncoder)) + level := zap.Level(zapcore.Level(0)) + goLog := strings.ToLower(os.Getenv("GO_LOG")) + if goLog == DEBUGLOGLEVEL { + level = zap.Level(zapcore.Level(-1)) + } + logger := zap.New(zap.UseDevMode(true), zap.WriteTo(os.Stdout), zap.Encoder(logfmtEncoder), level) ctrl.SetLogger(logger) mgr, err := common.NewControllerManager(setupLog, scheme) diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go index 0630a98c..b543befc 100644 --- a/cmd/proxy/controllers/os_controller.go +++ b/cmd/proxy/controllers/os_controller.go @@ -244,6 +244,13 @@ func (r *OSReconciler) refreshNode(ctx context.Context, node *corev1.Node, osIns return err } } + if _, ok := node.Labels[values.LabelConfiguring]; ok { + delete(node.Labels, values.LabelConfiguring) + if err := r.Update(ctx, node); err != nil { + log.Error(err, "unable to delete label", "node", node.Name) + return err + } + } if node.Spec.Unschedulable { // update done, uncordon the node drainer := &drain.Helper{ Ctx: ctx, diff --git a/docs/example/config/admin-container/admin-container.yaml b/docs/example/config/admin-container/admin-container.yaml index b5ec9e03..518b955f 100644 --- a/docs/example/config/admin-container/admin-container.yaml +++ b/docs/example/config/admin-container/admin-container.yaml @@ -1,63 +1,63 @@ -apiVersion: v1 -kind: Secret -metadata: - name: root-secret -data: - ssh-pub-key: your-ssh-pub-key ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: admin-container-sysmaster - namespace: default - labels: - control-plane: admin-container-sysmaster -spec: - selector: - matchLabels: - control-plane: admin-container-sysmaster - replicas: 1 - template: - metadata: - labels: - control-plane: admin-container-sysmaster - spec: - hostPID: true - containers: - - name: admin-container-sysmaster - image: your_imageRepository/admin_imageName:version - imagePullPolicy: Always - securityContext: - privileged: true - ports: - - containerPort: 22 - # sysmaster要求 - env: - - name: container - value: containerd - volumeMounts: - # name 必须与下面的卷名匹配 - - name: secret-volume - # mountPath必须为/etc/secret-volume - mountPath: /etc/secret-volume - readOnly: true - nodeName: your-worker-node-name - volumes: - - name: secret-volume - secret: - # secretName必须与上面指定的Secret的name相同 - secretName: root-secret ---- -apiVersion: v1 -kind: Service -metadata: - name: admin-container-sysmaster - namespace: default -spec: - type: NodePort - ports: - - port: 22 - targetPort: 22 - nodePort: your-exposed-port - selector: - control-plane: admin-container-sysmaster \ No newline at end of file +apiVersion: v1 +kind: Secret +metadata: + name: root-secret +data: + ssh-pub-key: +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: admin-container-sysmaster + namespace: default + labels: + control-plane: admin-container-sysmaster +spec: + selector: + matchLabels: + control-plane: admin-container-sysmaster + replicas: 1 + template: + metadata: + labels: + control-plane: admin-container-sysmaster + spec: + hostPID: true + containers: + - name: admin-container-sysmaster + image: + imagePullPolicy: Always + securityContext: + privileged: true + ports: + - containerPort: 22 + # sysmaster要求 + env: + - name: container + value: containerd + volumeMounts: + # name 必须与下面的卷名匹配 + - name: secret-volume + # mountPath必须为/etc/secret-volume + mountPath: /etc/secret-volume + readOnly: true + nodeName: + volumes: + - name: secret-volume + secret: + # secretName必须与上面指定的Secret的name相同 + secretName: root-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: admin-container-sysmaster + namespace: default +spec: + type: NodePort + ports: + - port: 22 + targetPort: 22 + nodePort: + selector: + control-plane: admin-container-sysmaster diff --git a/docs/example/config/crd/upgrade.openeuler.org_os.yaml b/docs/example/config/crd/upgrade.openeuler.org_os.yaml index 2dd822e9..8a29e0c3 100644 --- a/docs/example/config/crd/upgrade.openeuler.org_os.yaml +++ b/docs/example/config/crd/upgrade.openeuler.org_os.yaml @@ -57,9 +57,19 @@ spec: type: string evictpodforce: type: boolean + executionmode: + default: parallel + enum: + - serial + - parallel + type: string flagSafe: type: boolean imagetype: + enum: + - docker + - disk + - containerd type: string imageurl: type: string @@ -67,7 +77,14 @@ spec: type: integer mtls: type: boolean + nodeselector: + default: no-label + type: string opstype: + enum: + - upgrade + - config + - rollback type: string osversion: type: string @@ -99,6 +116,19 @@ spec: version: type: string type: object + timeinterval: + default: 15 + type: integer + timewindow: + properties: + endtime: + type: string + starttime: + type: string + required: + - endtime + - starttime + type: object upgradeconfigs: description: SysConfigs defines all configurations expected by the user properties: diff --git a/docs/example/config/crd/upgrade.openeuler.org_osinstances.yaml b/docs/example/config/crd/upgrade.openeuler.org_osinstances.yaml index df9119b4..47de87e3 100644 --- a/docs/example/config/crd/upgrade.openeuler.org_osinstances.yaml +++ b/docs/example/config/crd/upgrade.openeuler.org_osinstances.yaml @@ -53,6 +53,14 @@ spec: spec: description: OSInstanceSpec defines desired state of OS properties: + namespacedname: + description: NamespacedName defines name and namespace of os corresponding to osinstance + properties: + name: + type: string + namespace: + type: string + type: object nodestatus: type: string sysconfigs: diff --git a/docs/example/config/manager/manager.yaml b/docs/example/config/manager/manager.yaml index 93d15220..c35d9010 100644 --- a/docs/example/config/manager/manager.yaml +++ b/docs/example/config/manager/manager.yaml @@ -20,19 +20,19 @@ spec: control-plane: upgrade-proxy spec: containers: - - name: proxy - command: - - /proxy - image: edit.proxy.image.addr - volumeMounts: - - name: upgrade-agent - mountPath: /var/run/os-agent - env: - - name: NODE_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: spec.nodeName + - name: proxy + command: + - /proxy + image: + volumeMounts: + - name: upgrade-agent + mountPath: /var/run/os-agent + env: + - name: NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName volumes: - name: upgrade-agent hostPath: @@ -56,24 +56,33 @@ spec: control-plane: upgrade-operator spec: containers: - - command: - - /operator - image: edit.operator.image.addr - name: operator - securityContext: - allowPrivilegeEscalation: false - runAsUser: 6552 - runAsGroup: 6552 - resources: - limits: - cpu: 100m - memory: 30Mi - requests: - cpu: 100m - memory: 20Mi + - command: + - /operator + image: + name: operator + volumeMounts: + - name: date-config + mountPath: /etc/localtime + securityContext: + allowPrivilegeEscalation: false + runAsUser: 6552 + runAsGroup: 6552 + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 20Mi + volumes: + - name: date-config + hostPath: + path: /etc/localtime terminationGracePeriodSeconds: 10 nodeSelector: node-role.kubernetes.io/control-plane: "" tolerations: - key: "node-role.kubernetes.io/master" operator: "Exists" + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" diff --git a/docs/example/config/samples/upgrade_v1alpha1_os.yaml b/docs/example/config/samples/upgrade_v1alpha1_os.yaml index b33a30b2..d7528831 100644 --- a/docs/example/config/samples/upgrade_v1alpha1_os.yaml +++ b/docs/example/config/samples/upgrade_v1alpha1_os.yaml @@ -1,38 +1,38 @@ apiVersion: upgrade.openeuler.org/v1alpha1 kind: OS metadata: - name: os-sample + name: os-sample spec: - imagetype: docker/containerd/disk - opstype: upgrade/config/rollback - osversion: edit.os.version - maxunavailable: edit.node.upgrade.number - containerimage: "" - evictpodforce: true - imageurl: "" - checksum: image digests - flagSafe: false - mtls: false - sysconfigs: - version: edit.sysconfig.version - configs: - - model: kernel.systcl - contents: - - key: kernel param key1 - value: kernel param value1 - - key: kernel param key2 - value: kernel param value2 - - model: kernel.systcl.persist - configpath: persist file path - contents: - - key: kernel param key3 - value: kernel param value3 - operation: delete - upgradeconfigs: - version: edit.upgradeconfig.version - configs: - - model: kernel.systcl - contents: - - key: kernel param key4 - value: kernel param value4 - operation: delete \ No newline at end of file + imagetype: docker/containerd/disk + opstype: upgrade/config/rollback + osversion: edit.os.version + maxunavailable: edit.node.upgrade.number + containerimage: "" + evictpodforce: true + imageurl: "" + checksum: image digests + flagSafe: false + mtls: false + sysconfigs: + version: edit.sysconfig.version + configs: + - model: kernel.sysctl + contents: + - key: kernel param key1 + value: kernel param value1 + - key: kernel param key2 + value: kernel param value2 + - model: kernel.sysctl.persist + configpath: persist file path + contents: + - key: kernel param key3 + value: kernel param value3 + operation: delete + upgradeconfigs: + version: edit.upgradeconfig.version + configs: + - model: kernel.sysctl + contents: + - key: kernel param key4 + value: kernel param value4 + operation: delete diff --git a/docs/quick-start.md b/docs/quick-start.md index 9656fb99..af37c224 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -10,8 +10,8 @@ * golang(大于等于1.15版本) * make * git - * rust(大于等于1.57版本) - * cargo(大于等于1.57版本) + * rust(大于等于1.64版本) + * cargo(大于等于1.64版本) * openssl-devel ``` shell @@ -65,11 +65,18 @@ * Dockerfile参考如下, Dockerfile也可以使用多阶段构建: + `proxy`容器镜像Dockerfile + ``` dockerfile - FROM your_baseimage + FROM openeuler/openeuler:24.03-lts COPY ./bin/proxy /proxy ENTRYPOINT ["/proxy"] - FROM your_baseimage + ``` + + `operator`容器镜像Dockerfile + + ``` dockerfile + FROM openeuler/openeuler:24.03-lts COPY --chown=6552:6552 ./bin/operator /operator ENTRYPOINT ["/operator"] ``` @@ -112,14 +119,34 @@ bash kbimg.sh create vm-image -p xxx.repo -v v1 -b ../bin/os-agent -e '''$1$xyz$RdLyKTL32WEvK3lg8CXID0''' ``` - * 其中 xx.repo 为制作镜像所需要的 yum 源,yum 源建议配置为 openEuler 具体版本的 everything 仓库和 EPOL 仓库。 - * 容器 OS 镜像制作完成后,会在 scripts 目录下生成: - * raw格式的系统镜像system.img,system.img大小默认为20G,支持的根文件系统分区大小<2020MiB,持久化分区<16GB。 - * qcow2 格式的系统镜像 system.qcow2。 - * 可用于升级的根文件系统分区镜像 update.img 。 - * 制作出来的容器 OS 虚拟机镜像目前只能用于 CPU 架构为 x86 和 AArch64 的虚拟机场景,x86 架构的虚拟机使用 legacy 启动模式启动需制作镜像时指定-l参数 - * 容器OS运行底噪<150M (不包含k8s组件及相关依赖kubernetes-kubeadm,kubernetes-kubelet, containernetworking-plugins,socat,conntrack-tools,ebtables,ethtool) - * 本项目不提供容器OS镜像,仅提供裁剪工具,裁剪出来的容器OS内部的安全性由OS发行商保证。 + 参数说明如下: + + ```bash + Usage : kbimg create vm-image -p iso-path -v os-version -b os-agent-dir -e os-password + or + kbimg create vm-image -d repository/name:tag + + options: + -p repo path + -v KubeOS version + -b path of os-agent binary + -e os encrypted password + -d docker image like repository/name:tag + -l boot to legacy BIOS mode, if not specify, then UEFI mode + -h,--help show help information + ``` + + * 其中 xxx.repo 为制作镜像所需要的 yum 源,yum 源建议配置为 openEuler 具体版本的 everything 仓库和 EPOL 仓库。 + * 容器 OS 镜像制作完成后,会在 scripts 目录下生成: + * raw格式的系统镜像system.img,system.img大小默认为20G,支持的根文件系统分区大小<2020MiB,持久化分区<16GB。 + * qcow2 格式的系统镜像 system.qcow2。 + * 可用于升级的根文件系统分区镜像 update.img 。 + * 制作出来的容器 OS 虚拟机镜像目前只能用于 CPU 架构为 x86 和 AArch64 的虚拟机场景。若x86 架构的虚拟机需要使用 legacy 启动模式,需制作镜像时指定-l参数 + * 默认root密码为openEuler12#$ + * 您可通过`openssl passwd -6 -salt $(head -c18 /dev/urandom | openssl base64)`命令生成root密码并通过`-e`参数配置密码 + * 容器OS运行底噪<150M (不包含k8s组件及相关依赖kubernetes-kubeadm,kubernetes-kubelet, containernetworking-plugins,socat,conntrack-tools,ebtables,ethtool) + * 本项目不提供容器OS镜像,仅提供裁剪工具,裁剪出来的容器OS内部的安全性由OS发行商保证。 + * 详细参数说明请见[《容器OS镜像制作指导》](../docs/user_guide/%E5%AE%B9%E5%99%A8OS%E9%95%9C%E5%83%8F%E5%88%B6%E4%BD%9C%E6%8C%87%E5%AF%BC.md) * 声明: os-agent使用本地unix socket进行通信,因此不会新增端口。下载镜像的时候会新增一个客户端的随机端口,1024~65535使用完后关闭。proxy和operator与api-server通信时作为客户端也会有一个随机端口,基于kubernetes的operator框架,必须使用端口。他们部署在容器里。 @@ -173,7 +200,7 @@ ## 使用指导 -#### 注意事项 +### 注意事项 * 公共注意事项 * 仅支持虚拟机x86和arm64 UEFI场景。 @@ -209,7 +236,7 @@ | 参数 |参数类型 | 参数说明 | 使用说明 | 是否必选 | | -------------- | ------ | ------------------------------------------------------------ | ----- | ---------------- | - | imagetype | string | 升级镜像的类型 | 仅支持docker ,containerd ,或者是 disk,仅在升级场景有效。
**注意**:若使用containerd,agent优先使用crictl工具拉取镜像,没有crictl时才会使用ctr命令拉取镜像。使用ctr拉取镜像时,镜像如果在私有仓内,需按照[官方文档](https://github.com/containerd/containerd/blob/main/docs/hosts.md)在/etc/containerd/certs.d目录下配置私有仓主机信息,才能成功拉取镜像。 |是 | + | imagetype | string | 升级镜像的类型 | 仅支持docker ,containerd ,或者是 disk,仅在升级场景有效。**注意**:若使用containerd,agent优先使用crictl工具拉取镜像,没有crictl时才会使用ctr命令拉取镜像。使用ctr拉取镜像时,镜像如果在私有仓内,需按照[官方文档](https://github.com/containerd/containerd/blob/main/docs/hosts.md)在/etc/containerd/certs.d目录下配置私有仓主机信息,才能成功拉取镜像。 |是 | | opstype | string | 操作类型:升级,回退或者配置 | 仅支持upgrade ,config 或者 rollback |是 | | osversion | string | 升级/回退的目标版本 | osversion需与节点的目标os版本对应(节点上/etc/os-release中PRETTY_NAME字段或k8s检查到的节点os版本) 例如:KubeOS 1.0.0。 |是 | | maxunavailable | int | 每批同时进行升级/回退/配置的节点数。 | maxunavailable值大于实际节点数时,取实际节点数进行升级/回退/配置。 |是 | @@ -224,7 +251,7 @@ | evictpodforce | bool | 升级/回退时是否强制驱逐pod | 需为 true 或者 false ,仅在升级或者回退时有效| 必选 | | sysconfigs | / | 配置设置 | 1. “opstype=config”时只进行配置。 2.“opstype=upgrade/rollback”时,代表升级/回退后配置,即在升级/回退重启后进行配置。```配置(Settings)指导``` | “opstype=config”时必选 | | upgradeconfigs | / | 升级前配置设置 | 在升级或者回退时有效,在升级或者回退操作之前起效,详细字段说明请见```配置(Settings)指导```| 可选 | - + | nodeselector | string | 需要进行升级/配置/回滚操作的节点label | 用于只对具有某些特定label的节点而不是集群所有worker节点进行运维的场景,需要进行运维操作的节点需要包含key为upgrade.openeuler.org/node-selector的label,nodeselector为该label的value值,此参数不配置时,或者配置为""时默认对所有节点进行操作| 可选 | #### 升级指导 * 编写YAML文件,在集群中部署 OS 的cr实例,用于部署cr实例的YAML示例如下,假定将上面的YAML保存到upgrade_v1alpha1_os.yaml; @@ -293,7 +320,7 @@ mtls: true ``` - * 升级并且进行配置的示例如下, + * 升级并且进行配置的示例如下 * 以节点容器引擎为containerd为例,升级方式对配置无影响,upgradeconfigs在升级前起效,sysconfigs在升级后起效,配置参数说明请见```配置(Settings)指导``` * 升级并且配置时opstype字段需为upgrade * upgradeconfig为升级之前执行的配置,sysconfigs为升级机器重启后执行的配置,用户可按需进行配置 @@ -338,7 +365,37 @@ - key: kernel param key4 value: kernel param value4 ``` + * 只升级部分节点示例如下 + * 以节点容器引擎为containerd为例,升级方式对节点筛选无影响 + * 需要进行升级的节点需包含key为upgrade.openeuler.org/node-selector的label,nodeselector的值为该label的value,即假定nodeselector值为kubeos,则只对包含upgrade.openeuler.org/node-selector=kubeos的label的worker节点进行升级 + * nodeselector对配置和回滚同样有效 + * 节点添加label和label修改命令示例如下: + ``` shell + # 为节点kubeos-node1增加label + kubectl label nodes kubeos-node1 upgrade.openeuler.org/node-selector=kubeos-v1 + # 修改节点kubeos-node1的label + kubectl label --overwrite nodes kubeos-node2 upgrade.openeuler.org/node-selector=kubeos-v2 + ``` + * yaml示例如下: + ```yaml + apiVersion: upgrade.openeuler.org/v1alpha1 + kind: OS + metadata: + name: os-sample + spec: + imagetype: containerd + opstype: upgrade + osversion: edit.os.version + maxunavailable: edit.node.upgrade.number + containerimage: container image like repository/name:tag + evictpodforce: true/false + imageurl: "" + checksum: container image digests + flagSafe: false + mtls: true + nodeselector: edit.node.label.key + ``` * 查看未升级的节点的 OS 版本 ```shell @@ -357,13 +414,13 @@ kubectl get nodes -o custom-columns='NAME:.metadata.name,OS:.status.nodeInfo.osImage' ``` -* 如果后续需要再次升级,与上面相同,对upgrade_v1alpha1_os.yaml的相应字段进行修改 +* 如果后续需要再次升级,与上面相同,对upgrade_v1alpha1_os.yaml的相应字段进行修改 #### 配置(Settings)指导 * Settings参数说明: - 基于示例YAML对配置的参数进行说明,示例YAML如下,配置的格式(缩进)需和示例保持一致: + 基于示例YAML对配置的参数进行说明,示例YAML如下,配置的格式(缩进)需和示例保持一致: ```yaml apiVersion: upgrade.openeuler.org/v1alpha1 @@ -424,9 +481,7 @@ | value | string | 参数值 | key=value形式的参数中,value不能为空,不建议配置含空格、tab键的字符串,具体请看附录下的```Settings列表```中对每种配置类型对value的说明。 | key=value形式的参数必选 | | operation | string | 对参数进行的操作 | 仅对kernel.sysctl.persist、grub.cmdline.current、grub.cmdline.next类型的参数生效。默认为添加或更新。仅支持配置为delete,代表删除已存在的参数(key=value需完全一致才能删除)。 | 否 | - - - * upgradeconfigs与sysconfigs参数相同,upgradeconfigs为升级/回退前进行的配置,仅在upgrade/rollback场景起效,sysconfigs既支持只进行配置,也支持在升级/回退重启后进行配置 + * upgradeconfigs与sysconfigs参数相同,upgradeconfigs为升级/回退前进行的配置,仅在upgrade/rollback场景起效,sysconfigs既支持只进行配置,也支持在升级/回退重启后进行配置 * 使用说明 @@ -463,7 +518,7 @@ 2. upgrade模式重新升级至上一版本 * 手动回退指导 - * 手动重启虚拟机,进入启动项页面后,选择第二启动项进行回退,手动回退仅支持回退到上一个版本。 + * 手动重启虚拟机,进入启动项页面后,选择第二启动项进行回退,手动回退仅支持回退到上一个版本。 * 工具回退指导 * 回退至任意版本 * 修改 OS 的cr实例的YAML 配置文件(例如 upgrade_v1alpha1_os.yaml),设置相应字段为期望回退的老版本镜像信息。类别OS来自于安装和部署章节创建的CRD对象,字段说明及示例请见上一节升级指导。 @@ -554,19 +609,14 @@ KubeOS提供一个分离的包含sshd服务和hostshell工具的Admin容器, ```Dockerfile FROM openeuler-22.03-lts-sp1 - RUN yum -y install openssh-clients util-linux - ADD ./your-sysmaster.rpm /home RUN rpm -ivh /home/your-sysmaster.rpm - COPY ./hostshell /usr/bin/ COPY ./set-ssh-pub-key.sh /usr/local/bin COPY ./set-ssh-pub-key.service /usr/lib/sysmaster - EXPOSE 22 RUN sed -i 's/sysinit.target/sysinit.target;sshd.service;set-ssh-pub-key.service/g' /usr/lib/sysmaster/basic.target - CMD ["/usr/lib/sysmaster/init"] ``` @@ -594,8 +644,7 @@ kind: Secret metadata: name: root-secret data: - # base64 encode your pub key in one line - ssh-pub-key: your-ssh-pub-key + ssh-pub-key: --- apiVersion: apps/v1 kind: Deployment @@ -603,7 +652,7 @@ metadata: name: admin-container-sysmaster namespace: default labels: - control-plane: admin-container-sysmaster + control-plane: admin-container-sysmaster spec: selector: matchLabels: @@ -616,24 +665,24 @@ spec: spec: hostPID: true containers: - - name: admin-container-sysmaster - image: your_imageRepository/admin_imageName:version - imagePullPolicy: Always - securityContext: - privileged: true - ports: - - containerPort: 22 - # sysmaster要求 - env: - - name: container - value: containerd - volumeMounts: - # name 必须与下面的卷名匹配 - - name: secret-volume - # mountPath必须为/etc/secret-volume - mountPath: /etc/secret-volume - readOnly: true - nodeName: your-worker-node-name + - name: admin-container-sysmaster + image: + imagePullPolicy: Always + securityContext: + privileged: true + ports: + - containerPort: 22 + # sysmaster要求 + env: + - name: container + value: containerd + volumeMounts: + # name 必须与下面的卷名匹配 + - name: secret-volume + # mountPath必须为/etc/secret-volume + mountPath: /etc/secret-volume + readOnly: true + nodeName: volumes: - name: secret-volume secret: @@ -650,9 +699,9 @@ spec: ports: - port: 22 targetPort: 22 - nodePort: your-exposed-port + nodePort: selector: - control-plane: admin-container-sysmaster + control-plane: admin-container-sysmaster ``` ### admin容器使用 @@ -680,6 +729,7 @@ hostshell #### kernel Settings * kenerl.sysctl:临时设置内核参数,重启后无效,key/value 表示内核参数的 key/value, key与value均不能为空且key不能包含“=”,该参数不支持删除操作(operation=delete)示例如下: + ```yaml configs: - model: kernel.sysctl @@ -690,7 +740,8 @@ hostshell value: 0 operation: delete ``` -* kenerl.sysctl:临时设置内核参数,重启后无效,key/value 表示内核参数的 key/value, key与value均不能为空且key不能包含“=”,该参数不支持删除操作(operation=delete)示例如下: + +* kernel.sysctl.persist: 设置持久化内核参数,key/value表示内核参数的key/value,key与value均不能为空且key不能包含“=”, configpath为配置文件路径,支持新建(需保证父目录存在),如不指定configpath默认修改/etc/sysctl.conf,示例如下: ```yaml configs: - model: kernel.sysctl.persist @@ -713,8 +764,8 @@ hostshell * KubeOS使用双分区,grub.cmdline支持对当前分区或下一分区进行配置: - - grub.cmdline.current:对当前分区的启动项参数进行配置。 - - grub.cmdline.next:对下一分区的启动项参数进行配置。 + * grub.cmdline.current:对当前分区的启动项参数进行配置。 + * grub.cmdline.next:对下一分区的启动项参数进行配置。 * 注意:升级/回退前后的配置,始终基于升级/回退操作下发时的分区位置进行current/next的区分。假设当前分区为A分区,下发升级操作并在sysconfigs(升级重启后配置)中配置grub.cmdline.current,重启后进行配置时仍修改A分区对应的grub cmdline。 diff --git "a/docs/user_guide/\345\256\271\345\231\250OS\351\225\234\345\203\217\345\210\266\344\275\234\346\214\207\345\257\274.md" "b/docs/user_guide/\345\256\271\345\231\250OS\351\225\234\345\203\217\345\210\266\344\275\234\346\214\207\345\257\274.md" index f2823c85..d54cc837 100644 --- "a/docs/user_guide/\345\256\271\345\231\250OS\351\225\234\345\203\217\345\210\266\344\275\234\346\214\207\345\257\274.md" +++ "b/docs/user_guide/\345\256\271\345\231\250OS\351\225\234\345\203\217\345\210\266\344\275\234\346\214\207\345\257\274.md" @@ -1,16 +1,16 @@ -# 容器OS镜像制作指导# +# 容器OS镜像制作指导 -## 简介 ## +## 简介 kbimg是KubeOS部署和升级所需的镜像制作工具,可以使用kbimg制作KubeOS 容器,虚拟机和物理机镜像 -## 命令介绍 ## +## 命令介绍 -### 命令格式 ### +### 命令格式 **bash kbimg.sh** \[ --help | -h \] create \[ COMMANDS \] \[ OPTIONS \] -### 参数说明 ### +### 参数说明 * COMMANDS @@ -20,8 +20,6 @@ kbimg是KubeOS部署和升级所需的镜像制作工具,可以使用kbimg制 | vm-image | 生成用于部署和升级的虚拟机镜像 | | pxe-image | 生成物理机安装所需的镜像及文件 | - - * OPTIONS | 参数 | 描述 | @@ -34,31 +32,33 @@ kbimg是KubeOS部署和升级所需的镜像制作工具,可以使用kbimg制 | -l | 如果指定参数,则镜像为legacy引导,不指定默认是UEFI引导 | | -h --help | 查看帮助信息 | +## 使用说明 - -## 使用说明 ## - -#### 注意事项 ### +### 注意事项 * kbimg.sh 执行需要 root 权限 * 当前仅支持 x86和 AArch64 架构使用 * 容器 OS 镜像制作工具的 rpm 包源为 openEuler 具体版本的 everything 仓库和 EPOL 仓库。制作镜像时提供的 repo 文件中,yum 源建议同时配置 openEuler 具体版本的 everything 仓库和 EPOL 仓库 -### KubeOS OCI 镜像制作 ### +### KubeOS OCI 镜像制作 -#### 注意事项 #### +#### 注意事项 * 制作的 OCI 镜像仅用于后续的虚拟机/物理机镜像制作或升级使用,不支持启动容器 * 使用默认 rpmlist 进行容器OS镜像制作时所需磁盘空间至少为6G,如自已定义 rpmlist 可能会超过6G -#### 使用示例 #### +#### 使用示例 + * 如需进行DNS配置,请先在```scripts```目录下自定义```resolv.conf```文件 + ```shell cd /opt/kubeOS/scripts touch resolv.conf vim resolv.conf ``` + * 制作KubeOS容器镜像 + ``` shell cd /opt/kubeOS/scripts bash kbimg.sh create upgrade-image -p xxx.repo -v v1 -b ../bin/os-agent -e '''$1$xyz$RdLyKTL32WEvK3lg8CXID0''' -d your_imageRepository/imageName:version @@ -70,25 +70,28 @@ bash kbimg.sh create upgrade-image -p xxx.repo -v v1 -b ../bin/os-agent -e '''$1 docker images ``` -### KubeOS 虚拟机镜像制作 ### +### KubeOS 虚拟机镜像制作 -#### 注意事项 #### +#### 注意事项 * 如使用 docker 镜像制作请先拉取相应镜像或者先制作docker镜像,并保证 docker 镜像的安全性 * 制作出来的容器 OS 虚拟机镜像目前只能用于 CPU 架构为 x86 和 AArch64 的虚拟机 * 容器 OS 目前不支持 x86 架构的虚拟机使用 legacy 启动模式启动 * 使用默认rpmlist进行容器OS镜像制作时所需磁盘空间至少为25G,如自已定义rpmlist可能会超过25G -#### 使用示例 #### +#### 使用示例 * 使用repo源制作 - * 如需进行DNS配置,请先在```scripts```目录下自定义```resolv.conf```文件 + * 如需进行DNS配置,请先在```scripts```目录下自定义```resolv.conf```文件 + ```shell cd /opt/kubeOS/scripts touch resolv.conf vim resolv.conf ``` - * KubeOS虚拟机镜像制作 + + * KubeOS虚拟机镜像制作 + ``` shell cd /opt/kubeOS/scripts bash kbimg.sh create vm-image -p xxx.repo -v v1 -b ../bin/os-agent -e '''$1$xyz$RdLyKTL32WEvK3lg8CXID0''' @@ -100,15 +103,15 @@ docker images cd /opt/kubeOS/scripts bash kbimg.sh create vm-image -d your_imageRepository/imageName:version ``` -* 结果说明 - 容器 OS 镜像制作完成后,会在 /opt/kubeOS/scripts 目录下生成: - * system.qcow2: qcow2 格式的系统镜像,大小默认为 20GiB,支持的根文件系统分区大小 < 2020 MiB,持久化分区 < 16GiB 。 - * update.img: 用于升级的根文件系统分区镜像 +* 结果说明 + 容器 OS 镜像制作完成后,会在 /opt/kubeOS/scripts 目录下生成: + * system.qcow2: qcow2 格式的系统镜像,大小默认为 20GiB,支持的根文件系统分区大小 < 2020 MiB,持久化分区 < 16GiB 。 + * update.img: 用于升级的根文件系统分区镜像 -### KubeOS 物理机安装所需镜像及文件制作 ### +### KubeOS 物理机安装所需镜像及文件制作 -#### 注意事项 #### +#### 注意事项 * 如使用 docker 镜像制作请先拉取相应镜像或者先制作 docker 镜像,并保证 docker 镜像的安全性 * 制作出来的容器 OS 物理安装所需的镜像目前只能用于 CPU 架构为 x86 和 AArch64 的物理机安装 @@ -116,7 +119,8 @@ docker images * 不支持多个磁盘都安装KubeOS,可能会造成启动失败或挂载紊乱 * 容器OS 目前不支持 x86 架构的物理机使用 legacy 启动模式启动 * 使用默认rpmlist进行镜像制作时所需磁盘空间至少为5G,如自已定义 rpmlist 可能会超过5G -#### 使用示例 #### + +#### 使用示例 * 首先需要修改```00bootup/Global.cfg```的配置,对相关参数进行配置,参数均为必填,ip目前仅支持ipv4,配置示例如下 @@ -138,25 +142,344 @@ docker images ``` * 使用 repo 源制作 - * 如需进行DNS配置,请在```scripts```目录下自定义```resolv.conf```文件 + * 如需进行DNS配置,请在```scripts```目录下自定义```resolv.conf```文件 + ```shell cd /opt/kubeOS/scripts touch resolv.conf vim resolv.conf ``` - * KubeOS物理机安装所需镜像制作 - ``` + + * KubeOS物理机安装所需镜像制作 + + ```shell cd /opt/kubeOS/scripts bash kbimg.sh create pxe-image -p xxx.repo -v v1 -b ../bin/os-agent -e '''$1$xyz$RdLyKTL32WEvK3lg8CXID0''' ``` * 使用 docker 镜像制作 + ``` shell cd /opt/kubeOS/scripts bash kbimg.sh create pxe-image -d your_imageRepository/imageName:version ``` * 结果说明 + * initramfs.img: 用于pxe启动用的 initramfs 镜像 + * kubeos.tar: pxe安装所用的 OS + + +# KubeOS-Rust 镜像制作说明 + +## 简介 + +KubeOS 虚拟机镜像制作的 Rust 二进制版本 + +## 命令介绍 + +### 命令格式 + +**.../kbimg** \[ --config | -c \] \ + +## 配置文件说明 + +* from_repo: 从 repo 创建 OCI 镜像、虚拟机镜像或物理机镜像 + + | 参数 | 描述 | + | --- | --- | + | agent_path | os-agent 二进制的路径 | + | image_type | upgrade: 用于安装和升级的 OCI 镜像格式的 KubeOS 镜像; vm-repo: 用于部署和升级的虚拟机镜像; pxe-repo: 物理机安装所需的镜像及文件 | + | legacy_bios | 镜像为 legacy 引导或 UEFI 引导 | + | repo_path | repo 文件的路径,repo 文件中配置制作镜像所需要的 yum 源 | + | root_passwd | KubeOS 镜像 root 用户密码,加密后的带盐值的密码,可以用 openssl、kiwi 命令生成 | + | version | 制作出来的 KubeOS 镜像的版本 | + | rpmlist | 镜像所需的 rpm 包 | + | docker_img | 生成或者使用的 docker 镜像 | + +* from_docker: 从 docker 镜像创建虚拟机镜像或物理机镜像 + + | 参数 | 描述 | + | --- | --- | + | docker_img | 生成或者使用的 docker 镜像 | + | image_type | vm-docker: 用于部署和升级的虚拟机镜像; pxe-docker: 物理机安装所需的镜像及文件 | + +* admin_container: + + | 参数 | 描述 | + | --- | --- | + | dockerfile | dockerfile 路径 | + | docker_img | 生成或者使用的 docker 镜像 | + +* [OPTIONAL] users: 添加用户 + + | 参数 | 描述 | + | --- | --- | + | groups | [OPTIONAL] 用户组 (第一个为主组,其他为附加组) | + | name | 用户名 | + | passwd | 密码 | + | sudo | [OPTIONAL] 用户是否具有 sudo 权限 | + +* [OPTIONAL] copy_files: 拷贝文件到指定目录 + + | 参数 | 描述 | + | --- | --- | + | dst | 目标目录 | + | src | 源文件路径 | + +* [OPTIONAL] grub: grub配置 + + | 参数 | 描述 | + | --- | --- | + | passwd | [OPTIONAL] grub 密码 | + +* [OPTIONAL] systemd_service: 新增 systemd 服务 + + | 参数 | 描述 | + | --- | --- | + | name | systemd 服务名 | + +* [OPTIONAL] chroot_script: 自定义 chroot 脚本 + + | 参数 | 描述 | + | --- | --- | + | path | 脚本路径 | + +* [OPTIONAL] disk_partition: 自定义分区大小和镜像大小 + + | 参数 | 描述 | + | --- | --- | + | first | 引导分区大小 | + | second | ROOT-A 分区大小 | + | third | ROOT-B 分区大小 | + | img_size | 镜像大小 | + +* [OPTIONAL] persist_mkdir: persist 分区新建目录 + + | 参数 | 描述 | + | --- | --- | + | name | 目录名 | + +## 使用说明 + +#### 注意事项 + +* 新增 systemd 服务需要将对应的 .service 文件或 .mount 文件拷贝至镜像```/usr/lib/systemd/system```目录 + + ```toml + [[copy_files]] + dst = "/usr/lib/systemd/system" + src = ".../containerd.service" + + [systemd_service] + name = ["containerd"] + ``` + + * 如需挂载数据盘,请先自定义```persist-data.mount```文件,并启用```copy_files```和```systemd_service```字段设置启动时挂载,启用```persist_mkdir```字段创建挂载点 + * .mount文件名由挂载点路径生成,将斜杠替换为连字符 + * 请先在磁盘映像文件上创建ext4文件系统 - * initramfs.img: 用于pxe启动用的 initramfs 镜像 - * kubeos.tar: pxe安装所用的 OS + ``` + # persist-data.mount + [Unit] + Description=Mount Disk + Documentation=man:systemd.mount(5) + + [Mount] + What=/dev/vdb + Where=/persist/data + Type=ext4 + Options=defaults,noatime + + [Install] + WantedBy=local-fs.target + ``` + + ```toml + [[copy_files]] + dst = "/usr/lib/systemd/system" + src = ".../persist-data.mount" + + [systemd_service] + name = ["persist-data.mount"] + + [persist_mkdir] + name = ["data"] + ``` + + * 如需配置逻辑卷,请先自定义```volume.service```文件,并启用```copy_files```和```systemd_service```设置启动时配置逻辑卷,启用```persist_mkdir```字段创建挂载点 + + ``` + # volume.service + [Unit] + Description=Mount Logical Volume + After=local-fs.target + + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=pvcreate /dev/vdb + ExecStart=pvcreate /dev/vdc + ExecStart=vgcreate my_vg /dev/vdb /dev/vdc + ExecStart=lvcreate -L 15G -n my_lv my_vg + ExecStart=mkfs.ext4 /dev/my_vg/my_lv + ExecStart=mount /dev/my_vg/my_lv /persist/lv_data + + [Install] + WantedBy=local-fs.target + ``` + + ```toml + [[copy_files]] + dst = "/usr/lib/systemd/system" + src = ".../volume.service" + + [systemd_service] + name = ["volume"] + + [persist_mkdir] + name = ["lv_data"] + ``` + +## 使用示例 + +### KubeOS OCI 镜像制作 + +* 如需进行DNS配置,请先自定义```resolv.conf```文件,并启用```copy_files```字段将配置文件拷贝到```/etc```目录 + + ```shell + touch \/resolv.conf + vim \resolv.conf + ``` + + ```toml + [[copy_files]] + dst = "/etc" + src = "" + ``` + +* 制作KubeOS容器镜像 + + ```toml + [from_repo] + agent_path = "/bin/os-agent" + image_type = "upgrade" + legacy_bios = false + repo_path = "xxx.repo" + root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" + version = "v1" + docker_img = "your_imageRepository/imageName:version" + rpmlist = [ + # your rpms + ] + ``` + +* 制作完成后查看制作出来的KubeOS容器镜像 + ``` shell + docker images + ``` + +### KubeOS 虚拟机镜像制作 + +* 使用repo源制作 + + * 如需进行DNS配置,请先自定义```resolv.conf```文件,并启用**copy_files**字段将配置文件拷贝到```/etc```目录 + + ```shell + touch \/resolv.conf + vim \resolv.conf + ``` + + ```toml + [[copy_files]] + dst = "/etc" + src = "" + ``` + + * KubeOS虚拟机镜像制作 + + ```toml + [from_repo] + agent_path = "/bin/os-agent" + image_type = "vm-repo" + legacy_bios = false + repo_path = "xxx.repo" + root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" + version = "v1" + rpmlist = [ + # your rpms + ] + ``` + +* 使用docker镜像制作 + + ```toml + [from_dockerimg] + docker_img = "your_imageRepository/imageName:version" + image_type = "vm-docker" + ``` + +* 结果说明 + 容器 OS 镜像制作完成后,会在 ./scripts-auto 目录下生成 + * system.qcow2: qcow2 格式的系统镜像,大小默认为 20GiB,支持的根文件系统分区大小 < 2020 MiB,持久化分区 < 16GiB 。 + * update.img: 用于升级的根文件系统分区镜像 + +### KubeOS 物理机安装所需镜像及文件制作 + +* 首先需要修改```kbimg.toml```中```pxe_config```的配置,对相关参数进行配置,参数均为必填,ip目前仅支持ipv4,配置示例如下 + + ```toml + [pxe_config] + # rootfs file name + rootfs_name = "kubeos.tar" + # select the target disk to install kubeOS + disk = "/dev/sda" + # pxe server ip address where stores the rootfs on the http server + server_ip = "192.168.1.50" + # target machine ip + local_ip = "192.168.1.100" + # target machine route + route_ip = "192.168.1.1" + # target machine netmask + netmask = "255.255.255.0" + # target machine netDevice name + net_name = "eth0" + ``` + +* 使用 repo 源制作 + * 如需进行DNS配置,请先自定义```resolv.conf```文件,并启用```copy_files```字段将配置文件拷贝到```/etc```目录 + + ```shell + touch \/resolv.conf + vim \resolv.conf + ``` + + ```toml + [[copy_files]] + dst = "/etc" + src = "" + ``` + + * KubeOS物理机安装所需镜像制作 + ```toml + [from_repo] + agent_path = "/bin/os-agent" + image_type = "pxe-repo" + legacy_bios = true + repo_path = "xxx.repo" + root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" + version = "v1" + rpmlist = [ + # your rpms + ] + ``` + +* 使用 docker 镜像制作 + ```toml + [from_dockerimg] + docker_img = "your_imageRepository/imageName:version" + image_type = "vm-docker" + ``` + +* 结果说明 + * initramfs.img: 用于pxe启动用的 initramfs 镜像 + * kubeos.tar: pxe安装所用的 OS diff --git a/pkg/values/values.go b/pkg/values/values.go index f488ae52..9e213a83 100644 --- a/pkg/values/values.go +++ b/pkg/values/values.go @@ -26,13 +26,22 @@ const ( LabelMaster = "node-role.kubernetes.io/control-plane" // LabelOSinstance is used to select the osinstance with the nodeName by label LabelOSinstance = "upgrade.openeuler.org/osinstance-node" - defaultPeriod = 15 * time.Second + // LabelNodeSelector is used to filter the nodes that need to be upgraded or configured. + LabelNodeSelector = "upgrade.openeuler.org/node-selector" + // LabelConfiguring is the key of the configuring label for nodes + LabelConfiguring = "upgrade.openeuler.org/configuring" + // LabelSerial is the key of the serial label for nodes + LabelSerial = "upgrade.openeuler.org/serial" + + defaultPeriod = 15 * time.Second // OsiStatusName is param name of nodeStatus in osInstance OsiStatusName = "nodestatus" // UpgradeConfigName is param name of UpgradeConfig UpgradeConfigName = "UpgradeConfig" // SysConfigName is param name of SysConfig SysConfigName = "SysConfig" + // OsiNamespace is the namespace of osinstance + OsiNamespace = "default" ) // NodeStatus defines state of nodes -- Gitee