From fc346d45f12e1e8c48fbabffbd73b3651fa5714f Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Sun, 15 Jan 2023 20:39:00 +0100 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 696 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 + README.md | 2 + examples/helmfile.yaml | 14 + src/connectors/argo.rs | 115 ++++++ src/connectors/helm.rs | 23 ++ src/connectors/helmfile.rs | 53 +++ src/connectors/mod.rs | 16 + src/main.rs | 381 ++++++++++++++++++++ src/types/mod.rs | 33 ++ 11 files changed, 1352 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 examples/helmfile.yaml create mode 100644 src/connectors/argo.rs create mode 100644 src/connectors/helm.rs create mode 100644 src/connectors/helmfile.rs create mode 100644 src/connectors/mod.rs create mode 100644 src/main.rs create mode 100644 src/types/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..011ec71 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,696 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build_html" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8ffb62af7b0911893e2d6126891b2a018e387078fa5d855cb42d0d90d88075" + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cdh" +version = "0.1.0" +dependencies = [ + "build_html", + "clap", + "env_logger", + "handlebars", + "log", + "serde", + "serde_json", + "serde_yaml", + "tabled", + "version-compare", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[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 = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "handlebars" +version = "4.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e4ab33f1213cdc25b5fa45c76881240cfe79284cf2b395e8b9e312a30a2fd" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394faa0efb47f9f227f1cd89978f854542b318a6f64fa695489c9c993056656" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "os_str_bytes" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + +[[package]] +name = "papergrid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1526bb6aa9f10ec339fb10360f22c57edf81d5678d0278e93bc12a47ffbe4b01" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] +name = "pest" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" +dependencies = [ + "once_cell", + "pest", + "sha1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabled" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c3ee73732ffceaea7b8f6b719ce3bb17f253fa27461ffeaf568ebd0cdb4b85" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca1b4eaceb4f2755df858b88d9b9315b7ccfd1ffd0d7a48a52602301f01a57" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" + +[[package]] +name = "version-compare" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..db36db3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cdh" +authors = ["allanger "] +version = "0.1.0" +description = "Your helm releases are outdated, aren't they? Now you can check" +edition = "2021" + +[dependencies] +serde = { version = "1.0.126", features = ["derive"] } +serde_json = "1.0.64" +log = "0.4.17" +env_logger = "0.10.0" +version-compare = "0.1.0" +clap = { version = "4.1.1", features = ["derive", "env"] } +serde_yaml = "0.9.16" +tabled = "0.10.0" +build_html = "2.1.0" +handlebars = "4.3.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e4d1c1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Check Da Helm +> Your helm releases are outdated, aren't they? Now you can check! diff --git a/examples/helmfile.yaml b/examples/helmfile.yaml new file mode 100644 index 0000000..be825ca --- /dev/null +++ b/examples/helmfile.yaml @@ -0,0 +1,14 @@ +repositories: + - name: keel + url: https://charts.keel.sh + +releases: + # ----------------------------- + # -- Keel + # ----------------------------- + - name: keel + installed: true + namespace: keel-system + createNamespace: true + chart: keel/keel + version: 0.9.11 diff --git a/src/connectors/argo.rs b/src/connectors/argo.rs new file mode 100644 index 0000000..d1a302e --- /dev/null +++ b/src/connectors/argo.rs @@ -0,0 +1,115 @@ +use clap::Arg; +use log::{debug, info, error}; +use serde_json::from_str; +use crate::types::{self, HelmRepo}; + +use super::Connector; +use std::{borrow::Borrow, io::{Result, Error, ErrorKind}, process::Command}; + +pub(crate) struct Argo; + +impl Connector for Argo { + type ConnectorType = Argo; + + fn get_app(&self) -> Result> { + let cmd: String = "argocd app list -o json | jq '[.[] | {chart: .spec.source.chart, version: .spec.source.targetRevision}]'".to_string(); + + debug!("executing '${}'", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("argo is failed"); + let helm_stdout = String::from_utf8_lossy(&output.stdout); + + match from_str::>(Borrow::borrow(&helm_stdout)) { + Ok(mut charts) => { + charts.dedup(); + Ok(charts) + } + Err(err) => Err(err.into()), + } + } + fn sync_repos(&self) -> Result<()> { + info!("syncing helm repos"); + let cmd: String = "argocd app list -o json | jq '[ .[] | {name: .spec.source.chart, url: .spec.source.repoURL} ]'".to_string(); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile is failed"); + info!("{:?}", output.clone()); + if output.status.success() { + let repos: Vec = serde_json::from_slice(&output.stdout).unwrap(); + info!("adding repositories"); + for repo in repos.iter() { + let name = repo.name.clone(); + if name.is_some() { + info!( + "syncing {} with the origin {}", + name.clone().unwrap(), + repo.url + ); + let cmd = format!( + "helm repo add {} {}", + name.clone().unwrap(), + repo.url.clone() + ); + debug!("running {}", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helm repo sync is failed"); + match output.status.success() { + true => { + info!( + "{} with the origin {} is synced successfully", + name.unwrap(), + repo.url + ); + } + false => { + error!( + "{} with the origin {} can't be synced", + name.unwrap(), + repo.url + ) + } + } + } + } + let cmd = "helm repo update"; + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helm repo sync is failed"); + match output.status.success() { + true => { + info!("repositories are updated successfully"); + } + false => { + error!( + "repositories can't be updated, {}", + String::from_utf8_lossy(&output.stderr) + ); + } + } + + Ok(()) + } else { + Err(Error::new( + ErrorKind::Other, + String::from_utf8_lossy(&output.stderr), + )) + } + + } + +} +impl Argo{ +pub(crate) fn init() -> Argo { + Argo +} +} \ No newline at end of file diff --git a/src/connectors/helm.rs b/src/connectors/helm.rs new file mode 100644 index 0000000..8145e05 --- /dev/null +++ b/src/connectors/helm.rs @@ -0,0 +1,23 @@ +use crate::types; + +use super::Connector; +use std::io::Result; + +pub(crate) struct Helm; + +impl Connector for Helm { + fn get_app(&self) -> Result> { + todo!() + } + fn sync_repos(&self) -> Result<()> { + todo!() + } + + type ConnectorType = Helm; +} + +impl Helm { + pub(crate) fn init() -> Helm { + Helm + } +} diff --git a/src/connectors/helmfile.rs b/src/connectors/helmfile.rs new file mode 100644 index 0000000..fb8ff6e --- /dev/null +++ b/src/connectors/helmfile.rs @@ -0,0 +1,53 @@ +use log::debug; +use serde_json::from_str; + +use crate::types; + +use super::Connector; +use std::{borrow::Borrow, fmt::format, io::Result, process::Command}; + +pub(crate) struct Helmfile { + path: String, +} + +impl Connector for Helmfile { + fn get_app(&self) -> Result> { + let cmd: String = format!( + "helmfile -f {} list --output json | jq '[.[] | {{chart: .name, version: .version}}]'", + self.path + ) + .to_string(); + + debug!("executing '${}'", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile list is failed"); + let helm_stdout = String::from_utf8_lossy(&output.stdout); + + match from_str::>(Borrow::borrow(&helm_stdout)) { + Ok(mut charts) => { + charts.dedup(); + Ok(charts) + } + Err(err) => Err(err.into()), + } + } + fn sync_repos(&self) -> Result<()> { + let cmd: String = format!("helmfile -f {} sync", self.path); + Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile sync is failed"); + Ok(()) + } + + type ConnectorType = Helmfile; +} +impl Helmfile { + pub(crate) fn init(path: String) -> Self { + Self { path: path } + } +} diff --git a/src/connectors/mod.rs b/src/connectors/mod.rs new file mode 100644 index 0000000..cba7625 --- /dev/null +++ b/src/connectors/mod.rs @@ -0,0 +1,16 @@ +mod argo; +mod helm; +mod helmfile; + +use std::io::Result; +use crate::types; + +pub (crate) use self::argo::Argo; +pub (crate) use self::helm::Helm; +pub (crate) use self::helmfile::Helmfile; + +pub(crate) trait Connector { + type ConnectorType; + fn get_app(&self) -> Result>; + fn sync_repos(&self) -> Result<()>; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..49ee268 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,381 @@ +mod connectors; +mod types; + +use clap::{Parser, ValueEnum}; +use connectors::{Argo, Connector, Helm, Helmfile}; +use handlebars::Handlebars; +use log::{debug, error, info, warn}; +use serde::{Deserialize, Serialize}; +use serde_json::from_str; +use std::{ + borrow::Borrow, + fmt::{self, format}, + io::{Error, ErrorKind, Result}, + process::{exit, Command}, +}; +use tabled::Tabled; +use version_compare::{Cmp, Version}; + +use crate::types::HelmChart; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum Kinds { + Argo, + Helm, + Helmfile, +} + +/// Check you helm releaseas managed by Argo +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Type of the + #[clap(long, value_enum)] + kind: Kinds, + /// Path to the helmfile + #[clap(short, long, value_parser, default_value = "./")] + path: String, + /// Should execution be failed if you have outdated charts + #[clap(short, long, action, default_value_t = false, env = "OUTDATED_FAIL")] + outdated_fail: bool, + /// Set to true if you don't want to sync repositories + #[clap(short, long, action, default_value_t = false)] + no_sync: bool, +} + +/// A struct to write helm repo description to +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +struct Repo { + name: Option, + url: String, +} + +/// Struct for parsing charts info from helmfile +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +struct LocalCharts { + #[serde(alias = "name", alias = "chart")] + chart: Option, + version: Option, +} + +/// Three possible statuses of versions comparison +#[derive(Clone, Serialize)] +enum Status { + Uptodate, + Outdated, + Missing, +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Status::Uptodate => write!(f, "Up-to-date"), + Status::Outdated => write!(f, "Outdated"), + Status::Missing => write!(f, "Missing"), + } + } +} +#[derive(Clone, Tabled, Serialize)] +struct ExecResult { + name: String, + latest_version: String, + current_version: String, + status: Status, +} + +// Implementation for the ExecResult struct +impl ExecResult { + fn new(name: String, latest_version: String, current_version: String, status: Status) -> Self { + Self { + name, + latest_version, + current_version, + status, + } + } +} + +fn main() { + // Preparations step + env_logger::init(); + let args = Args::parse(); + let mut result: Vec = Vec::new(); + + let charts = match args.kind { + Kinds::Argo => Argo::init().get_app(), + Kinds::Helm => Helm::init().get_app(), + Kinds::Helmfile => Helmfile::init(args.path.clone()).get_app(), + } + .unwrap(); + + if !args.no_sync { + info!("syncing helm repositories"); + let res = match args.kind { + Kinds::Argo => Argo::init().sync_repos(), + Kinds::Helm => Helm::init().sync_repos(), + Kinds::Helmfile => Helmfile::init(args.path).sync_repos(), + }; + match res { + Ok(_) => info!("helm repos are synced"), + Err(err) => error!("couldn't sync repos', {}", err), + } + } + + charts.iter().for_each(|a| { + let err = check_chart(&mut result, a); + }); + + // Parse the helmfile + // Handling the result + match handle_result(&result, args.outdated_fail) { + Ok(result) => { + if result { + exit(1); + } + } + Err(err) => { + error!("{}", err); + exit(1); + } + }; +} + +fn check_chart(result: &mut Vec, local_chart: &types::HelmChart) -> Result<()> { + if local_chart.clone().name.is_some() { + let version = local_chart.version.clone().unwrap(); + let chart = local_chart.name.clone().unwrap(); + return match version.is_empty() { + true => { + warn!( + "version is not specified for the '{}' chart, skipping", + chart + ); + Ok(()) + } + false => { + info!("checking {} - {}", chart, version); + let cmd = format!( + "helm search repo {}/{} --versions --output json", + chart, chart + ); + debug!("executing '${}'", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile is failed"); + let helm_stdout = String::from_utf8_lossy(&output.stdout); + + // Remove "v" from version definitions + let mut versions: Vec = from_str(helm_stdout.borrow()).unwrap(); + versions.iter_mut().for_each(|f| { + if f.version.is_some() { + f.version = Some(f.version.as_ref().unwrap().replace('v', "")); + } + }); + // Create a Version from the chart version string + let local = Version::from(&version).unwrap(); + let mut current_version: String = "0.0.0".to_string(); + + // Get the latest remote version + for v in versions.iter() { + current_version = get_newer_version( + current_version.clone(), + v.version.as_ref().unwrap().clone(), + ); + } + let remote = Version::from(current_version.as_str()).unwrap(); + let status: Status = if versions.contains(&HelmChart { + name: Some(format!("{}/{}", chart.clone(), chart.clone())), + version: Some(version.clone()), + }) { + match local.compare(remote.clone()) { + Cmp::Lt => Status::Outdated, + Cmp::Eq => Status::Uptodate, + Cmp::Gt => Status::Missing, + _ => unreachable!(), + } + } else { + Status::Missing + }; + + result.push(ExecResult::new( + chart.clone(), + current_version.clone(), + version.clone(), + status, + )); + + Ok(()) + } + }; + } else { + return Ok(()); + } +} + +/// Handle result +fn handle_result(result: &Vec, outdated_fail: bool) -> Result { + let mut failed = false; + for r in result.clone() { + match r.status { + Status::Uptodate => info!("{} is up-to-date", r.name), + Status::Outdated => { + if outdated_fail { + failed = true + } + warn!( + "{} is outdated. Current version is {}, but the latest is {}", + r.name, r.current_version, r.latest_version + ); + } + Status::Missing => { + failed = true; + error!( + "{} is broken. Current version is {}, but it can't be found in the repo", + r.name, r.current_version + ); + } + } + } + let template = r#" + + + + + + + + {{#each this as |tr|}} + + + + + + + {{/each}} +
Chart NameCurrent VersionLatest VersionStatus
{{tr.name}}{{tr.current_version}}{{tr.latest_version}}{{tr.status}}
+"#; + let mut reg = Handlebars::new(); + + // TODO: Handle this error + reg.register_template_string("html_table", template) + .unwrap(); + + match reg.render("html_table", &result) { + Ok(res) => println!("{}", res), + Err(err) => error!("{}", err), + }; + Ok(failed) +} + +/// Downloading repos from repositories +fn repo_sync() -> Result<()> { + info!("syncing helm repos"); + let cmd: String = "argocd app list -o json | jq '[ .[] | {name: .spec.source.chart, url: .spec.source.repoURL} ]'".to_string(); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile is failed"); + info!("{:?}", output.clone()); + if output.status.success() { + let repos: Vec = serde_json::from_slice(&output.stdout).unwrap(); + info!("adding repositories"); + for repo in repos.iter() { + let name = repo.name.clone(); + if name.is_some() { + info!( + "syncing {} with the origin {}", + name.clone().unwrap(), + repo.url + ); + let cmd = format!( + "helm repo add {} {}", + name.clone().unwrap(), + repo.url.clone() + ); + debug!("running {}", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helm repo sync is failed"); + match output.status.success() { + true => { + info!( + "{} with the origin {} is synced successfully", + name.unwrap(), + repo.url + ); + } + false => { + error!( + "{} with the origin {} can't be synced", + name.unwrap(), + repo.url + ) + } + } + } + } + let cmd = "helm repo update"; + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helm repo sync is failed"); + match output.status.success() { + true => { + info!("repositories are updated successfully"); + } + false => { + error!( + "repositories can't be updated, {}", + String::from_utf8_lossy(&output.stderr) + ); + } + } + + Ok(()) + } else { + Err(Error::new( + ErrorKind::Other, + String::from_utf8_lossy(&output.stderr), + )) + } +} + +/// Run helmfile list and write the result into struct +fn parse_argo_apps() -> Result> { + let cmd: String = "argocd app list -o json | jq '[.[] | {chart: .spec.source.chart, version: .spec.source.targetRevision}]'".to_string(); + + debug!("executing '${}'", cmd); + let output = Command::new("bash") + .arg("-c") + .arg(cmd) + .output() + .expect("helmfile is failed"); + let helm_stdout = String::from_utf8_lossy(&output.stdout); + + match from_str::>(Borrow::borrow(&helm_stdout)) { + Ok(mut charts) => { + charts.dedup(); + Ok(charts) + } + Err(err) => Err(err.into()), + } +} + +/// Takes two version and returns the newer one. +fn get_newer_version(v1: String, v2: String) -> String { + match Version::from(&v1.replace('v', "")) + .unwrap() + .compare(Version::from(&v2.replace('v', "")).unwrap().clone()) + { + Cmp::Eq => v1, + Cmp::Lt => v2, + Cmp::Gt => v1, + _ => unreachable!(), + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..a0be023 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Struct for parsing charts info from helmfile +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub(crate) struct HelmChart { + #[serde(alias = "name", alias = "chart")] + pub(crate) name: Option, + pub(crate) version: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub(crate) struct HelmRepo { + pub(crate) name: Option, + pub(crate) url: String, +} + +#[derive(Clone, Serialize)] +enum Status { + Uptodate, + Outdated, + Missing, +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Status::Uptodate => write!(f, "Up-to-date"), + Status::Outdated => write!(f, "Outdated"), + Status::Missing => write!(f, "Missing"), + } + } +}