diff --git a/Cargo.lock b/Cargo.lock index cbacb12..1ba5cf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,52 @@ version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccf09143e56923c12e027b83a9553210a3c58322ed8419a53461b14a4dccd85" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.43", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262eb9cf7be51269c5f2951eeda9ccd14d6934e437457f47b4f066bf55a6770d" +dependencies = [ + "nom", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -87,10 +133,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "bitflags" -version = "1.3.2" +name = "basic-toml" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778" +dependencies = [ + "serde", +] [[package]] name = "bitflags" @@ -98,20 +147,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "blue-build" version = "0.3.9" dependencies = [ "anyhow", + "askama", "cfg-if", "chrono", "clap", @@ -123,20 +164,9 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "tera", "typed-builder", ] -[[package]] -name = "bstr" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "bumpalo" version = "3.14.0" @@ -172,28 +202,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "chrono-tz" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d7b79e99bfaa0d47da0687c43aa3b7381938a62ad3a6498599039321f660b7" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - [[package]] name = "ci_info" version = "0.10.2" @@ -265,56 +273,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "cpufeatures" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "darling" version = "0.14.4" @@ -381,22 +339,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "deunicode" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "env_logger" version = "0.10.1" @@ -448,16 +390,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getopts" version = "0.2.21" @@ -467,41 +399,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "getrandom" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "globset" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags 1.3.2", - "ignore", - "walkdir", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -570,22 +467,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "ignore" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -632,12 +513,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.151" @@ -668,12 +543,44 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nias" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -689,110 +596,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.43", -] - -[[package]] -name = "pest_meta" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - [[package]] name = "proc-macro2" version = "1.0.71" @@ -811,36 +620,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "regex" version = "1.10.2" @@ -876,7 +655,7 @@ version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -901,15 +680,6 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "serde" version = "1.0.193" @@ -954,33 +724,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slug" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - [[package]] name = "strsim" version = "0.10.0" @@ -1009,28 +752,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tera" -version = "1.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - [[package]] name = "termcolor" version = "1.4.0" @@ -1040,26 +761,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.43", -] - [[package]] name = "toml" version = "0.5.11" @@ -1090,65 +791,12 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.17.0" +name = "unicase" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", + "version_check", ] [[package]] @@ -1181,22 +829,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "wasm-bindgen" version = "0.2.89" diff --git a/Cargo.toml b/Cargo.toml index 0013e0d..5dbb408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["command-line-utilities"] [dependencies] anyhow = "1.0.75" +askama = { version = "0.12.1", features = ["serde-json"] } cfg-if = "1.0.0" chrono = "0.4.31" clap = { version = "4.4.4", features = ["derive"] } @@ -21,7 +22,6 @@ log = "0.4.20" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" serde_yaml = "0.9.25" -tera = "1.19.1" typed-builder = "0.18.0" [features] diff --git a/src/build.rs b/src/build.rs index cdaa5e6..4eed174 100644 --- a/src/build.rs +++ b/src/build.rs @@ -9,7 +9,10 @@ use clap::Args; use log::{debug, error, info, trace, warn}; use typed_builder::TypedBuilder; -use crate::{module_recipe::Recipe, ops, template::TemplateCommand}; +use crate::{ + ops, + template::{Recipe, TemplateCommand}, +}; #[derive(Debug, Clone, Args, TypedBuilder)] pub struct BuildCommand { diff --git a/src/lib.rs b/src/lib.rs index 9f80024..1762643 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,5 @@ pub mod init; #[cfg(feature = "build")] pub mod build; -pub mod module_recipe; mod ops; pub mod template; diff --git a/src/module_recipe.rs b/src/module_recipe.rs index 2b29fa1..8b13789 100644 --- a/src/module_recipe.rs +++ b/src/module_recipe.rs @@ -1,119 +1 @@ -use std::{collections::HashMap, env}; -use chrono::Local; -use log::{debug, info, trace, warn}; -use serde::{Deserialize, Serialize}; -use serde_yaml::Value; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Recipe { - pub name: String, - - pub description: String, - - #[serde(alias = "base-image")] - pub base_image: String, - - #[serde(alias = "image-version")] - pub image_version: String, - - pub modules: Vec, - - pub containerfiles: Option, - - #[serde(flatten)] - pub extra: HashMap, -} - -impl Recipe { - pub fn generate_tags(&self) -> Vec { - debug!("Generating image tags for {}", &self.name); - trace!("Recipe::generate_tags()"); - - let mut tags: Vec = Vec::new(); - let image_version = &self.image_version; - let timestamp = Local::now().format("%Y%m%d").to_string(); - - if let (Ok(commit_branch), Ok(default_branch), Ok(commit_sha), Ok(pipeline_source)) = ( - env::var("CI_COMMIT_REF_NAME"), - env::var("CI_DEFAULT_BRANCH"), - env::var("CI_COMMIT_SHORT_SHA"), - env::var("CI_PIPELINE_SOURCE"), - ) { - trace!("CI_COMMIT_REF_NAME={commit_branch}, CI_DEFAULT_BRANCH={default_branch},CI_COMMIT_SHORT_SHA={commit_sha}, CI_PIPELINE_SOURCE={pipeline_source}"); - warn!("Detected running in Gitlab, pulling information from CI variables"); - - if let Ok(mr_iid) = env::var("CI_MERGE_REQUEST_IID") { - trace!("CI_MERGE_REQUEST_IID={mr_iid}"); - if pipeline_source == "merge_request_event" { - debug!("Running in a MR"); - tags.push(format!("mr-{mr_iid}-{image_version}")); - } - } - - if default_branch != commit_branch { - debug!("Running on branch {commit_branch}"); - tags.push(format!("{commit_branch}-{image_version}")); - } else { - debug!("Running on the default branch"); - tags.push(image_version.to_string()); - tags.push(format!("{image_version}-{timestamp}")); - tags.push(timestamp.to_string()); - } - - tags.push(format!("{commit_sha}-{image_version}")); - } else if let ( - Ok(github_event_name), - Ok(github_event_number), - Ok(github_sha), - Ok(github_ref_name), - ) = ( - env::var("GITHUB_EVENT_NAME"), - env::var("PR_EVENT_NUMBER"), - env::var("GITHUB_SHA"), - env::var("GITHUB_REF_NAME"), - ) { - trace!("GITHUB_EVENT_NAME={github_event_name},PR_EVENT_NUMBER={github_event_number},GITHUB_SHA={github_sha},GITHUB_REF_NAME={github_ref_name}"); - warn!("Detected running in Github, pulling information from GITHUB variables"); - - let mut short_sha = github_sha.clone(); - short_sha.truncate(7); - - if github_event_name == "pull_request" { - debug!("Running in a PR"); - tags.push(format!("pr-{github_event_number}-{image_version}")); - } else if github_ref_name == "live" { - tags.push(image_version.to_owned()); - tags.push(format!("{image_version}-{timestamp}")); - tags.push("latest".to_string()); - } else { - tags.push(format!("br-{github_ref_name}-{image_version}")); - } - tags.push(format!("{short_sha}-{image_version}")); - } else { - warn!("Running locally"); - tags.push(format!("{image_version}-local")); - } - info!("Finished generating tags!"); - debug!("Tags: {tags:#?}"); - tags - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Module { - #[serde(rename = "type")] - pub module_type: Option, - - #[serde(rename = "from-file")] - pub from_file: Option, - - #[serde(flatten)] - pub config: HashMap, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Containerfiles { - pub pre: Option>, - pub post: Option>, -} diff --git a/src/template.rs b/src/template.rs index 69a407e..9ceab93 100644 --- a/src/template.rs +++ b/src/template.rs @@ -6,15 +6,139 @@ use std::{ }; use anyhow::Result; +use askama::Template; +use chrono::Local; use clap::Args; -use log::{debug, error, info, trace}; -use tera::{Context, Tera}; +use log::{debug, error, info, trace, warn}; +use serde::{Deserialize, Serialize}; +use serde_yaml::Value; use typed_builder::TypedBuilder; -use crate::module_recipe::Recipe; +#[derive(Debug, Clone, Template, TypedBuilder)] +#[template(path = "Containerfile")] +pub struct ContainerFileTemplate<'a> { + recipe: &'a Recipe, + recipe_path: &'a Path, -pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.tera"); -pub const EXPORT_SCRIPT: &str = include_str!("../templates/export.sh"); + #[builder(default)] + export_script: ExportsTemplate, +} + +#[derive(Debug, Clone, Default, Template)] +#[template(path = "export.sh", escape = "none")] +pub struct ExportsTemplate; + +#[derive(Serialize, Clone, Deserialize, Debug)] +pub struct Recipe { + pub name: String, + + pub description: String, + + #[serde(alias = "base-image")] + pub base_image: String, + + #[serde(alias = "image-version")] + pub image_version: String, + + #[serde(flatten)] + pub modules_ext: ModuleExt, + + #[serde(flatten)] + pub extra: HashMap, +} + +impl Recipe { + pub fn generate_tags(&self) -> Vec { + debug!("Generating image tags for {}", &self.name); + trace!("Recipe::generate_tags()"); + + let mut tags: Vec = Vec::new(); + let image_version = &self.image_version; + let timestamp = Local::now().format("%Y%m%d").to_string(); + + if let (Ok(commit_branch), Ok(default_branch), Ok(commit_sha), Ok(pipeline_source)) = ( + env::var("CI_COMMIT_REF_NAME"), + env::var("CI_DEFAULT_BRANCH"), + env::var("CI_COMMIT_SHORT_SHA"), + env::var("CI_PIPELINE_SOURCE"), + ) { + trace!("CI_COMMIT_REF_NAME={commit_branch}, CI_DEFAULT_BRANCH={default_branch},CI_COMMIT_SHORT_SHA={commit_sha}, CI_PIPELINE_SOURCE={pipeline_source}"); + warn!("Detected running in Gitlab, pulling information from CI variables"); + + if let Ok(mr_iid) = env::var("CI_MERGE_REQUEST_IID") { + trace!("CI_MERGE_REQUEST_IID={mr_iid}"); + if pipeline_source == "merge_request_event" { + debug!("Running in a MR"); + tags.push(format!("mr-{mr_iid}-{image_version}")); + } + } + + if default_branch != commit_branch { + debug!("Running on branch {commit_branch}"); + tags.push(format!("{commit_branch}-{image_version}")); + } else { + debug!("Running on the default branch"); + tags.push(image_version.to_string()); + tags.push(format!("{image_version}-{timestamp}")); + tags.push(timestamp.to_string()); + } + + tags.push(format!("{commit_sha}-{image_version}")); + } else if let ( + Ok(github_event_name), + Ok(github_event_number), + Ok(github_sha), + Ok(github_ref_name), + ) = ( + env::var("GITHUB_EVENT_NAME"), + env::var("PR_EVENT_NUMBER"), + env::var("GITHUB_SHA"), + env::var("GITHUB_REF_NAME"), + ) { + trace!("GITHUB_EVENT_NAME={github_event_name},PR_EVENT_NUMBER={github_event_number},GITHUB_SHA={github_sha},GITHUB_REF_NAME={github_ref_name}"); + warn!("Detected running in Github, pulling information from GITHUB variables"); + + let mut short_sha = github_sha.clone(); + short_sha.truncate(7); + + if github_event_name == "pull_request" { + debug!("Running in a PR"); + tags.push(format!("pr-{github_event_number}-{image_version}")); + } else if github_ref_name == "live" { + tags.push(image_version.to_owned()); + tags.push(format!("{image_version}-{timestamp}")); + tags.push("latest".to_string()); + } else { + tags.push(format!("br-{github_ref_name}-{image_version}")); + } + tags.push(format!("{short_sha}-{image_version}")); + } else { + warn!("Running locally"); + tags.push(format!("{image_version}-local")); + } + info!("Finished generating tags!"); + debug!("Tags: {tags:#?}"); + tags + } +} + +#[derive(Serialize, Clone, Deserialize, Debug, Template)] +#[template(path = "Containerfile.module", escape = "none")] +pub struct ModuleExt { + pub modules: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Module { + #[serde(rename = "type")] + pub module_type: Option, + + #[serde(rename = "from-file")] + pub from_file: Option, + + #[serde(flatten)] + pub config: HashMap, +} #[derive(Debug, Clone, Args, TypedBuilder)] pub struct TemplateCommand { @@ -50,14 +174,16 @@ impl TemplateCommand { fn template_file(&self) -> Result<()> { trace!("TemplateCommand::template_file()"); - debug!("Setting up tera"); - let (tera, context) = self.setup_tera()?; + debug!("Deserializing recipe"); + let recipe_de = serde_yaml::from_str::(fs::read_to_string(&self.recipe)?.as_str())?; + trace!("recipe_de: {recipe_de:#?}"); - trace!("tera: {tera:#?}"); - trace!("context: {context:#?}"); + let template = ContainerFileTemplate::builder() + .recipe(&recipe_de) + .recipe_path(&self.recipe) + .build(); - debug!("Rendering Containerfile"); - let output_str = tera.render("Containerfile", &context)?; + let output_str = template.render()?; match self.output.as_ref() { Some(output) => { @@ -75,137 +201,83 @@ impl TemplateCommand { info!("Finished templating Containerfile"); Ok(()) } +} - fn setup_tera(&self) -> Result<(Tera, Context)> { - trace!("TemplateCommand::setup_tera()"); +fn print_script(script_contents: &ExportsTemplate) -> String { + trace!("print_script({script_contents})"); - debug!("Deserializing recipe"); - let recipe_de = serde_yaml::from_str::(fs::read_to_string(&self.recipe)?.as_str())?; - trace!("recipe_de: {recipe_de:#?}"); + format!( + "\"{}\"", + script_contents + .render() + .unwrap_or_else(|e| { + error!("Failed to render export.sh script: {e}"); + process::exit(1); + }) + .replace('\n', "\\n") + .replace('\"', "\\\"") + .replace('$', "\\$") + ) +} - debug!("Building context"); - let mut context = Context::from_serialize(recipe_de)?; - - trace!("add to context 'recipe': {}", self.recipe.display()); - context.insert("recipe", &self.recipe); - - let mut tera = Tera::default(); - - match self.containerfile.as_ref() { - Some(containerfile) => { - debug!("Using {} as the template", containerfile.display()); - tera.add_raw_template("Containerfile", &fs::read_to_string(containerfile)?)? - } - None => tera.add_raw_template("Containerfile", DEFAULT_CONTAINERFILE)?, - } - - debug!("Registering function `print_containerfile`"); - tera.register_function( - "print_containerfile", - |args: &HashMap| -> tera::Result { - trace!("tera fn print_containerfile({args:#?})"); - match args.get("containerfile") { - Some(v) => match v.as_str() { - Some(containerfile) => { - debug!("Loading containerfile contents for {containerfile}"); - - let path = - format!("config/containerfiles/{containerfile}/Containerfile"); - let path = Path::new(path.as_str()); - - let file = fs::read_to_string(path)?; - - trace!("Containerfile contents {}:\n{file}", path.display()); - Ok(file.into()) - } - None => Err("Arg containerfile wasn't a string".into()), - }, - None => { - Err("Needs the argument 'containerfile' for print_containerfile()".into()) - } - } - }, - ); - - debug!("Registering function `print_module_context`"); - tera.register_function( - "print_module_context", - |args: &HashMap| -> tera::Result { - trace!("tera fn print_module_context({args:#?})"); - match args.get("module") { - Some(v) => match serde_json::to_string(v) { - Ok(s) => Ok(s.into()), - Err(e) => Err(format!("Unable to serialize: {e}").into()), - }, - None => Err("Needs the argument 'module' for print_module_context()".into()), - } - }, - ); - - debug!("Registering function `get_module_from_file`"); - tera.register_function( - "get_module_from_file", - |args: &HashMap| -> tera::Result { - trace!("tera fn get_module_from_file({args:#?})"); - match args.get("file") { - Some(v) => { - let file = match v.as_str() { - Some(s) => s, - None => return Err("Property 'from-file' must be a string".into()), - }; - - trace!("from-file: {file}"); - match serde_yaml::from_str::( - fs::read_to_string(format!("config/{file}"))?.as_str(), - ) { - Ok(context) => { - trace!("context: {context}"); - Ok(context) - } - Err(_) => Err(format!("Unable to deserialize file {file}").into()), - } - } - None => Err("Needs the argument 'file' for get_module_from_file()".into()), - } - }, - ); - - debug!("Registering function `running_gitlab_actions`"); - tera.register_function( - "running_gitlab_actions", - |_: &HashMap| -> tera::Result { - trace!("tera fn running_gitlab_actions()"); - - Ok(env::var("GITHUB_ACTIONS").is_ok_and(|e| e == "true").into()) - }, - ); - - debug!("Registering function `print_script`"); - tera.register_function( - "print_script", - |args: &HashMap| -> tera::Result { - trace!("tera fn print_script({args:#?})"); - - let escape_script = |script_contents: &str| { - format!( - "\"{}\"", - script_contents - .replace('\n', "\\n") - .replace('\"', "\\\"") - .replace('$', "\\$") - ) - }; - - match args.get("script") { - Some(x) => match x.as_str().unwrap_or_default() { - "export" => Ok(escape_script(EXPORT_SCRIPT).into()), - _ => Err(format!("Script {x} doesn't exist").into()), - }, - None => Err("Needs the argument 'script' for 'print_script()'".into()), - } - }, - ); - - Ok((tera, context)) +fn get_containerfile_list(module: &Module) -> Option> { + if module.module_type.as_ref()? == "containerfile" { + Some( + module + .config + .get("containerfiles")? + .as_sequence()? + .iter() + .filter_map(|t| Some(t.as_str()?.to_owned())) + .collect(), + ) + } else { + None } } + +fn print_containerfile(containerfile: &str) -> String { + trace!("print_containerfile({containerfile})"); + debug!("Loading containerfile contents for {containerfile}"); + + let path = format!("config/containerfiles/{containerfile}/Containerfile"); + + let file = fs::read_to_string(&path).unwrap_or_else(|e| { + error!("Failed to read file {path}: {e}"); + process::exit(1); + }); + + trace!("Containerfile contents {path}:\n{file}"); + + file +} + +fn get_module_from_file(file: &str) -> ModuleExt { + trace!("get_module_from_file({file})"); + + serde_yaml::from_str( + fs::read_to_string(format!("config/{file}").as_str()) + .unwrap_or_else(|e| { + error!("Failed to read module {file}: {e}"); + process::exit(1); + }) + .as_str(), + ) + .unwrap_or_else(|e| { + error!("Failed to parse {file}: {e}"); + process::exit(1); + }) +} + +fn print_module_context(module: &Module) -> String { + serde_json::to_string(module).unwrap_or_else(|e| { + error!("Failed to parse module: {e}"); + process::exit(1); + }) +} + +fn running_gitlab_actions() -> bool { + trace!(" running_gitlab_actions()"); + + env::var("GITHUB_ACTIONS").is_ok_and(|e| e == "true") +} diff --git a/templates/Containerfile b/templates/Containerfile new file mode 100644 index 0000000..c01035e --- /dev/null +++ b/templates/Containerfile @@ -0,0 +1,40 @@ +FROM {{ recipe.base_image }}:{{ recipe.image_version }} + +LABEL org.opencontainers.image.title="{{ recipe.name }}" +LABEL org.opencontainers.image.version="{{ recipe.image_version }}" +LABEL org.opencontainers.image.description="{{ recipe.description }}" +LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/ublue-os/startingpoint/main/README.md +LABEL io.artifacthub.package.logo-url=https://avatars.githubusercontent.com/u/120078124?s=200&v=4 + +ARG RECIPE={{ recipe_path.display() }} +{%- if self::running_gitlab_actions() %} +ARG IMAGE_REGISTRY=ghcr.io/ublue-os +COPY cosign.pub /usr/share/ublue-os/cosign.pub +{%- endif %} + +# Copy the bling from ublue-os/bling into tmp, to be installed later by the bling module +# Feel free to remove these lines if you want to speed up image builds and don't want any bling +COPY --from=ghcr.io/ublue-os/bling:latest /rpms /tmp/bling/rpms +COPY --from=ghcr.io/ublue-os/bling:latest /files /tmp/bling/files + +COPY --from=docker.io/mikefarah/yq /usr/bin/yq /usr/bin/yq + +COPY --from=gcr.io/projectsigstore/cosign /ko-app/cosign /usr/bin/cosign + +COPY config /tmp/config/ + +# Copy modules +# The default modules are inside ublue-os/bling +COPY --from=ghcr.io/ublue-os/bling:latest /modules /tmp/modules/ +# Custom modules overwrite defaults +COPY modules /tmp/modules/ + +RUN printf {{ self::print_script(export_script) }} >> /tmp/exports.sh && chmod +x /tmp/exports.sh + +ARG CONFIG_DIRECTORY="/tmp/config" +ARG IMAGE_NAME="{{ recipe.name }}" +ARG BASE_IMAGE="{{ recipe.base_image }}" + +{{ recipe.modules_ext }} + +RUN rm -rf /tmp/* /var/* && ostree container commit diff --git a/templates/Containerfile.module b/templates/Containerfile.module new file mode 100644 index 0000000..fc078ae --- /dev/null +++ b/templates/Containerfile.module @@ -0,0 +1,17 @@ +{%- for module in modules %} + {%- if let Some(type) = module.module_type %} + {%- if type == "containerfile" %} + {%- if let Some(containerfiles) = self::get_containerfile_list(module) %} + {%- for c in containerfiles %} +{{ self::print_containerfile(c) }} + {%- endfor %} + {%- endif %} + {%- else %} +RUN chmod +x /tmp/modules/{{ type }}/{{ type }}.sh && source /tmp/exports.sh && /tmp/modules/{{ type }}/{{ type }}.sh '{{ self::print_module_context(module) }}' + {%- endif %} + {%- endif %} + {%- if let Some(from_file) = module.from_file %} +{{ self::get_module_from_file(from_file).render().unwrap() }} + {%- endif %} +{%- endfor %} + diff --git a/templates/Containerfile.tera b/templates/Containerfile.tera deleted file mode 100644 index 0bc6cad..0000000 --- a/templates/Containerfile.tera +++ /dev/null @@ -1,59 +0,0 @@ -FROM {{ base_image }}:{{ image_version }} - -LABEL org.opencontainers.image.title="{{ name }}" -LABEL org.opencontainers.image.version="{{ image_version }}" -LABEL org.opencontainers.image.description="{{ description }}" -LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/ublue-os/startingpoint/main/README.md -LABEL io.artifacthub.package.logo-url=https://avatars.githubusercontent.com/u/120078124?s=200&v=4 - -ARG RECIPE={{ recipe }} -{%- if running_gitlab_actions() %} -ARG IMAGE_REGISTRY=ghcr.io/ublue-os -COPY cosign.pub /usr/share/ublue-os/cosign.pub -{%- endif %} - -# Copy the bling from ublue-os/bling into tmp, to be installed later by the bling module -# Feel free to remove these lines if you want to speed up image builds and don't want any bling -COPY --from=ghcr.io/ublue-os/bling:latest /rpms /tmp/bling/rpms -COPY --from=ghcr.io/ublue-os/bling:latest /files /tmp/bling/files - -COPY --from=docker.io/mikefarah/yq /usr/bin/yq /usr/bin/yq - -COPY --from=gcr.io/projectsigstore/cosign /ko-app/cosign /usr/bin/cosign - -COPY config /tmp/config/ - -# Copy modules -# The default modules are inside ublue-os/bling -COPY --from=ghcr.io/ublue-os/bling:latest /modules /tmp/modules/ -# Custom modules overwrite defaults -COPY modules /tmp/modules/ - -RUN printf {{ print_script(script = "export") }} >> /tmp/exports.sh && chmod +x /tmp/exports.sh - -ARG CONFIG_DIRECTORY="/tmp/config" -ARG IMAGE_NAME="{{ name }}" -ARG BASE_IMAGE="{{ base_image }}" - -{%- macro run_modules(module) %} - {%- if module.type %} - {%- if module.type == "containerfile" %} - {%- for c in module.containerfiles %} -{{ print_containerfile(containerfile = c ) }} - {%- endfor %} - {%- else %} -RUN chmod +x /tmp/modules/{{ module.type }}/{{ module.type }}.sh && source /tmp/exports.sh && /tmp/modules/{{ module.type }}/{{ module.type }}.sh '{{ print_module_context(module = module) }}' - {%- endif %} - {%- elif module["from-file"] %} - {%- set extra_module = get_module_from_file(file = module["from-file"]) %} - {%- for m in extra_module.modules %} -{{ self::run_modules(module = m) }} - {%- endfor %} - {%- endif %} -{%- endmacro run_modules %} - -{%- for module in modules %} -{{ self::run_modules(module = module) }} -{%- endfor %} - -RUN rm -rf /tmp/* /var/* && ostree container commit