refactor: Use askama crate for compile-time template type checking

This commit is contained in:
Gerald Pinder 2024-01-03 23:38:08 -05:00
parent 039c5f9659
commit d663b7574b
9 changed files with 364 additions and 778 deletions

548
Cargo.lock generated
View file

@ -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"

View file

@ -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]

View file

@ -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 {

View file

@ -6,6 +6,5 @@ pub mod init;
#[cfg(feature = "build")]
pub mod build;
pub mod module_recipe;
mod ops;
pub mod template;

View file

@ -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<Module>,
pub containerfiles: Option<Containerfiles>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl Recipe {
pub fn generate_tags(&self) -> Vec<String> {
debug!("Generating image tags for {}", &self.name);
trace!("Recipe::generate_tags()");
let mut tags: Vec<String> = 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<String>,
#[serde(rename = "from-file")]
pub from_file: Option<String>,
#[serde(flatten)]
pub config: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Containerfiles {
pub pre: Option<Vec<String>>,
pub post: Option<Vec<String>>,
}

View file

@ -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<String, Value>,
}
impl Recipe {
pub fn generate_tags(&self) -> Vec<String> {
debug!("Generating image tags for {}", &self.name);
trace!("Recipe::generate_tags()");
let mut tags: Vec<String> = 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<Module>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Module {
#[serde(rename = "type")]
pub module_type: Option<String>,
#[serde(rename = "from-file")]
pub from_file: Option<String>,
#[serde(flatten)]
pub config: HashMap<String, Value>,
}
#[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::<Recipe>(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::<Recipe>(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<String, tera::Value>| -> tera::Result<tera::Value> {
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<String, tera::Value>| -> tera::Result<tera::Value> {
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<String, tera::Value>| -> tera::Result<tera::Value> {
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::<tera::Value>(
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<String, tera::Value>| -> tera::Result<tera::Value> {
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<String, tera::Value>| -> tera::Result<tera::Value> {
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<Vec<String>> {
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")
}

40
templates/Containerfile Normal file
View file

@ -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

View file

@ -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 %}

View file

@ -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