From 6b0033f6adff026b853f4760ddf8f3c6bf6d9a48 Mon Sep 17 00:00:00 2001 From: Thomas DA ROCHA Date: Thu, 14 Nov 2024 16:52:24 +0100 Subject: [PATCH] feat: Fail on cache artifact use (#305) * docs: Update readme since BuildKit is Docker default builder * build: Chmod as int instead of string * feat: Manage LintSession and messages * feat: Error message * feat: Error message * style: Format * fix: Better error management * test: Add unit tests * build(deps): Update dependencies * fix: Some improvments --- Cargo.lock | 347 +++++++-------- Cargo.toml | 2 +- README.md | 10 +- dofigen.lock | 4 +- dofigen.yml | 2 +- src/bin/commands/effective.rs | 2 +- src/bin/commands/generate.rs | 38 +- src/bin/commands/mod.rs | 11 +- src/bin/commands/update.rs | 2 +- src/bin/main.rs | 3 +- src/context.rs | 1 + src/dofigen_struct.rs | 8 +- src/errors.rs | 2 +- src/generator.rs | 815 +++++++++++++++++++++++++++------- src/lib.rs | 28 +- tests/cli_test.rs | 2 +- tests/lib_test.rs | 2 +- 17 files changed, 876 insertions(+), 403 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3c1b88..f202cb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -43,43 +43,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "assert_cmd" @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -131,23 +131,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata", @@ -190,15 +190,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.16" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] @@ -211,9 +211,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -245,15 +245,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -293,9 +293,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -430,9 +430,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "float-cmp" @@ -512,9 +512,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -536,21 +536,21 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -559,21 +559,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -610,15 +610,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -762,9 +762,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -816,9 +816,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -829,7 +829,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -975,9 +974,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", @@ -991,9 +990,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -1001,9 +1000,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -1019,9 +1018,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1034,9 +1033,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "linux-raw-sys" @@ -1076,11 +1075,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1139,24 +1138,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags", "cfg-if", @@ -1186,18 +1185,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1221,31 +1220,11 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1255,9 +1234,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "predicates" @@ -1291,9 +1270,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -1311,9 +1290,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1341,9 +1320,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1429,9 +1408,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags", "errno", @@ -1442,9 +1421,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", "rustls-pki-types", @@ -1455,25 +1434,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1497,11 +1475,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1544,9 +1522,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -1554,18 +1532,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -1772,9 +1750,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -1791,18 +1769,18 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.67" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.67" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", @@ -1821,9 +1799,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -1880,27 +1858,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -1940,9 +1897,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unsafe-libyaml" @@ -2040,9 +1997,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2051,9 +2008,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -2066,9 +2023,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2078,9 +2035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2088,9 +2045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -2101,15 +2058,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2316,9 +2273,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" diff --git a/Cargo.toml b/Cargo.toml index c9cd3a9..9d0d57c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ strict = [] serde = "1.0" serde_yaml = "0.9.34" serde_json = { version = "1.0.132", optional = true } -thiserror = "1.0.67" +thiserror = "2.0" clap = { version = "4.5", features = ["derive"], optional = true } schemars = { version = "0.8.21", optional = true, features = ["url"] } regex = "1.11.1" diff --git a/README.md b/README.md index 2f47ff5..0d2a472 100755 --- a/README.md +++ b/README.md @@ -37,13 +37,7 @@ Dofigen is a Dockerfile generator using a simplified description in YAML or JSON format. It defines default values and behaviors that simplify the creation of Dockerfiles. -Dofigen is also made to use the Buildkit optimizations that speed-up the Docker image build by parallelizing the layer builds. -It uses the [`--link` option](https://docs.docker.com/engine/reference/builder/#benefits-of-using---link) when adding files and the [`--mount=type=cache` option](https://docs.docker.com/engine/reference/builder/#run---mounttypecache) when running scripts (when you define `caches` attribute). -You can use Buildkit with the [`docker buildx build` subcommand](https://docs.docker.com/engine/reference/commandline/buildx_build/) like this: - -```bash -docker buildx build --cache-to=type=local,dest=.dockercache --cache-from=type=local,src=.dockercache -t my-app:latest --load . -``` +Dofigen is also made to use the Buildkit, which is now the default Docker build engine, optimizations that speed-up the Docker image build by parallelizing the layer builds. A french DevOps said about it: > C'est une bouffée, Dofigen, dans ce monde de con...teneurs. @@ -78,7 +72,7 @@ You can download the Dofigen binary from [the release page](https://github.com/l You can run Dofigen directly from its Docker image with the following command: ```bash -docker run --rm -it -v $(pwd):/app lenra/dofigen +docker run --rm -it -v $(pwd):/app lenra/dofigen --help ```

(back to top)

diff --git a/dofigen.lock b/dofigen.lock index 74b25cc..65eb233 100644 --- a/dofigen.lock +++ b/dofigen.lock @@ -16,7 +16,7 @@ effective: | images: {} resources: dofigen.yml: - hash: 07744cc5d5e65e7ebf07298fca4f239bf7296f9109c0fb60b177236291ccf61c + hash: 95a420edbdb40b289e59dda6f5ff2c0295aacf48a229ce39e86fddaa17eea59c content: | # Runtime workdir: /app @@ -26,7 +26,7 @@ resources: copy: - paths: builds/${TARGETPLATFORM}/dofigen target: /bin/dofigen - chmod: "555" + chmod: 555 entrypoint: /bin/dofigen cmd: --help context: diff --git a/dofigen.yml b/dofigen.yml index 525df79..8e0916f 100644 --- a/dofigen.yml +++ b/dofigen.yml @@ -6,7 +6,7 @@ arg: copy: - paths: builds/${TARGETPLATFORM}/dofigen target: /bin/dofigen - chmod: "555" + chmod: 555 entrypoint: /bin/dofigen cmd: --help context: diff --git a/src/bin/commands/effective.rs b/src/bin/commands/effective.rs index 37fd6d7..bee50c5 100644 --- a/src/bin/commands/effective.rs +++ b/src/bin/commands/effective.rs @@ -21,7 +21,7 @@ pub struct Effective { impl CliCommand for Effective { fn run(self) -> Result<()> { - let path = get_file_path(&self.options.file); + let path = get_file_path(&self.options.file)?; let lockfile_path = get_lockfile_path(path.clone()); let lockfile = load_lockfile(lockfile_path.clone()); let mut context = lockfile diff --git a/src/bin/commands/generate.rs b/src/bin/commands/generate.rs index b1ae05e..fec78af 100644 --- a/src/bin/commands/generate.rs +++ b/src/bin/commands/generate.rs @@ -5,10 +5,11 @@ use super::{get_file_path, get_image_from_path, get_lockfile_path, load_lockfile}; use crate::{CliCommand, GlobalOptions}; use clap::Args; +use colored::{Color, Colorize}; use dofigen_lib::{ - generate_dockerfile, generate_dockerignore, + generate_dockerignore, lock::{Lock, LockFile}, - DofigenContext, Error, Result, + DofigenContext, Error, GenerationContext, MessageLevel, Result, }; use std::{fs, path::PathBuf}; @@ -48,7 +49,7 @@ impl Generate { impl CliCommand for Generate { fn run(self) -> Result<()> { - let path = get_file_path(&self.options.file); + let path = get_file_path(&self.options.file)?; let lockfile_path = get_lockfile_path(path.clone()); let lockfile = load_lockfile(lockfile_path.clone()); let mut context = lockfile @@ -88,7 +89,36 @@ impl CliCommand for Generate { locked_image }; - let dockerfile_content = generate_dockerfile(&dofigen)?; + let mut generation_context = GenerationContext::from(&dofigen); + + let dockerfile_content = generation_context.generate_dockerfile(&dofigen)?; + + let messages = generation_context.get_lint_messages().clone(); + + messages.iter().for_each(|message| { + eprintln!( + "{}[path={}]: {}", + match message.level { + MessageLevel::Error => "error".color(Color::Red).bold(), + MessageLevel::Warn => "warning".color(Color::Yellow).bold(), + }, + message.path.join(".").color(Color::Blue).bold(), + message.message + ); + }); + + let errors = messages + .iter() + .filter(|m| m.level == MessageLevel::Error) + .count(); + + if errors > 0 { + return Err(Error::Custom(format!( + "Could not generate the Dockerfile due to {} previous error{}", + errors, + if errors > 1 { "s" } else { "" } + ))); + } if self.output == "-" { print!("{}", dockerfile_content); diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index c076012..f18f633 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,4 +1,4 @@ -use dofigen_lib::{lock::LockFile, Dofigen, DofigenContext, Resource, Result}; +use dofigen_lib::{lock::LockFile, Dofigen, DofigenContext, Error, Resource, Result}; use std::path::PathBuf; pub mod effective; @@ -7,17 +7,16 @@ pub mod generate; pub mod schema; pub mod update; -pub(crate) fn get_file_path(path: &Option) -> String { +pub(crate) fn get_file_path(path: &Option) -> Result { if let Some(path) = path { - path.clone() + Ok(path.clone()) } else { let mut files = vec!["dofigen.yml", "dofigen.yaml", "dofigen.json"]; files.retain(|f| std::path::Path::new(f).exists()); if files.is_empty() { - eprintln!("No Dofigen file found"); - std::process::exit(1); + return Err(Error::Custom("No Dofigen file found".into())); } - files[0].into() + Ok(files[0].into()) } } diff --git a/src/bin/commands/update.rs b/src/bin/commands/update.rs index d0f563b..1e82ff0 100644 --- a/src/bin/commands/update.rs +++ b/src/bin/commands/update.rs @@ -23,7 +23,7 @@ pub struct Update { impl CliCommand for Update { fn run(self) -> Result<()> { // Get lock file from the file - let path = get_file_path(&self.options.file); + let path = get_file_path(&self.options.file)?; if path == "-" { return Err(Error::Custom( "Update command can't be used with stdin".into(), diff --git a/src/bin/main.rs b/src/bin/main.rs index d6ee830..9f32681 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,5 @@ use clap::{Args, Parser, Subcommand}; +use colored::{Color, Colorize}; #[cfg(feature = "json_schema")] use commands::schema::Schema; use commands::{effective::Effective, generate::Generate, update::Update}; @@ -65,7 +66,7 @@ impl Command { fn main() { Cli::parse().command.run().unwrap_or_else(|e| { - eprintln!("{}", e); + eprintln!("{}: {}", "error".color(Color::Red).bold(), e); std::process::exit(1); }); } diff --git a/src/context.rs b/src/context.rs index caf827d..a4b183c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -26,6 +26,7 @@ pub struct DofigenContext { load_resource_stack: Vec, resources: HashMap, used_resources: HashSet, + // Images tags images: HashMap, used_images: HashSet, diff --git a/src/dofigen_struct.rs b/src/dofigen_struct.rs index 7550031..b0cec43 100644 --- a/src/dofigen_struct.rs +++ b/src/dofigen_struct.rs @@ -237,6 +237,10 @@ pub struct Cache { pub id: Option, /// The target path of the cache + #[cfg_attr( + not(feature = "strict"), + patch(attribute(serde(alias = "dst", alias = "destination"))) + )] pub target: String, /// Defines if the cache is readonly @@ -253,7 +257,7 @@ pub struct Cache { #[patch(name = "FromContextPatch", attribute(serde(flatten)))] pub from: FromContext, - /// Subpath in the from to mount + /// Subpath in the from to mount. Defaults to the root of the from #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, @@ -297,7 +301,7 @@ pub struct Bind { #[patch(name = "FromContextPatch", attribute(serde(flatten)))] pub from: FromContext, - /// Subpath in the from to mount + /// Source path in the from. Defaults to the root of the from #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, diff --git a/src/errors.rs b/src/errors.rs index f6b01e4..db3a0f3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,7 +7,7 @@ pub type Result = std::result::Result; #[derive(Error, Debug)] pub enum Error { - #[error("Error while deserializing the document{}: {0}", location_into(.0.location()))] + #[error("Error while deserializing the document{loc}: {0}", loc = location_into(.0.location()))] Deserialize(#[from] serde_yaml::Error), #[error("Error while parsing: {0}")] ParseFromStr(#[from] serde::de::value::Error), diff --git a/src/generator.rs b/src/generator.rs index 3a96095..1f30e1c 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,20 +1,98 @@ use std::collections::{HashMap, HashSet}; -use crate::{dockerfile_struct::*, dofigen_struct::*, Error, Result, DOCKERFILE_VERSION}; +use crate::{ + dockerfile_struct::*, dofigen_struct::*, Result, DOCKERFILE_VERSION, FILE_HEADER_COMMENTS, +}; pub const LINE_SEPARATOR: &str = " \\\n "; pub const DEFAULT_FROM: &str = "scratch"; -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq)] pub struct GenerationContext { - pub user: Option, - pub wokdir: Option, - pub stage_name: String, - pub default_from: FromContext, + pub(crate) user: Option, + pub(crate) stage_name: String, + pub(crate) default_from: FromContext, + state_stack: Vec, + pub(crate) lint_session: LintSession, } + +impl GenerationContext { + pub fn get_lint_messages(&self) -> Vec { + self.lint_session.messages.clone() + } + + fn push_state(&mut self, state: GenerationContextState) { + let mut prev_state = GenerationContextState::default(); + if let Some(user) = &state.user { + prev_state.user = Some(self.user.clone()); + self.user = user.clone(); + } + if let Some(stage_name) = &state.stage_name { + prev_state.stage_name = Some(self.stage_name.clone()); + self.stage_name = stage_name.clone(); + } + if let Some(default_from) = &state.default_from { + prev_state.default_from = Some(self.default_from.clone()); + self.default_from = default_from.clone(); + } + self.state_stack.push(prev_state); + } + + fn pop_state(&mut self) { + let prev_state = self.state_stack.pop().expect("The state stack is empty"); + if let Some(user) = prev_state.user { + self.user = user; + } + if let Some(stage_name) = prev_state.stage_name { + self.stage_name = stage_name; + } + if let Some(default_from) = prev_state.default_from { + self.default_from = default_from; + } + } + + pub fn from(dofigen: &Dofigen) -> Self { + Self { + user: None, + stage_name: String::default(), + default_from: FromContext::default(), + lint_session: LintSession::analyze(dofigen), + state_stack: vec![], + } + } + + pub fn generate_dockerfile(&mut self, dofigen: &Dofigen) -> Result { + let mut lines = dofigen.generate_dockerfile_lines(self)?; + let mut line_number = 1; + + for line in FILE_HEADER_COMMENTS { + lines.insert(line_number, DockerfileLine::Comment(line.to_string())); + line_number += 1; + } + + Ok(format!( + "{}\n", + lines + .iter() + .map(DockerfileLine::generate_content) + .collect::>() + .join("\n") + )) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct GenerationContextState { + user: Option>, + stage_name: Option, + default_from: Option, +} + pub trait DockerfileGenerator { - fn generate_dockerfile_lines(&self, context: &GenerationContext) - -> Result>; + fn generate_dockerfile_lines( + &self, + context: &mut GenerationContext, + ) -> Result>; } impl Stage { @@ -183,7 +261,7 @@ impl ToString for FromContext { impl DockerfileGenerator for CopyResource { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { match self { CopyResource::Copy(copy) => copy.generate_dockerfile_lines(context), @@ -214,7 +292,7 @@ fn add_copy_options( impl DockerfileGenerator for Copy { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { let mut options: Vec = vec![]; @@ -248,7 +326,7 @@ impl DockerfileGenerator for Copy { impl DockerfileGenerator for Add { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { let mut options: Vec = vec![]; if let Some(checksum) = &self.checksum { @@ -276,7 +354,7 @@ impl DockerfileGenerator for Add { impl DockerfileGenerator for AddGitRepo { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { let mut options: Vec = vec![]; add_copy_options(&mut options, &self.options, context); @@ -305,34 +383,40 @@ impl DockerfileGenerator for AddGitRepo { impl DockerfileGenerator for Dofigen { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { - let mut context: GenerationContext = GenerationContext { - user: None, - wokdir: None, - stage_name: String::new(), - default_from: self.stage.from(context).clone(), - }; + context.push_state(GenerationContextState { + default_from: Some(self.stage.from(context).clone()), + ..Default::default() + }); let mut lines = vec![ DockerfileLine::Comment(format!("syntax=docker/dockerfile:{}", DOCKERFILE_VERSION)), DockerfileLine::Empty, ]; - let stage_resolver = &mut StagesDependencyResolver::new(self); - - for name in stage_resolver.get_sorted_builders()? { - context.stage_name = name.clone(); + for name in context.lint_session.get_sorted_builders() { + context.push_state(GenerationContextState { + stage_name: Some(name.clone()), + ..Default::default() + }); let builder = self .builders .get(&name) - .ok_or(Error::Custom(format!("The builder '{}' not found", name)))?; - lines.append(&mut Stage::generate_dockerfile_lines(builder, &context)?); + .expect(format!("The builder '{}' not found", name).as_str()); + + lines.append(&mut Stage::generate_dockerfile_lines(builder, context)?); lines.push(DockerfileLine::Empty); + context.pop_state(); } - context.user = Some(User::new("1000")); - context.stage_name = "runtime".into(); - context.default_from = FromContext::default(); - lines.append(&mut self.stage.generate_dockerfile_lines(&context)?); + + context.push_state(GenerationContextState { + user: Some(Some(User::new("1000"))), + stage_name: Some("runtime".into()), + default_from: Some(FromContext::default()), + }); + lines.append(&mut self.stage.generate_dockerfile_lines(context)?); + context.pop_state(); + self.expose.iter().for_each(|port| { lines.push(DockerfileLine::Instruction(DockerfileInsctruction { command: "EXPOSE".into(), @@ -393,13 +477,12 @@ impl DockerfileGenerator for Dofigen { impl DockerfileGenerator for Stage { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { - let context = GenerationContext { - user: self.user(context), - wokdir: self.workdir.clone(), - ..context.clone() - }; + context.push_state(GenerationContextState { + user: Some(self.user(context)), + ..Default::default() + }); let stage_name = context.stage_name.clone(); // From @@ -409,7 +492,7 @@ impl DockerfileGenerator for Stage { command: "FROM".into(), content: format!( "{image_name} AS {stage_name}", - image_name = self.from(&context).to_string() + image_name = self.from(context).to_string() ), options: vec![], }), @@ -458,7 +541,7 @@ impl DockerfileGenerator for Stage { // Copy resources for copy in self.copy.iter() { - lines.append(&mut copy.generate_dockerfile_lines(&context)?); + lines.append(&mut copy.generate_dockerfile_lines(context)?); } // Root @@ -472,17 +555,18 @@ impl DockerfileGenerator for Stage { options: vec![], })); - let root_context = GenerationContext { - user: Some(root_user), - ..context.clone() - }; + context.push_state(GenerationContextState { + user: Some(Some(root_user)), + ..Default::default() + }); // Run - lines.append(&mut root.generate_dockerfile_lines(&root_context)?); + lines.append(&mut root.generate_dockerfile_lines(context)?); + context.pop_state(); } } // User - if let Some(user) = self.user(&context) { + if let Some(user) = self.user(context) { lines.push(DockerfileLine::Instruction(DockerfileInsctruction { command: "USER".into(), content: user.to_string(), @@ -491,7 +575,9 @@ impl DockerfileGenerator for Stage { } // Run - lines.append(&mut self.run.generate_dockerfile_lines(&context)?); + lines.append(&mut self.run.generate_dockerfile_lines(context)?); + + context.pop_state(); Ok(lines) } @@ -500,7 +586,7 @@ impl DockerfileGenerator for Stage { impl DockerfileGenerator for Run { fn generate_dockerfile_lines( &self, - context: &GenerationContext, + context: &mut GenerationContext, ) -> Result> { let script = &self.run; if script.is_empty() { @@ -544,19 +630,7 @@ impl DockerfileGenerator for Run { // Mount caches for cache in self.cache.iter() { - let mut target = cache.target.clone(); - - // Manage relative paths - if !target.starts_with("/") { - target = format!( - "{}/{}", - context.wokdir.clone().ok_or(Error::Custom( - "The cache target must be absolute or a workdir must be defined" - .to_string() - ))?, - target - ); - } + let target = cache.target.clone(); let mut cache_options = vec![ InstructionOptionOption::new("type", "cache".into()), @@ -630,45 +704,80 @@ fn string_vec_into(string_vec: Vec) -> String { ) } -impl Stage { - pub(crate) fn get_dependencies(&self) -> Vec { +#[derive(Debug, Clone, PartialEq)] +struct StageDependency { + stage: String, + path: String, + origin: Vec, +} + +trait StageDependencyGetter { + fn get_dependencies(&self, origin: &Vec) -> Vec; +} + +impl StageDependencyGetter for Stage { + fn get_dependencies(&self, origin: &Vec) -> Vec { let mut dependencies = vec![]; if let FromContext::FromBuilder(builder) = &self.from { - dependencies.push(builder.clone()); + dependencies.push(StageDependency { + stage: builder.clone(), + path: "/".into(), + origin: [origin.clone(), vec!["from".into()]].concat(), + }); } - for copy in self.copy.iter() { - dependencies.append(&mut copy.get_dependencies()); + for (position, copy) in self.copy.iter().enumerate() { + dependencies.append(&mut copy.get_dependencies( + &[origin.clone(), vec!["copy".into(), position.to_string()]].concat(), + )); } - dependencies.append(&mut self.run.get_dependencies()); + dependencies.append(&mut self.run.get_dependencies(origin)); if let Some(root) = &self.root { - dependencies.append(&mut root.get_dependencies()); + dependencies.append( + &mut root.get_dependencies(&[origin.clone(), vec!["root".into()]].concat()), + ); } dependencies } } -impl Run { - pub(crate) fn get_dependencies(&self) -> Vec { +impl StageDependencyGetter for Run { + fn get_dependencies(&self, origin: &Vec) -> Vec { let mut dependencies = vec![]; - for cache in self.cache.iter() { + for (position, cache) in self.cache.iter().enumerate() { if let FromContext::FromBuilder(builder) = &cache.from { - dependencies.push(builder.clone()); + dependencies.push(StageDependency { + stage: builder.clone(), + path: cache.source.clone().unwrap_or("/".into()), + origin: [origin.clone(), vec!["cache".into(), position.to_string()]].concat(), + }); } } - for bind in self.bind.iter() { + for (position, bind) in self.bind.iter().enumerate() { if let FromContext::FromBuilder(builder) = &bind.from { - dependencies.push(builder.clone()); + dependencies.push(StageDependency { + stage: builder.clone(), + path: bind.source.clone().unwrap_or("/".into()), + origin: [origin.clone(), vec!["bind".into(), position.to_string()]].concat(), + }); } } dependencies } } -impl CopyResource { - pub(crate) fn get_dependencies(&self) -> Vec { +impl StageDependencyGetter for CopyResource { + fn get_dependencies(&self, origin: &Vec) -> Vec { match self { CopyResource::Copy(copy) => match ©.from { - FromContext::FromBuilder(builder) => vec![builder.clone()], + FromContext::FromBuilder(builder) => copy + .paths + .iter() + .map(|path| StageDependency { + stage: builder.clone(), + path: path.clone(), + origin: origin.clone(), + }) + .collect(), _ => vec![], }, _ => vec![], @@ -676,21 +785,26 @@ impl CopyResource { } } -struct StagesDependencyResolver { - dependencies: HashMap>, - recursive_dependencies: HashMap>, +#[derive(Debug, Clone, PartialEq, Default)] +pub struct LintSession { + messages: Vec, + stage_infos: HashMap, + recursive_stage_dependencies: HashMap>, } -impl StagesDependencyResolver { - pub fn get_sorted_builders(&mut self) -> Result> { +impl LintSession { + pub fn get_sorted_builders(&mut self) -> Vec { let mut stages: Vec<(String, Vec)> = self - .dependencies + .stage_infos .clone() .keys() - .into_iter() - .filter(|stage| **stage != "runtime") - .map(|stage| Ok((stage.clone(), self.resolve_dependencies(stage.clone())?))) - .collect::>()?; + .map(|name| { + ( + name.clone(), + self.get_stage_recursive_dependencies(name.clone()), + ) + }) + .collect(); stages.sort_by(|(a_stage, a_deps), (b_stage, b_deps)| { if a_deps.contains(b_stage) { @@ -702,78 +816,219 @@ impl StagesDependencyResolver { a_stage.cmp(b_stage) }); - Ok(stages.into_iter().map(|(stage, _)| stage).collect()) + stages + .into_iter() + .map(|(stage, _)| stage) + .filter(|name| *name != "runtime") + .collect() } - pub fn resolve_dependencies(&mut self, stage: String) -> Result> { - self.resolve_recursive_dependencies(&mut vec![stage]) + pub fn get_stage_recursive_dependencies(&mut self, stage: String) -> Vec { + self.resolve_stage_recursive_dependencies(&mut vec![stage]) } - fn resolve_recursive_dependencies(&mut self, path: &mut Vec) -> Result> { - let stage = path - .last() - .ok_or(Error::Custom("The path is empty".to_string()))? - .clone(); - if let Some(dependencies) = self.recursive_dependencies.get(&stage) { - return Ok(dependencies.clone()); + fn resolve_stage_recursive_dependencies(&mut self, path: &mut Vec) -> Vec { + let stage = &path.last().expect("The path is empty").clone(); + if let Some(dependencies) = self.recursive_stage_dependencies.get(stage) { + return dependencies.clone(); } let mut deps = HashSet::new(); let dependencies = self + .stage_infos + .get(stage) + .expect(format!("The stage info not found for stage '{}'", stage).as_str()) .dependencies - .get(&stage) - .ok_or(Error::Custom(format!( - "The stage dependencies {} not found", - stage - )))? .clone(); for dependency in dependencies { - if path.contains(&dependency) { - return Err(Error::Custom(format!( - "Circular dependency detected: {} -> {}", - path.join(" -> "), - dependency - ))); + let dep_stage = &dependency.stage; + if path.contains(dep_stage) { + self.messages.push(LintMessage { + level: MessageLevel::Error, + message: format!( + "Circular dependency detected: {} -> {}", + path.join(" -> "), + dependency.stage + ), + path: dependency.origin.clone(), + }); + continue; } - deps.insert(dependency.clone()); - path.push(dependency.clone()); - deps.extend(self.resolve_recursive_dependencies(path)?); - path.pop(); + deps.insert(dep_stage.clone()); + if self.stage_infos.contains_key(dep_stage) { + path.push(dep_stage.clone()); + deps.extend(self.resolve_stage_recursive_dependencies(path)); + path.pop(); + } // the else is already managed in check_dependencies } let deps: Vec = deps.into_iter().collect(); - self.recursive_dependencies + self.recursive_stage_dependencies .insert(stage.clone(), deps.clone()); - Ok(deps) + deps } - pub fn new(dofigen: &Dofigen) -> Self { - let mut dependencies: HashMap> = dofigen - .builders + /// Checks if dependencies are using path that are in cache + fn check_dependencies(&mut self) { + let dependencies = self + .stage_infos + .iter() + .flat_map(|(_name, info)| info.dependencies.clone()) + .collect::>(); + + let caches = self + .stage_infos .iter() - .map(|(name, builder)| { - if name == "runtime" { - panic!("The builder name 'runtime' is reserved"); + .map(|(name, info)| (name, info.cache_paths.clone())) + .collect::>(); + + for dependency in dependencies { + if let Some(paths) = caches.get(&dependency.stage) { + paths + .iter() + .filter(|path| dependency.path.starts_with(*path)) + .for_each(|path| { + self.messages.push(LintMessage { + level: MessageLevel::Error, + message: format!( + "Use of the '{}' builder cache path '{}'", + dependency.stage, path + ), + path: dependency.origin.clone(), + }); + }); + } else { + self.messages.push(LintMessage { + level: MessageLevel::Error, + message: format!("The builder '{}' not found", dependency.stage), + path: dependency.origin.clone(), + }); + } + } + } + + fn analyze_stage(&mut self, path: &Vec, name: &String, stage: &Stage) { + let dependencies = stage.get_dependencies(path); + self.messages.append( + &mut dependencies + .iter() + .filter(|dep| dep.stage == "runtime") + .map(|dep| LintMessage { + level: MessageLevel::Error, + message: format!("The builder '{}' can't depend on the 'runtime'", name,), + path: dep.origin.clone(), + }) + .collect(), + ); + let cache_paths = self.get_stage_cache_paths(stage, path); + self.stage_infos.insert( + name.clone(), + StageLintInfo { + dependencies, + cache_paths, + }, + ); + } + + fn get_stage_cache_paths(&mut self, stage: &Stage, path: &Vec) -> Vec { + let mut paths = vec![]; + paths.append(&mut self.get_run_cache_paths(&stage.run, path, &stage.workdir)); + if let Some(root) = &stage.root { + paths.append(&mut self.get_run_cache_paths( + root, + &[path.clone(), vec!["root".into()]].concat(), + &stage.workdir, + )); + } + paths + } + + fn get_run_cache_paths( + &mut self, + run: &Run, + path: &Vec, + workdir: &Option, + ) -> Vec { + let mut cache_paths = vec![]; + for (position, cache) in run.cache.iter().enumerate() { + let target = cache.target.clone(); + cache_paths.push(if target.starts_with("/") { + target.clone() + } else { + if let Some(workdir) = workdir { + format!("{}/{}", workdir, target) } - let deps = builder.get_dependencies(); - if deps.contains(&"runtime".to_string()) { - panic!("The builder '{}' can't depend on the 'runtime'", name); + else { + self.messages.push(LintMessage { + level: MessageLevel::Warn, + message: "The cache target should be absolute or a workdir should be defined in the stage".to_string(), + path: [path.clone(), vec!["cache".into(), position.to_string()]].concat(), + }); + target.clone() } - (name.clone(), deps) - }) - .collect(); + }); + } + cache_paths + } - dependencies.insert("runtime".into(), dofigen.stage.get_dependencies()); - Self { - dependencies, - recursive_dependencies: HashMap::new(), + ////////// Statics ////////// + + /// Analyze the given Dofigen configuration and return a lint session + pub fn analyze(dofigen: &Dofigen) -> Self { + let mut session = Self::default(); + for (name, builder) in dofigen.builders.iter() { + let base_origin = vec!["builders".into(), name.clone()]; + if name == "runtime" { + session.messages.push(LintMessage { + level: MessageLevel::Error, + message: "The builder name 'runtime' is reserved".into(), + path: base_origin.clone(), + }); + } + session.analyze_stage(&base_origin, name, builder); } + + session.analyze_stage(&vec![], &"runtime".into(), &dofigen.stage); + session.check_dependencies(); + + session } } +#[derive(Debug, Clone, PartialEq)] +pub struct StageLintInfo { + dependencies: Vec, + cache_paths: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LintMessage { + pub level: MessageLevel, + pub path: Vec, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MessageLevel { + Warn, + Error, +} + #[cfg(test)] mod test { use super::*; use pretty_assertions_sorted::assert_eq_sorted; + impl Default for GenerationContext { + fn default() -> Self { + Self { + user: None, + stage_name: String::default(), + default_from: FromContext::default(), + lint_session: LintSession::default(), + state_stack: vec![], + } + } + } + mod stage { use super::*; @@ -807,7 +1062,7 @@ mod test { ..Default::default() }; - let lines = stage.generate_dockerfile_lines(&GenerationContext { + let lines = stage.generate_dockerfile_lines(&mut GenerationContext { stage_name: "test".into(), ..Default::default() }); @@ -852,7 +1107,7 @@ mod test { }; let lines = copy - .generate_dockerfile_lines(&GenerationContext::default()) + .generate_dockerfile_lines(&mut GenerationContext::default()) .unwrap(); assert_eq_sorted!( @@ -935,7 +1190,7 @@ mod test { }; assert_eq_sorted!( builder - .generate_dockerfile_lines(&GenerationContext::default()) + .generate_dockerfile_lines(&mut GenerationContext::default()) .unwrap(), vec![DockerfileLine::Instruction(DockerfileInsctruction { command: "RUN".into(), @@ -952,7 +1207,7 @@ mod test { }; assert_eq_sorted!( builder - .generate_dockerfile_lines(&GenerationContext::default()) + .generate_dockerfile_lines(&mut GenerationContext::default()) .unwrap(), vec![] ); @@ -966,7 +1221,7 @@ mod test { }; assert_eq_sorted!( builder - .generate_dockerfile_lines(&GenerationContext::default()) + .generate_dockerfile_lines(&mut GenerationContext::default()) .unwrap(), vec![] ); @@ -983,12 +1238,12 @@ mod test { .into(), ..Default::default() }; - let context = GenerationContext { + let mut context = GenerationContext { user: Some(User::new("test")), ..Default::default() }; assert_eq_sorted!( - builder.generate_dockerfile_lines(&context).unwrap(), + builder.generate_dockerfile_lines(&mut context).unwrap(), vec![DockerfileLine::Instruction(DockerfileInsctruction { command: "RUN".into(), content: "echo Hello".into(), @@ -1014,12 +1269,12 @@ mod test { }], ..Default::default() }; - let context = GenerationContext { + let mut context = GenerationContext { user: Some(User::new("1000")), ..Default::default() }; assert_eq_sorted!( - builder.generate_dockerfile_lines(&context).unwrap(), + builder.generate_dockerfile_lines(&mut context).unwrap(), vec![DockerfileLine::Instruction(DockerfileInsctruction { command: "RUN".into(), content: "echo Hello".into(), @@ -1047,12 +1302,12 @@ mod test { }], ..Default::default() }; - let context = GenerationContext { + let mut context = GenerationContext { user: Some(User::new_without_group("1000")), ..Default::default() }; assert_eq_sorted!( - builder.generate_dockerfile_lines(&context).unwrap(), + builder.generate_dockerfile_lines(&mut context).unwrap(), vec![DockerfileLine::Instruction(DockerfileInsctruction { command: "RUN".into(), content: "echo Hello".into(), @@ -1070,11 +1325,11 @@ mod test { } } - mod stages_dependency_resolver { + mod lint_session { use super::*; #[test] - fn resolve_builders_dependencies() { + fn builders_dependencies() { let dofigen = Dofigen { builders: HashMap::from([ ( @@ -1115,30 +1370,275 @@ mod test { ..Default::default() }; - let mut resolver = StagesDependencyResolver::new(&dofigen); + let mut lint_session = LintSession::analyze(&dofigen); - let mut dependencies = resolver.resolve_dependencies("runtime".into()).unwrap(); + let mut dependencies = lint_session.get_stage_recursive_dependencies("runtime".into()); dependencies.sort(); assert_eq_sorted!(dependencies, Vec::::new()); - dependencies = resolver.resolve_dependencies("builder1".into()).unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("builder1".into()); dependencies.sort(); assert_eq_sorted!(dependencies, vec!["builder2", "builder3"]); - dependencies = resolver.resolve_dependencies("builder2".into()).unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("builder2".into()); assert_eq_sorted!(dependencies, vec!["builder3"]); - dependencies = resolver.resolve_dependencies("builder3".into()).unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("builder3".into()); assert_eq_sorted!(dependencies, Vec::::new()); - let mut builders = resolver.get_sorted_builders().unwrap(); + let mut builders = lint_session.get_sorted_builders(); builders.sort(); assert_eq_sorted!(builders, vec!["builder1", "builder2", "builder3"]); + + assert_eq_sorted!(lint_session.messages, vec![]); } #[test] - fn resolve_runtime_dependencies() { + fn builders_circular_dependencies() { + let dofigen = Dofigen { + builders: HashMap::from([ + ( + "builder1".into(), + Stage { + copy: vec![CopyResource::Copy(Copy { + from: FromContext::FromBuilder("builder2".into()), + paths: vec!["/path/to/copy".into()], + options: Default::default(), + ..Default::default() + })], + ..Default::default() + }, + ), + ( + "builder2".into(), + Stage { + copy: vec![CopyResource::Copy(Copy { + from: FromContext::FromBuilder("builder3".into()), + paths: vec!["/path/to/copy".into()], + options: Default::default(), + ..Default::default() + })], + ..Default::default() + }, + ), + ( + "builder3".into(), + Stage { + copy: vec![CopyResource::Copy(Copy { + from: FromContext::FromBuilder("builder1".into()), + paths: vec!["/path/to/copy".into()], + options: Default::default(), + ..Default::default() + })], + ..Default::default() + }, + ), + ]), + ..Default::default() + }; + + let mut lint_session = LintSession::analyze(&dofigen); + + let mut dependencies = lint_session.get_stage_recursive_dependencies("runtime".into()); + dependencies.sort(); + assert_eq_sorted!(dependencies, Vec::::new()); + + dependencies = lint_session.get_stage_recursive_dependencies("builder1".into()); + dependencies.sort(); + assert_eq_sorted!(dependencies, vec!["builder2", "builder3"]); + + dependencies = lint_session.get_stage_recursive_dependencies("builder2".into()); + assert_eq_sorted!(dependencies, vec!["builder3"]); + + dependencies = lint_session.get_stage_recursive_dependencies("builder3".into()); + assert_eq_sorted!(dependencies, Vec::::new()); + + let mut builders = lint_session.get_sorted_builders(); + builders.sort(); + + assert_eq_sorted!(builders, vec!["builder1", "builder2", "builder3"]); + + assert_eq_sorted!( + lint_session.messages, + vec![LintMessage { + level: MessageLevel::Error, + path: vec![ + "builders".into(), + "builder3".into(), + "copy".into(), + "0".into(), + ], + message: + "Circular dependency detected: builder1 -> builder2 -> builder3 -> builder1" + .into(), + },] + ); + } + + #[test] + fn builder_named_runtime() { + let dofigen = Dofigen { + builders: HashMap::from([( + "runtime".into(), + Stage { + run: Run { + run: vec!["echo Hello".into()].into(), + ..Default::default() + }, + ..Default::default() + }, + )]), + ..Default::default() + }; + + let mut lint_session = LintSession::analyze(&dofigen); + + let mut builders = lint_session.get_sorted_builders(); + builders.sort(); + + assert_eq_sorted!(builders, Vec::::new()); + + assert_eq_sorted!( + lint_session.messages, + vec![LintMessage { + level: MessageLevel::Error, + path: vec!["builders".into(), "runtime".into(),], + message: "The builder name 'runtime' is reserved".into(), + },] + ); + } + + #[test] + fn builder_not_found() { + let dofigen = Dofigen { + stage: Stage { + from: FromContext::FromBuilder("builder1".into()), + ..Default::default() + }, + ..Default::default() + }; + + let mut lint_session = LintSession::analyze(&dofigen); + + let mut builders = lint_session.get_sorted_builders(); + builders.sort(); + + assert_eq_sorted!(builders, Vec::::new()); + + assert_eq_sorted!( + lint_session.messages, + vec![LintMessage { + level: MessageLevel::Error, + path: vec!["from".into(),], + message: "The builder 'builder1' not found".into(), + },] + ); + } + + #[test] + fn dependency_to_runtime() { + let dofigen = Dofigen { + builders: HashMap::from([( + "builder".into(), + Stage { + copy: vec![CopyResource::Copy(Copy { + from: FromContext::FromBuilder("runtime".into()), + paths: vec!["/path/to/copy".into()], + ..Default::default() + })], + ..Default::default() + }, + )]), + stage: Stage { + run: Run { + run: vec!["echo Hello".into()].into(), + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + let mut lint_session = LintSession::analyze(&dofigen); + + let mut builders = lint_session.get_sorted_builders(); + builders.sort(); + + assert_eq_sorted!(builders, vec!["builder"]); + + assert_eq_sorted!( + lint_session.messages, + vec![LintMessage { + level: MessageLevel::Error, + path: vec![ + "builders".into(), + "builder".into(), + "copy".into(), + "0".into() + ], + message: "The builder 'builder' can't depend on the 'runtime'".into(), + },] + ); + } + + #[test] + fn dependency_to_cache_path() { + let dofigen = Dofigen { + builders: HashMap::from([ + ( + "builder1".into(), + Stage { + run: Run { + run: vec!["echo Hello".into()].into(), + cache: vec![Cache { + target: "/path/to/cache".into(), + ..Default::default() + }], + ..Default::default() + }, + ..Default::default() + }, + ), + ( + "builder2".into(), + Stage { + copy: vec![CopyResource::Copy(Copy { + from: FromContext::FromBuilder("builder1".into()), + paths: vec!["/path/to/cache/test".into()], + ..Default::default() + })], + ..Default::default() + }, + ), + ]), + ..Default::default() + }; + + let mut lint_session = LintSession::analyze(&dofigen); + + let mut builders = lint_session.get_sorted_builders(); + builders.sort(); + + assert_eq_sorted!(builders, vec!["builder1", "builder2"]); + + assert_eq_sorted!( + lint_session.messages, + vec![LintMessage { + level: MessageLevel::Error, + path: vec![ + "builders".into(), + "builder2".into(), + "copy".into(), + "0".into() + ], + message: "Use of the 'builder1' builder cache path '/path/to/cache'".into(), + },] + ); + } + + #[test] + fn runtime_dependencies() { let dofigen = Dofigen { builders: HashMap::from([ ( @@ -1187,38 +1687,35 @@ mod test { ..Default::default() }; - let mut resolver = StagesDependencyResolver::new(&dofigen); + let mut lint_session = LintSession::analyze(&dofigen); - let mut dependencies = resolver - .resolve_dependencies("install-deps".into()) - .unwrap(); + let mut dependencies = + lint_session.get_stage_recursive_dependencies("install-deps".into()); dependencies.sort(); assert_eq_sorted!(dependencies, Vec::::new()); - dependencies = resolver - .resolve_dependencies("install-php-ext".into()) - .unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("install-php-ext".into()); assert_eq_sorted!(dependencies, vec!["install-deps"]); - dependencies = resolver - .resolve_dependencies("get-composer".into()) - .unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("get-composer".into()); assert_eq_sorted!(dependencies, Vec::::new()); - dependencies = resolver.resolve_dependencies("runtime".into()).unwrap(); + dependencies = lint_session.get_stage_recursive_dependencies("runtime".into()); dependencies.sort(); assert_eq_sorted!( dependencies, vec!["get-composer", "install-deps", "install-php-ext"] ); - let mut builders = resolver.get_sorted_builders().unwrap(); + let mut builders = lint_session.get_sorted_builders(); builders.sort(); assert_eq_sorted!( builders, vec!["get-composer", "install-deps", "install-php-ext"] ); + + assert_eq_sorted!(lint_session.messages, vec![]); } } } diff --git a/src/lib.rs b/src/lib.rs index 0e5bb8c..40cfea1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,16 @@ mod generator; #[cfg(feature = "json_schema")] mod json_schema; pub mod lock; -use dockerfile_struct::{DockerfileContent, DockerfileLine}; -use generator::{DockerfileGenerator, GenerationContext}; #[cfg(feature = "json_schema")] use schemars::gen::*; -pub use {context::*, deserialize::*, dofigen_struct::*, errors::*, extend::*}; +pub use { + context::*, + deserialize::*, + dofigen_struct::*, + errors::*, + extend::*, + generator::{GenerationContext, MessageLevel}, +}; #[cfg(all(feature = "strict", feature = "permissive"))] compile_error!("You can't enable both 'strict' and 'permissive' features at the same time."); @@ -73,22 +78,7 @@ const FILE_HEADER_COMMENTS: [&str; 2] = [ /// ); /// ``` pub fn generate_dockerfile(dofigen: &Dofigen) -> Result { - let mut lines = dofigen.generate_dockerfile_lines(&GenerationContext::default())?; - let mut line_number = 1; - - for line in FILE_HEADER_COMMENTS { - lines.insert(line_number, DockerfileLine::Comment(line.to_string())); - line_number += 1; - } - - Ok(format!( - "{}\n", - lines - .iter() - .map(DockerfileLine::generate_content) - .collect::>() - .join("\n") - )) + GenerationContext::from(dofigen).generate_dockerfile(dofigen) } /// Generates the .dockerignore file content from an Dofigen struct. diff --git a/tests/cli_test.rs b/tests/cli_test.rs index ebc0958..4515d4b 100644 --- a/tests/cli_test.rs +++ b/tests/cli_test.rs @@ -168,7 +168,7 @@ Usage: dofigen "#, assert!(output.stdout.is_empty()); - output_starts_with(&output.stderr, "No Dofigen file found"); + output_starts_with(&output.stderr, "error: No Dofigen file found"); temp.close().unwrap(); } diff --git a/tests/lib_test.rs b/tests/lib_test.rs index 28d1cb7..afdf782 100644 --- a/tests/lib_test.rs +++ b/tests/lib_test.rs @@ -537,7 +537,7 @@ RUN \ --mount=type=bind,target=Cargo.lock,source=Cargo.lock \ --mount=type=bind,target=src/,source=src/ \ --mount=type=cache,target=/home/rust/.cargo,sharing=locked \ - --mount=type=cache,target=/app/target,sharing=locked \ + --mount=type=cache,target=target,sharing=locked \ <