From a29b5b5c07e8f64d883072a5987f8308965011e7 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 13 Dec 2024 23:26:11 -0800 Subject: [PATCH 01/14] Initial version of filesystem resource --- build.ps1 | 1 + dsc/examples/faultydriver_detect.dsc.yaml | 13 + dsc/examples/faultydriver_recovery.dsc.yaml | 9 + resources/fs/Cargo.lock | 573 ++++++++++++++++++++ resources/fs/Cargo.toml | 13 + resources/fs/fs.dsc.resource.json | 37 ++ resources/fs/src/args.rs | 35 ++ resources/fs/src/config.rs | 39 ++ resources/fs/src/file_helper.rs | 97 ++++ resources/fs/src/main.rs | 86 +++ 10 files changed, 903 insertions(+) create mode 100644 dsc/examples/faultydriver_detect.dsc.yaml create mode 100644 dsc/examples/faultydriver_recovery.dsc.yaml create mode 100644 resources/fs/Cargo.lock create mode 100644 resources/fs/Cargo.toml create mode 100644 resources/fs/fs.dsc.resource.json create mode 100644 resources/fs/src/args.rs create mode 100644 resources/fs/src/config.rs create mode 100644 resources/fs/src/file_helper.rs create mode 100644 resources/fs/src/main.rs diff --git a/build.ps1 b/build.ps1 index 8a81fbddd..63267c702 100644 --- a/build.ps1 +++ b/build.ps1 @@ -262,6 +262,7 @@ if (!$SkipBuild) { "osinfo", "powershell-adapter", "process", + "resources/fs", "runcommandonset", "tools/dsctest", "tools/test_group_resource", diff --git a/dsc/examples/faultydriver_detect.dsc.yaml b/dsc/examples/faultydriver_detect.dsc.yaml new file mode 100644 index 000000000..7d6715995 --- /dev/null +++ b/dsc/examples/faultydriver_detect.dsc.yaml @@ -0,0 +1,13 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: check for CrowdStrike driver + type: Microsoft.Windows/Driver + properties: + name: CSAgent + +- name: check if faulty driver of CrowdStrike exists under system32\drivers + type: Microsoft.DSC/FileSystem + properties: + path: "[path(systemRoot(), 'windows', 'system32', 'drivers', 'crowdstrike', 'C-00000291-00000000-00000026.sys')]" + hash: '6454cc4779c1c8387c547099181cfcbecc17401fb22750431c15a3b02f8243ea' diff --git a/dsc/examples/faultydriver_recovery.dsc.yaml b/dsc/examples/faultydriver_recovery.dsc.yaml new file mode 100644 index 000000000..ab9bba7ef --- /dev/null +++ b/dsc/examples/faultydriver_recovery.dsc.yaml @@ -0,0 +1,9 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: check if faulty driver of CrowdStrike exists under system32\drivers + type: Microsoft.DSC/FileSystem + properties: + path: "[path(systemRoot(), 'windows', 'system32', 'drivers', 'crowdstrike', 'C-00000291-00000000-00000026.sys')]" + _exist: False + hash: '6454cc4779c1c8387c547099181cfcbecc17401fb22750431c15a3b02f8243ea' diff --git a/resources/fs/Cargo.lock b/resources/fs/Cargo.lock new file mode 100644 index 000000000..a0d4e8910 --- /dev/null +++ b/resources/fs/Cargo.lock @@ -0,0 +1,573 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[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.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "fs" +version = "0.1.0" +dependencies = [ + "clap", + "fs_extra", + "schemars", + "serde", + "serde_json", + "sha256", + "tracing", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "pin-project-lite", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/resources/fs/Cargo.toml b/resources/fs/Cargo.toml new file mode 100644 index 000000000..72dc370c6 --- /dev/null +++ b/resources/fs/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fs" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version ="4.5", features = ["derive"] } +serde = "1.0" +serde_json = "1.0" +schemars = "0.8" +tracing = { version = "0.1" } +sha256 = { version = "1.5.0" } +fs_extra = { version = "1.2.0" } diff --git a/resources/fs/fs.dsc.resource.json b/resources/fs/fs.dsc.resource.json new file mode 100644 index 000000000..ad923cca3 --- /dev/null +++ b/resources/fs/fs.dsc.resource.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", + "type": "Microsoft.DSC/FileSystem", + "description": "Manage file system configuration settings.", + "version": "0.1.0", + "get": { + "executable": "fs", + "args": [ + "get", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin", + "implementsPretest": false + }, + "delete": { + "executable": "fs", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, + "schema": { + "command": { + "executable": "fs", + "args": [ + "schema" + ] + } + } +} diff --git a/resources/fs/src/args.rs b/resources/fs/src/args.rs new file mode 100644 index 000000000..7ba96f69d --- /dev/null +++ b/resources/fs/src/args.rs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[clap(name = "file", version = "1.0", about = "Manage state of a file on disk.", long_about = None)] + +pub struct Args { + #[clap(subcommand)] + pub subcommand: SubCommand, +} + +#[derive(Debug, PartialEq, Eq, Subcommand)] +pub enum SubCommand { + #[clap(name = "get", about = "Get the current state of the file.", arg_required_else_help = true)] + Get { + #[clap(short, long, required = true, help = "The path to the file.")] + input: String, + }, + + #[clap(name = "delete", about = "Delete the file on disk.", arg_required_else_help = true)] + Delete { + #[clap(short, long, required = true, help = "The path to the file.")] + input: String, + }, + + // #[clap(name = "export", about = "Exports the files and directories under the specified path", arg_required_else_help = true)] + // Export { + // #[clap(short, long, required = true, help = "The path to the file or directory.")] + // path: String, + // }, + #[clap(name = "schema", about = "Retrieve JSON schema.")] + Schema, +} \ No newline at end of file diff --git a/resources/fs/src/config.rs b/resources/fs/src/config.rs new file mode 100644 index 000000000..d7aff4e25 --- /dev/null +++ b/resources/fs/src/config.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +#[serde(rename ="File", deny_unknown_fields)] +pub struct File { + /// The path to the file. + pub path: String, + + /// The file size. + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + + /// The file hash. + pub hash: String, + + #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + pub exist: Option, +} + + +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +// #[serde(rename ="Directory", deny_unknown_fields)] +// pub struct Directory { +// /// The path to the directory. +// pub path: String, + +// /// The directory size. +// pub size: u64, + +// /// The files under the directory. +// pub files: Vec, + +// #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] +// pub exist: Option, +// } \ No newline at end of file diff --git a/resources/fs/src/file_helper.rs b/resources/fs/src/file_helper.rs new file mode 100644 index 000000000..a9665d059 --- /dev/null +++ b/resources/fs/src/file_helper.rs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::config::File; +//use crate::config::Directory; +use std::fs; +use std::fs::File as fsFile; +use std::path::Path; +use tracing::{debug, error}; +//use fs_extra::dir::get_size; + +pub fn get_file(file: &File) -> Result> { + let resolved_path = Path::new(file.path.as_str()); + debug!("Resolved path: {:?}", resolved_path); + match resolved_path.is_dir() { + true => { + return Ok(File { path: file.path.to_string(), size: None, hash: String::new(), exist: Some(false) }); + } + false => {} + } + let f: fsFile = match fsFile::open(resolved_path) { + Ok(f) => f, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + return Ok(File { path: file.path.to_string(), size: None, hash: String::new(), exist: Some(false) }); + } else { + return Err(e)? + } + } + }; + + let metadata = f.metadata()?; + let hash = calculate_hash(file.path.as_str())?; + + let mut updated_file = file.clone(); + updated_file.path = file.path.clone(); + updated_file.size = Some(metadata.len()); + updated_file.hash = hash; + updated_file.exist = Some(true); + Ok(updated_file) +} + +// pub fn export_path(path: &str) -> Result> { +// // Export the file or directory +// let path = Path::new(path); + +// match path.exists() { +// false => { +// return Ok(Directory { path: path.to_str().unwrap().to_string(), size: 0, files: vec![], exist: Some(false) }); +// } +// _ => {} +// } + +// match path.is_dir() { +// true => { +// let files: Vec = { +// let dir = fs::read_dir(path)?; +// let mut files = Vec::new(); +// for entry in dir { +// let entry = entry?; +// let path = entry.path(); +// files.push(get_file(path.to_str().unwrap())?); +// } +// files +// }; + +// let dir_size = get_size(path)?; + +// Ok(Directory { path: path.to_str().unwrap().to_string(), size: dir_size, files, exist: Some(true) }) +// } +// false => { +// let file = get_file(path.to_str().unwrap())?; +// let parent = path.parent(); +// match parent { +// Some(parent) => { +// Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file], exist: Some(true) }) +// } +// _ => { +// return Err("Path is not a file or directory")?; +// } +// } +// } +// } +// } + +pub fn delete_file(file: &File) -> Result<(), Box> { + let path = file.path.as_str(); + fs::remove_file(path)?; + Ok(()) +} + +fn calculate_hash(path: &str) -> Result> { + let bytes = fs::read(path)?; + let digest = sha256::digest(&bytes); + + Ok(digest) +} diff --git a/resources/fs/src/main.rs b/resources/fs/src/main.rs new file mode 100644 index 000000000..7b76ad687 --- /dev/null +++ b/resources/fs/src/main.rs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use args::Args; +use clap::Parser; +use std::process::exit; +use tracing::{debug, error}; +use crate::config::{File}; +use file_helper::{get_file, delete_file}; +use schemars::schema_for; + +mod args; +pub mod config; +mod file_helper; + +const EXIT_SUCCESS: i32 = 0; +const EXIT_INVALID_INPUT: i32 = 2; + +fn main() { + let args = Args::parse(); + match args.subcommand { + args::SubCommand::Get { input } => { + debug!("Getting file at path: {}", input); + let file: File = match serde_json::from_str(input.as_str()) { + Ok(input) => input, + Err(e) => return Err(e).unwrap(), + }; + + match get_file(&file) { + Ok(file) => { + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + Err(e) => { + error!("Failed to get file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + args::SubCommand::Delete { input } => { + debug!("Deleting file at path: {}", input); + let file: File = match serde_json::from_str(input.as_str()) { + Ok(input) => input, + Err(e) => return Err(e).unwrap(), + }; + + match delete_file(&file) { + Ok(_) => { + debug!("File deleted successfully."); + } + Err(e) => { + error!("Failed to delete file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + // args::SubCommand::Export { path } => { + // println!("Exporting file at path: {}", path); + // match export_path(&path) { + // Ok(dir) => { + // let json = serde_json::to_string(&dir).unwrap(); + // println!("{}", json); + // debug!("File exported successfully."); + // } + // Err(e) => { + // println!("Failed to export file: {}", e); + // exit(EXIT_INVALID_INPUT); + // } + // } + // // Export the file or directory + + // } + args::SubCommand::Schema => { + debug!("Retrieving JSON schema."); + let schema = schema_for!(File); + let json =serde_json::to_string(&schema).unwrap(); + println!("{json}"); + + // let schema = schema_for!(Directory); + // let json = serde_json::to_string(&schema).unwrap(); + // println!("{json}"); + } + } + + exit(EXIT_SUCCESS); +} \ No newline at end of file From c2f9135b8244622f0804169622781e709d1661e2 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Sat, 14 Dec 2024 11:05:28 -0800 Subject: [PATCH 02/14] Refactor --- resources/fs/src/file_helper.rs | 94 ++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/resources/fs/src/file_helper.rs b/resources/fs/src/file_helper.rs index a9665d059..89fc89faf 100644 --- a/resources/fs/src/file_helper.rs +++ b/resources/fs/src/file_helper.rs @@ -6,38 +6,18 @@ use crate::config::File; use std::fs; use std::fs::File as fsFile; use std::path::Path; -use tracing::{debug, error}; +use tracing::{debug}; //use fs_extra::dir::get_size; pub fn get_file(file: &File) -> Result> { - let resolved_path = Path::new(file.path.as_str()); - debug!("Resolved path: {:?}", resolved_path); - match resolved_path.is_dir() { - true => { - return Ok(File { path: file.path.to_string(), size: None, hash: String::new(), exist: Some(false) }); - } - false => {} - } - let f: fsFile = match fsFile::open(resolved_path) { - Ok(f) => f, + match compare_file_state(file) { + Ok(f) => { + Ok(f) + }, Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - return Ok(File { path: file.path.to_string(), size: None, hash: String::new(), exist: Some(false) }); - } else { - return Err(e)? - } + Err(e)? } - }; - - let metadata = f.metadata()?; - let hash = calculate_hash(file.path.as_str())?; - - let mut updated_file = file.clone(); - updated_file.path = file.path.clone(); - updated_file.size = Some(metadata.len()); - updated_file.hash = hash; - updated_file.exist = Some(true); - Ok(updated_file) + } } // pub fn export_path(path: &str) -> Result> { @@ -84,14 +64,66 @@ pub fn get_file(file: &File) -> Result> { // } pub fn delete_file(file: &File) -> Result<(), Box> { - let path = file.path.as_str(); - fs::remove_file(path)?; - Ok(()) + match compare_file_state(file) { + Ok(f) => { + debug!("Deleting file: {:?}", f.path); + fs::remove_file(f.path)?; + Ok(()) + }, + Err(e) => { + Err(e)? + } + } +} + +fn compare_file_state(file: &File) -> Result> { + let resolved_path = Path::new(file.path.as_str()); + debug!("Resolved path: {:?}", resolved_path); + match resolved_path.is_dir() { + true => { + debug!("Path is a directory"); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } + false => {} + } + let f: fsFile = match fsFile::open(resolved_path) { + Ok(f) => { + debug!("File found: {:?}", file.path); + f + }, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + debug!("File not found: {:?}", file.path); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } else { + return Err(e)? + } + } + }; + + let hash = calculate_hash(file.path.as_str())?; + + if hash.to_lowercase() != file.hash.to_lowercase() { + debug!("Hash mismatch: {:?}", file.path); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } + + let metadata = f.metadata()?; + let mut updated_file = file.clone(); + updated_file.size = Some(metadata.len()); + updated_file.exist = Some(true); + + Ok(updated_file) } fn calculate_hash(path: &str) -> Result> { let bytes = fs::read(path)?; let digest = sha256::digest(&bytes); - Ok(digest) } From 0b10559ac1a107ce91069fff52b09d3a3627afb7 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 26 Dec 2024 11:11:40 -0800 Subject: [PATCH 03/14] Rename and reenable directory --- resources/filesys/Cargo.lock | 573 +++++++++++++++++++++++++ resources/filesys/Cargo.toml | 13 + resources/filesys/fs.dsc.resource.json | 37 ++ resources/filesys/src/args.rs | 35 ++ resources/filesys/src/config.rs | 39 ++ resources/filesys/src/file_helper.rs | 129 ++++++ resources/filesys/src/main.rs | 86 ++++ 7 files changed, 912 insertions(+) create mode 100644 resources/filesys/Cargo.lock create mode 100644 resources/filesys/Cargo.toml create mode 100644 resources/filesys/fs.dsc.resource.json create mode 100644 resources/filesys/src/args.rs create mode 100644 resources/filesys/src/config.rs create mode 100644 resources/filesys/src/file_helper.rs create mode 100644 resources/filesys/src/main.rs diff --git a/resources/filesys/Cargo.lock b/resources/filesys/Cargo.lock new file mode 100644 index 000000000..fd0752154 --- /dev/null +++ b/resources/filesys/Cargo.lock @@ -0,0 +1,573 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[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.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "filesys" +version = "0.1.0" +dependencies = [ + "clap", + "fs_extra", + "schemars", + "serde", + "serde_json", + "sha256", + "tracing", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "pin-project-lite", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/resources/filesys/Cargo.toml b/resources/filesys/Cargo.toml new file mode 100644 index 000000000..2284966ac --- /dev/null +++ b/resources/filesys/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "filesys" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version ="4.5", features = ["derive"] } +serde = "1.0" +serde_json = "1.0" +schemars = "0.8" +tracing = { version = "0.1" } +sha256 = { version = "1.5.0" } +fs_extra = { version = "1.2.0" } diff --git a/resources/filesys/fs.dsc.resource.json b/resources/filesys/fs.dsc.resource.json new file mode 100644 index 000000000..7928023f6 --- /dev/null +++ b/resources/filesys/fs.dsc.resource.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", + "type": "Microsoft.DSC/FileSystem", + "description": "Manage file system configuration settings.", + "version": "0.1.0", + "get": { + "executable": "filesys", + "args": [ + "get", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin", + "implementsPretest": false + }, + "delete": { + "executable": "fs", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, + "schema": { + "command": { + "executable": "fs", + "args": [ + "schema" + ] + } + } +} diff --git a/resources/filesys/src/args.rs b/resources/filesys/src/args.rs new file mode 100644 index 000000000..8de982432 --- /dev/null +++ b/resources/filesys/src/args.rs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[clap(name = "file", version = "1.0", about = "Manage state of a file on disk.", long_about = None)] + +pub struct Args { + #[clap(subcommand)] + pub subcommand: SubCommand, +} + +#[derive(Debug, PartialEq, Eq, Subcommand)] +pub enum SubCommand { + #[clap(name = "get", about = "Get the current state of the file.", arg_required_else_help = true)] + Get { + #[clap(short, long, required = true, help = "The path to the file.")] + input: String, + }, + + #[clap(name = "delete", about = "Delete the file on disk.", arg_required_else_help = true)] + Delete { + #[clap(short, long, required = true, help = "The path to the file.")] + input: String, + }, + + #[clap(name = "export", about = "Exports the files and directories under the specified path", arg_required_else_help = true)] + Export { + #[clap(short, long, required = true, help = "The path to the file or directory.")] + path: String, + }, + #[clap(name = "schema", about = "Retrieve JSON schema.")] + Schema, +} \ No newline at end of file diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs new file mode 100644 index 000000000..f7b3ca2a0 --- /dev/null +++ b/resources/filesys/src/config.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +#[serde(rename ="File", deny_unknown_fields)] +pub struct File { + /// The path to the file. + pub path: String, + + /// The file size. + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + + /// The file hash. + pub hash: String, + + #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + pub exist: Option, +} + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +#[serde(rename ="Directory", deny_unknown_fields)] +pub struct Directory { + /// The path to the directory. + pub path: String, + + /// The directory size. + pub size: u64, + + /// The files under the directory. + pub files: Vec, + + #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + pub exist: Option, +} \ No newline at end of file diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs new file mode 100644 index 000000000..56133891c --- /dev/null +++ b/resources/filesys/src/file_helper.rs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::config::File; +use crate::config::Directory; +use std::fs; +use std::fs::File as fsFile; +use std::path::Path; +use tracing::{debug}; +use fs_extra::dir::get_size; + +pub fn get_file(file: &File) -> Result> { + match compare_file_state(file) { + Ok(f) => { + Ok(f) + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn export_path(path: &str) -> Result> { + // Export the file or directory + let path = Path::new(path); + + match path.exists() { + false => { + return Ok(Directory { path: path.to_str().unwrap().to_string(), size: 0, files: vec![], exist: Some(false) }); + } + _ => {} + } + + match path.is_dir() { + true => { + let files: Vec = { + let dir = fs::read_dir(path)?; + let mut files = Vec::new(); + for entry in dir { + let entry = entry?; + let path = entry.path(); + files.push(get_file(path.to_str().unwrap())?); + } + files + }; + + let dir_size = get_size(path)?; + + Ok(Directory { path: path.to_str().unwrap().to_string(), size: dir_size, files, exist: Some(true) }) + } + false => { + let file = get_file(path.to_str().unwrap())?; + let parent = path.parent(); + match parent { + Some(parent) => { + Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file], exist: Some(true) }) + } + _ => { + return Err("Path is not a file or directory")?; + } + } + } + } +} + +pub fn delete_file(file: &File) -> Result<(), Box> { + match compare_file_state(file) { + Ok(f) => { + debug!("Deleting file: {:?}", f.path); + fs::remove_file(f.path)?; + Ok(()) + }, + Err(e) => { + Err(e)? + } + } +} + +fn compare_file_state(file: &File) -> Result> { + let resolved_path = Path::new(file.path.as_str()); + debug!("Resolved path: {:?}", resolved_path); + match resolved_path.is_dir() { + true => { + debug!("Path is a directory"); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } + false => {} + } + let f: fsFile = match fsFile::open(resolved_path) { + Ok(f) => { + debug!("File found: {:?}", file.path); + f + }, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + debug!("File not found: {:?}", file.path); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } else { + return Err(e)? + } + } + }; + + let hash = calculate_hash(file.path.as_str())?; + + if hash.to_lowercase() != file.hash.to_lowercase() { + debug!("Hash mismatch: {:?}", file.path); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } + + let metadata = f.metadata()?; + let mut updated_file = file.clone(); + updated_file.size = Some(metadata.len()); + updated_file.exist = Some(true); + + Ok(updated_file) +} + +fn calculate_hash(path: &str) -> Result> { + let bytes = fs::read(path)?; + let digest = sha256::digest(&bytes); + Ok(digest) +} diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs new file mode 100644 index 000000000..39ef64903 --- /dev/null +++ b/resources/filesys/src/main.rs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use args::Args; +use clap::Parser; +use std::process::exit; +use tracing::{debug, error}; +use crate::config::{File}; +use file_helper::{get_file, delete_file}; +use schemars::schema_for; + +mod args; +pub mod config; +mod file_helper; + +const EXIT_SUCCESS: i32 = 0; +const EXIT_INVALID_INPUT: i32 = 2; + +fn main() { + let args = Args::parse(); + match args.subcommand { + args::SubCommand::Get { input } => { + debug!("Getting file at path: {}", input); + let file: File = match serde_json::from_str(input.as_str()) { + Ok(input) => input, + Err(e) => return Err(e).unwrap(), + }; + + match get_file(&file) { + Ok(file) => { + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + Err(e) => { + error!("Failed to get file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + args::SubCommand::Delete { input } => { + debug!("Deleting file at path: {}", input); + let file: File = match serde_json::from_str(input.as_str()) { + Ok(input) => input, + Err(e) => return Err(e).unwrap(), + }; + + match delete_file(&file) { + Ok(_) => { + debug!("File deleted successfully."); + } + Err(e) => { + error!("Failed to delete file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + args::SubCommand::Export { path } => { + println!("Exporting file at path: {}", path); + match export_path(&path) { + Ok(dir) => { + let json = serde_json::to_string(&dir).unwrap(); + println!("{}", json); + debug!("File exported successfully."); + } + Err(e) => { + println!("Failed to export file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + // Export the file or directory + + } + args::SubCommand::Schema => { + debug!("Retrieving JSON schema."); + let schema = schema_for!(File); + let json =serde_json::to_string(&schema).unwrap(); + println!("{json}"); + + // let schema = schema_for!(Directory); + // let json = serde_json::to_string(&schema).unwrap(); + // println!("{json}"); + } + } + + exit(EXIT_SUCCESS); +} \ No newline at end of file From 92a45167ef19268e367f4abae4008dd1a5ea3160 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 31 Dec 2024 16:05:48 -0800 Subject: [PATCH 04/14] File touch working --- build.ps1 | 2 +- dsc/examples/filesys_create.dsc.yaml | 8 + .../directory.dsc.resource.json} | 25 +- ...c.resource.json => file.dsc.resource.json} | 14 +- .../filesys/filecontent.dsc.resource.json | 50 ++ resources/filesys/src/args.rs | 26 +- resources/filesys/src/config.rs | 43 +- resources/filesys/src/file_helper.rs | 155 ++++- resources/filesys/src/main.rs | 206 +++++-- resources/fs/Cargo.lock | 573 ------------------ resources/fs/Cargo.toml | 13 - resources/fs/src/args.rs | 35 -- resources/fs/src/config.rs | 39 -- resources/fs/src/file_helper.rs | 129 ---- resources/fs/src/main.rs | 86 --- 15 files changed, 441 insertions(+), 963 deletions(-) create mode 100644 dsc/examples/filesys_create.dsc.yaml rename resources/{fs/fs.dsc.resource.json => filesys/directory.dsc.resource.json} (56%) rename resources/filesys/{fs.dsc.resource.json => file.dsc.resource.json} (77%) create mode 100644 resources/filesys/filecontent.dsc.resource.json delete mode 100644 resources/fs/Cargo.lock delete mode 100644 resources/fs/Cargo.toml delete mode 100644 resources/fs/src/args.rs delete mode 100644 resources/fs/src/config.rs delete mode 100644 resources/fs/src/file_helper.rs delete mode 100644 resources/fs/src/main.rs diff --git a/build.ps1 b/build.ps1 index 63267c702..7b284837c 100644 --- a/build.ps1 +++ b/build.ps1 @@ -262,7 +262,7 @@ if (!$SkipBuild) { "osinfo", "powershell-adapter", "process", - "resources/fs", + "resources/filesys", "runcommandonset", "tools/dsctest", "tools/test_group_resource", diff --git a/dsc/examples/filesys_create.dsc.yaml b/dsc/examples/filesys_create.dsc.yaml new file mode 100644 index 000000000..aa5cccfc5 --- /dev/null +++ b/dsc/examples/filesys_create.dsc.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Create empty file + type: Microsoft.DSC/File + properties: + path: "[path('d:\\', 'temp', 'a.txt')]" + _exist: true diff --git a/resources/fs/fs.dsc.resource.json b/resources/filesys/directory.dsc.resource.json similarity index 56% rename from resources/fs/fs.dsc.resource.json rename to resources/filesys/directory.dsc.resource.json index ad923cca3..69edd77cb 100644 --- a/resources/fs/fs.dsc.resource.json +++ b/resources/filesys/directory.dsc.resource.json @@ -1,10 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", - "type": "Microsoft.DSC/FileSystem", - "description": "Manage file system configuration settings.", + "type": "Microsoft.DSC/Directory", + "description": "Manage directory configuration settings.", "version": "0.1.0", "get": { - "executable": "fs", + "executable": "filesys", "args": [ "get", { @@ -16,7 +16,7 @@ "implementsPretest": false }, "delete": { - "executable": "fs", + "executable": "filesys", "args": [ "delete", { @@ -26,11 +26,24 @@ ], "input": "stdin" }, + "set": { + "executable": "filesys", + "args": [ + "set", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, "schema": { "command": { - "executable": "fs", + "executable": "filesys", "args": [ - "schema" + "schema", + "--schema-type", + "directory" ] } } diff --git a/resources/filesys/fs.dsc.resource.json b/resources/filesys/file.dsc.resource.json similarity index 77% rename from resources/filesys/fs.dsc.resource.json rename to resources/filesys/file.dsc.resource.json index 7928023f6..f9ffd47ec 100644 --- a/resources/filesys/fs.dsc.resource.json +++ b/resources/filesys/file.dsc.resource.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", - "type": "Microsoft.DSC/FileSystem", + "type": "Microsoft.DSC/File", "description": "Manage file system configuration settings.", "version": "0.1.0", "get": { @@ -15,10 +15,10 @@ "input": "stdin", "implementsPretest": false }, - "delete": { - "executable": "fs", + "set": { + "executable": "filesys", "args": [ - "delete", + "set", { "jsonInputArg": "--input", "mandatory": true @@ -28,9 +28,11 @@ }, "schema": { "command": { - "executable": "fs", + "executable": "filesys", "args": [ - "schema" + "schema", + "--schema-type", + "file" ] } } diff --git a/resources/filesys/filecontent.dsc.resource.json b/resources/filesys/filecontent.dsc.resource.json new file mode 100644 index 000000000..e430ffb22 --- /dev/null +++ b/resources/filesys/filecontent.dsc.resource.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", + "type": "Microsoft.DSC/FileContent", + "description": "Manage file content configuration settings.", + "version": "0.1.0", + "get": { + "executable": "filesys", + "args": [ + "get", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin", + "implementsPretest": false + }, + "delete": { + "executable": "filesys", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, + "set": { + "executable": "filesys", + "args": [ + "set", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, + "schema": { + "command": { + "executable": "filesys", + "args": [ + "schema", + "--schema-type", + "file-content" + ] + } + } +} diff --git a/resources/filesys/src/args.rs b/resources/filesys/src/args.rs index 8de982432..1a872e9a5 100644 --- a/resources/filesys/src/args.rs +++ b/resources/filesys/src/args.rs @@ -8,7 +8,7 @@ use clap::{Parser, Subcommand}; pub struct Args { #[clap(subcommand)] - pub subcommand: SubCommand, + pub subcommand: SubCommand } #[derive(Debug, PartialEq, Eq, Subcommand)] @@ -25,11 +25,29 @@ pub enum SubCommand { input: String, }, + #[clap(name = "set", about = "Set the current state of file or directory.", arg_required_else_help = true)] + Set { + #[clap(short, long, required = true, help = "The path to the file or directory.")] + input : String, + }, + #[clap(name = "export", about = "Exports the files and directories under the specified path", arg_required_else_help = true)] Export { #[clap(short, long, required = true, help = "The path to the file or directory.")] - path: String, + input: String, }, + #[clap(name = "schema", about = "Retrieve JSON schema.")] - Schema, -} \ No newline at end of file + Schema { + #[clap(short, long, default_value = "file", help = "The type of schema to retrieve.")] + schema_type: FileSystemObjectType, + } +} + +#[derive(Clone, Default, Debug, PartialEq, Eq, clap::ValueEnum)] +pub enum FileSystemObjectType { + #[default] + File, + Directory, + FileContent, +} diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs index f7b3ca2a0..22047e3eb 100644 --- a/resources/filesys/src/config.rs +++ b/resources/filesys/src/config.rs @@ -15,13 +15,12 @@ pub struct File { pub size: Option, /// The file hash. - pub hash: String, + pub hash: Option, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option, } - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] #[serde(rename ="Directory", deny_unknown_fields)] pub struct Directory { @@ -29,11 +28,47 @@ pub struct Directory { pub path: String, /// The directory size. - pub size: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, /// The files under the directory. - pub files: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub files: Option>, + + /// Recurse into subdirectories. + pub recurse: bool, + + #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + pub exist: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +#[serde(rename ="FileContent", deny_unknown_fields)] +pub struct FileContent +{ + /// The path to the file. + pub path: String, + + /// The file hash. + pub hash: String, + + /// The file encoding. + pub encoding: Encoding, + + /// The file content. + pub content: String, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] +pub enum Encoding { + Utf8, + Utf16, + Utf32, + Ascii, + Base64, + Hex, + Binary, } \ No newline at end of file diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs index 56133891c..5c8819271 100644 --- a/resources/filesys/src/file_helper.rs +++ b/resources/filesys/src/file_helper.rs @@ -9,7 +9,92 @@ use std::path::Path; use tracing::{debug}; use fs_extra::dir::get_size; +impl File { + /// Create a new `File`. + /// + /// # Arguments + /// + /// * `string` - The string for the Path + #[must_use] + pub fn new(path: &str) -> File { + File { + path: path.to_string(), + size: None, + hash: None, + exist: None, + } + } +} + +impl Directory { + /// Create a new `Directory`. + /// + /// # Arguments + /// + /// * `string` - The string for the Path + #[must_use] + pub fn new(path: &str) -> Directory { + Directory { + path: path.to_string(), + size: None, + files: None, + recurse: false, + exist: None, + } + } +} + pub fn get_file(file: &File) -> Result> { + debug!("In get_file"); + match compare_file_state(file) { + Ok(f) => { + Ok(f) + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn set_file(file: &File) -> Result> { + match compare_file_state(file) { + Ok(_) => { + debug!("In set_file"); + debug!("file exist {:?}", file_exists(file.path.as_str())); + debug!("expected file exist {:?}", file.exist.unwrap_or(true)); + + match (file_exists(file.path.as_str()), file.exist.unwrap_or(true)) { + // if the current file exists and expected state is exist == false, delete it + (true, false) => { + debug!("Deleting file: {:?}", file.path); + fs::remove_file(file.path.as_str())?; + Ok(get_file(&file)?) + } + + // if the current file does not exist and expected state is exist == true, create it + (false, true) => { + debug!("Creating file: {:?}", file.path); + fsFile::create(file.path.as_str())?; + let new_file = File::new(file.path.as_str()); + + Ok(get_file(&new_file)?) + } + + // if the current file exists and expected state is exist == true or both are false update and return + (true, true) | (false, false) => { + debug!("Updating file: {:?}", file.path); + let new_file = File::new(file.path.as_str()); + Ok(get_file(&new_file)?) + } + } + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn export_file_path(file: &File) -> Result> { match compare_file_state(file) { Ok(f) => { Ok(f) @@ -20,13 +105,13 @@ pub fn get_file(file: &File) -> Result> { } } -pub fn export_path(path: &str) -> Result> { +pub fn export_dir_path(dir: &Directory) -> Result> { // Export the file or directory - let path = Path::new(path); + let path = Path::new(dir.path.as_str()); match path.exists() { false => { - return Ok(Directory { path: path.to_str().unwrap().to_string(), size: 0, files: vec![], exist: Some(false) }); + return Ok(Directory { path: path.to_str().unwrap().to_string(), size: None, files: None, recurse: dir.recurse, exist: Some(false) }); } _ => {} } @@ -39,21 +124,24 @@ pub fn export_path(path: &str) -> Result> for entry in dir { let entry = entry?; let path = entry.path(); - files.push(get_file(path.to_str().unwrap())?); + let f = File::new(path.to_str().unwrap()); + files.push(get_file(&f)?); } files }; let dir_size = get_size(path)?; - Ok(Directory { path: path.to_str().unwrap().to_string(), size: dir_size, files, exist: Some(true) }) + Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: Some(files), recurse: dir.recurse, exist: Some(true) }) } false => { - let file = get_file(path.to_str().unwrap())?; + let path = Path::new(path); + let f = File::new(path.to_str().unwrap()); + let file = get_file(&f)?; let parent = path.parent(); match parent { Some(parent) => { - Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file], exist: Some(true) }) + Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file].into(), recurse: dir.recurse, exist: Some(true) }) } _ => { return Err("Path is not a file or directory")?; @@ -81,10 +169,11 @@ fn compare_file_state(file: &File) -> Result> { debug!("Resolved path: {:?}", resolved_path); match resolved_path.is_dir() { true => { - debug!("Path is a directory"); - let mut updated_file = file.clone(); - updated_file.exist = Some(false); - return Ok(updated_file) + // debug!("Path is a directory"); + // let mut updated_file = file.clone(); + // updated_file.exist = Some(false); + // return Ok(updated_file) + return Err("Path is a directory")? } false => {} } @@ -94,6 +183,7 @@ fn compare_file_state(file: &File) -> Result> { f }, Err(e) => { + debug!("Error: {:?}", e); if e.kind() == std::io::ErrorKind::NotFound { debug!("File not found: {:?}", file.path); let mut updated_file = file.clone(); @@ -107,19 +197,31 @@ fn compare_file_state(file: &File) -> Result> { let hash = calculate_hash(file.path.as_str())?; - if hash.to_lowercase() != file.hash.to_lowercase() { - debug!("Hash mismatch: {:?}", file.path); - let mut updated_file = file.clone(); - updated_file.exist = Some(false); - return Ok(updated_file) - } - - let metadata = f.metadata()?; - let mut updated_file = file.clone(); - updated_file.size = Some(metadata.len()); - updated_file.exist = Some(true); - - Ok(updated_file) + match file.hash.as_ref() { + Some(h) => { + if h.to_lowercase() != hash.to_lowercase() { + debug!("Hash mismatch: {:?}", file.path); + let mut updated_file = file.clone(); + updated_file.exist = Some(false); + return Ok(updated_file) + } + else { + let metadata = f.metadata()?; + let mut updated_file = file.clone(); + updated_file.size = Some(metadata.len()); + updated_file.exist = Some(true); + return Ok(updated_file) + } + } + None => { + let metadata = f.metadata()?; + let mut updated_file = file.clone(); + updated_file.hash = Some(hash); + updated_file.size = Some(metadata.len()); + updated_file.exist = Some(true); + return Ok(updated_file) + } + }; } fn calculate_hash(path: &str) -> Result> { @@ -127,3 +229,8 @@ fn calculate_hash(path: &str) -> Result> { let digest = sha256::digest(&bytes); Ok(digest) } + +fn file_exists(path: &str) -> bool { + let resolved_path = Path::new(path); + return resolved_path.exists(); +} diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index 39ef64903..124c69303 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -5,8 +5,8 @@ use args::Args; use clap::Parser; use std::process::exit; use tracing::{debug, error}; -use crate::config::{File}; -use file_helper::{get_file, delete_file}; +use crate::config::{File, Directory, FileContent}; +use file_helper::{get_file, set_file, delete_file, export_file_path, export_dir_path}; use schemars::schema_for; mod args; @@ -20,67 +20,187 @@ fn main() { let args = Args::parse(); match args.subcommand { args::SubCommand::Get { input } => { - debug!("Getting file at path: {}", input); - let file: File = match serde_json::from_str(input.as_str()) { - Ok(input) => input, - Err(e) => return Err(e).unwrap(), - }; + debug!("Getting at path: {}", input); - match get_file(&file) { - Ok(file) => { + match is_file_type(input.as_str()) { + Some(file) => { + let file = get_file(&file).unwrap(); let json = serde_json::to_string(&file).unwrap(); println!("{}", json); } - Err(e) => { - error!("Failed to get file: {}", e); - exit(EXIT_INVALID_INPUT); + None => { + let dir = match is_directory_type(input.as_str()) { + Some(dir) => { + // let dir = get_file(&dir).unwrap(); + // let json = serde_json::to_string(&dir).unwrap(); + // println!("{}", json); + } + None => { + let filecontent = match is_fillecontent_type(input.as_str()) { + Some(filecontent) => { + // let filecontent = get_file(&filecontent).unwrap(); + // let json = serde_json::to_string(&filecontent).unwrap(); + // println!("{}", json); + } + None => { + error!("Invalid input."); + exit(EXIT_INVALID_INPUT); + } + }; + } + }; } - } + }; } args::SubCommand::Delete { input } => { debug!("Deleting file at path: {}", input); - let file: File = match serde_json::from_str(input.as_str()) { - Ok(input) => input, - Err(e) => return Err(e).unwrap(), + + match is_file_type(input.as_str()) { + Some(file) => { + let file = delete_file(&file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + None => { + let dir = match is_directory_type(input.as_str()) { + Some(dir) => { + // let dir = delete_file(&dir).unwrap(); + // let json = serde_json::to_string(&dir).unwrap(); + // println!("{}", json); + } + None => { + let filecontent = match is_fillecontent_type(input.as_str()) { + Some(filecontent) => { + // let filecontent = delete_file(&filecontent).unwrap(); + // let json = serde_json::to_string(&filecontent).unwrap(); + // println!("{}", json); + } + None => { + error!("Invalid input."); + exit(EXIT_INVALID_INPUT); + } + }; + } + }; + } }; + } + args::SubCommand::Set { input } => { + debug!("Setting file at path: {}", input); - match delete_file(&file) { - Ok(_) => { - debug!("File deleted successfully."); + match is_file_type(input.as_str()) { + Some(file) => { + let file = set_file(&file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + debug!("File set successfully."); } - Err(e) => { - error!("Failed to delete file: {}", e); - exit(EXIT_INVALID_INPUT); + None => { + let dir = match is_directory_type(input.as_str()) { + Some(dir) => { + // let dir = get_file(&dir).unwrap(); + // let json = serde_json::to_string(&dir).unwrap(); + // println!("{}", json); + } + None => { + let filecontent = match is_fillecontent_type(input.as_str()) { + Some(filecontent) => { + // let filecontent = get_file(&filecontent).unwrap(); + // let json = serde_json::to_string(&filecontent).unwrap(); + // println!("{}", json); + } + None => { + error!("Invalid input."); + exit(EXIT_INVALID_INPUT); + } + }; + } + }; } - } + }; } - args::SubCommand::Export { path } => { - println!("Exporting file at path: {}", path); - match export_path(&path) { - Ok(dir) => { - let json = serde_json::to_string(&dir).unwrap(); + args::SubCommand::Export { input } => { + debug!("Exporting file at path: {}", input); + + match is_file_type(input.as_str()) { + Some(file) => { + let file = export_file_path(&file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); println!("{}", json); debug!("File exported successfully."); } - Err(e) => { - println!("Failed to export file: {}", e); - exit(EXIT_INVALID_INPUT); + None => { + let dir = match is_directory_type(input.as_str()) { + Some(dir) => { + let exported_dir = export_dir_path(&dir).unwrap(); + let json = serde_json::to_string(&exported_dir).unwrap(); + println!("{}", json); + debug!("File exported successfully."); + } + None => { + let filecontent = match is_fillecontent_type(input.as_str()) { + Some(filecontent) => { + // let filecontent = get_file(&filecontent).unwrap(); + // let json = serde_json::to_string(&filecontent).unwrap(); + // println!("{}", json); + } + None => { + error!("Invalid input."); + exit(EXIT_INVALID_INPUT); + } + }; + } + }; } - } - // Export the file or directory - + }; } - args::SubCommand::Schema => { - debug!("Retrieving JSON schema."); - let schema = schema_for!(File); - let json =serde_json::to_string(&schema).unwrap(); - println!("{json}"); - - // let schema = schema_for!(Directory); - // let json = serde_json::to_string(&schema).unwrap(); - // println!("{json}"); + args::SubCommand::Schema { schema_type }=> { + match schema_type { + args::FileSystemObjectType::File => { + let schema = schema_for!(File); + let json = serde_json::to_string(&schema).unwrap(); + println!("{}", json); + } + args::FileSystemObjectType::Directory => { + let schema = schema_for!(Directory); + let json = serde_json::to_string(&schema).unwrap(); + println!("{}", json); + } + args::FileSystemObjectType::FileContent => { + let schema = schema_for!(FileContent); + let json = serde_json::to_string(&schema).unwrap(); + println!("{}", json); + } + } } } exit(EXIT_SUCCESS); +} + +fn is_file_type(input: &str) -> Option { + let file: File = match serde_json::from_str(input) { + Ok(input) => input, + Err(_) => return None, + }; + + Some(file) +} + +fn is_directory_type(input: &str) -> Option { + let dir: Directory = match serde_json::from_str(input) { + Ok(input) => input, + Err(_) => return None, + }; + + Some(dir) +} + +fn is_fillecontent_type(input: &str) -> Option { + let filecontent: FileContent = match serde_json::from_str(input) { + Ok(input) => input, + Err(_) => return None, + }; + + Some(filecontent) } \ No newline at end of file diff --git a/resources/fs/Cargo.lock b/resources/fs/Cargo.lock deleted file mode 100644 index a0d4e8910..000000000 --- a/resources/fs/Cargo.lock +++ /dev/null @@ -1,573 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "anstream" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" - -[[package]] -name = "anstyle-parse" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[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.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - -[[package]] -name = "cpufeatures" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" -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.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - -[[package]] -name = "fs" -version = "0.1.0" -dependencies = [ - "clap", - "fs_extra", - "schemars", - "serde", - "serde_json", - "sha256", - "tracing", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "libc" -version = "0.2.167" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "serde" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha256" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" -dependencies = [ - "async-trait", - "bytes", - "hex", - "sha2", - "tokio", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "bytes", - "pin-project-lite", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/resources/fs/Cargo.toml b/resources/fs/Cargo.toml deleted file mode 100644 index 72dc370c6..000000000 --- a/resources/fs/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "fs" -version = "0.1.0" -edition = "2021" - -[dependencies] -clap = { version ="4.5", features = ["derive"] } -serde = "1.0" -serde_json = "1.0" -schemars = "0.8" -tracing = { version = "0.1" } -sha256 = { version = "1.5.0" } -fs_extra = { version = "1.2.0" } diff --git a/resources/fs/src/args.rs b/resources/fs/src/args.rs deleted file mode 100644 index 7ba96f69d..000000000 --- a/resources/fs/src/args.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use clap::{Parser, Subcommand}; - -#[derive(Parser, Debug)] -#[clap(name = "file", version = "1.0", about = "Manage state of a file on disk.", long_about = None)] - -pub struct Args { - #[clap(subcommand)] - pub subcommand: SubCommand, -} - -#[derive(Debug, PartialEq, Eq, Subcommand)] -pub enum SubCommand { - #[clap(name = "get", about = "Get the current state of the file.", arg_required_else_help = true)] - Get { - #[clap(short, long, required = true, help = "The path to the file.")] - input: String, - }, - - #[clap(name = "delete", about = "Delete the file on disk.", arg_required_else_help = true)] - Delete { - #[clap(short, long, required = true, help = "The path to the file.")] - input: String, - }, - - // #[clap(name = "export", about = "Exports the files and directories under the specified path", arg_required_else_help = true)] - // Export { - // #[clap(short, long, required = true, help = "The path to the file or directory.")] - // path: String, - // }, - #[clap(name = "schema", about = "Retrieve JSON schema.")] - Schema, -} \ No newline at end of file diff --git a/resources/fs/src/config.rs b/resources/fs/src/config.rs deleted file mode 100644 index d7aff4e25..000000000 --- a/resources/fs/src/config.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] -#[serde(rename ="File", deny_unknown_fields)] -pub struct File { - /// The path to the file. - pub path: String, - - /// The file size. - #[serde(skip_serializing_if = "Option::is_none")] - pub size: Option, - - /// The file hash. - pub hash: String, - - #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] - pub exist: Option, -} - - -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Hash)] -// #[serde(rename ="Directory", deny_unknown_fields)] -// pub struct Directory { -// /// The path to the directory. -// pub path: String, - -// /// The directory size. -// pub size: u64, - -// /// The files under the directory. -// pub files: Vec, - -// #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] -// pub exist: Option, -// } \ No newline at end of file diff --git a/resources/fs/src/file_helper.rs b/resources/fs/src/file_helper.rs deleted file mode 100644 index 89fc89faf..000000000 --- a/resources/fs/src/file_helper.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::config::File; -//use crate::config::Directory; -use std::fs; -use std::fs::File as fsFile; -use std::path::Path; -use tracing::{debug}; -//use fs_extra::dir::get_size; - -pub fn get_file(file: &File) -> Result> { - match compare_file_state(file) { - Ok(f) => { - Ok(f) - }, - Err(e) => { - Err(e)? - } - } -} - -// pub fn export_path(path: &str) -> Result> { -// // Export the file or directory -// let path = Path::new(path); - -// match path.exists() { -// false => { -// return Ok(Directory { path: path.to_str().unwrap().to_string(), size: 0, files: vec![], exist: Some(false) }); -// } -// _ => {} -// } - -// match path.is_dir() { -// true => { -// let files: Vec = { -// let dir = fs::read_dir(path)?; -// let mut files = Vec::new(); -// for entry in dir { -// let entry = entry?; -// let path = entry.path(); -// files.push(get_file(path.to_str().unwrap())?); -// } -// files -// }; - -// let dir_size = get_size(path)?; - -// Ok(Directory { path: path.to_str().unwrap().to_string(), size: dir_size, files, exist: Some(true) }) -// } -// false => { -// let file = get_file(path.to_str().unwrap())?; -// let parent = path.parent(); -// match parent { -// Some(parent) => { -// Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file], exist: Some(true) }) -// } -// _ => { -// return Err("Path is not a file or directory")?; -// } -// } -// } -// } -// } - -pub fn delete_file(file: &File) -> Result<(), Box> { - match compare_file_state(file) { - Ok(f) => { - debug!("Deleting file: {:?}", f.path); - fs::remove_file(f.path)?; - Ok(()) - }, - Err(e) => { - Err(e)? - } - } -} - -fn compare_file_state(file: &File) -> Result> { - let resolved_path = Path::new(file.path.as_str()); - debug!("Resolved path: {:?}", resolved_path); - match resolved_path.is_dir() { - true => { - debug!("Path is a directory"); - let mut updated_file = file.clone(); - updated_file.exist = Some(false); - return Ok(updated_file) - } - false => {} - } - let f: fsFile = match fsFile::open(resolved_path) { - Ok(f) => { - debug!("File found: {:?}", file.path); - f - }, - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - debug!("File not found: {:?}", file.path); - let mut updated_file = file.clone(); - updated_file.exist = Some(false); - return Ok(updated_file) - } else { - return Err(e)? - } - } - }; - - let hash = calculate_hash(file.path.as_str())?; - - if hash.to_lowercase() != file.hash.to_lowercase() { - debug!("Hash mismatch: {:?}", file.path); - let mut updated_file = file.clone(); - updated_file.exist = Some(false); - return Ok(updated_file) - } - - let metadata = f.metadata()?; - let mut updated_file = file.clone(); - updated_file.size = Some(metadata.len()); - updated_file.exist = Some(true); - - Ok(updated_file) -} - -fn calculate_hash(path: &str) -> Result> { - let bytes = fs::read(path)?; - let digest = sha256::digest(&bytes); - Ok(digest) -} diff --git a/resources/fs/src/main.rs b/resources/fs/src/main.rs deleted file mode 100644 index 7b76ad687..000000000 --- a/resources/fs/src/main.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use args::Args; -use clap::Parser; -use std::process::exit; -use tracing::{debug, error}; -use crate::config::{File}; -use file_helper::{get_file, delete_file}; -use schemars::schema_for; - -mod args; -pub mod config; -mod file_helper; - -const EXIT_SUCCESS: i32 = 0; -const EXIT_INVALID_INPUT: i32 = 2; - -fn main() { - let args = Args::parse(); - match args.subcommand { - args::SubCommand::Get { input } => { - debug!("Getting file at path: {}", input); - let file: File = match serde_json::from_str(input.as_str()) { - Ok(input) => input, - Err(e) => return Err(e).unwrap(), - }; - - match get_file(&file) { - Ok(file) => { - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); - } - Err(e) => { - error!("Failed to get file: {}", e); - exit(EXIT_INVALID_INPUT); - } - } - } - args::SubCommand::Delete { input } => { - debug!("Deleting file at path: {}", input); - let file: File = match serde_json::from_str(input.as_str()) { - Ok(input) => input, - Err(e) => return Err(e).unwrap(), - }; - - match delete_file(&file) { - Ok(_) => { - debug!("File deleted successfully."); - } - Err(e) => { - error!("Failed to delete file: {}", e); - exit(EXIT_INVALID_INPUT); - } - } - } - // args::SubCommand::Export { path } => { - // println!("Exporting file at path: {}", path); - // match export_path(&path) { - // Ok(dir) => { - // let json = serde_json::to_string(&dir).unwrap(); - // println!("{}", json); - // debug!("File exported successfully."); - // } - // Err(e) => { - // println!("Failed to export file: {}", e); - // exit(EXIT_INVALID_INPUT); - // } - // } - // // Export the file or directory - - // } - args::SubCommand::Schema => { - debug!("Retrieving JSON schema."); - let schema = schema_for!(File); - let json =serde_json::to_string(&schema).unwrap(); - println!("{json}"); - - // let schema = schema_for!(Directory); - // let json = serde_json::to_string(&schema).unwrap(); - // println!("{json}"); - } - } - - exit(EXIT_SUCCESS); -} \ No newline at end of file From d08a4b38af3c261f2a133e80f333b7bb252fb4e8 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 2 Jan 2025 09:25:12 -0800 Subject: [PATCH 05/14] Remove delete --- resources/filesys/directory.dsc.resource.json | 11 ----------- resources/filesys/filecontent.dsc.resource.json | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/resources/filesys/directory.dsc.resource.json b/resources/filesys/directory.dsc.resource.json index 69edd77cb..f6006c2ad 100644 --- a/resources/filesys/directory.dsc.resource.json +++ b/resources/filesys/directory.dsc.resource.json @@ -15,17 +15,6 @@ "input": "stdin", "implementsPretest": false }, - "delete": { - "executable": "filesys", - "args": [ - "delete", - { - "jsonInputArg": "--input", - "mandatory": true - } - ], - "input": "stdin" - }, "set": { "executable": "filesys", "args": [ diff --git a/resources/filesys/filecontent.dsc.resource.json b/resources/filesys/filecontent.dsc.resource.json index e430ffb22..6f5aeed3f 100644 --- a/resources/filesys/filecontent.dsc.resource.json +++ b/resources/filesys/filecontent.dsc.resource.json @@ -15,17 +15,6 @@ "input": "stdin", "implementsPretest": false }, - "delete": { - "executable": "filesys", - "args": [ - "delete", - { - "jsonInputArg": "--input", - "mandatory": true - } - ], - "input": "stdin" - }, "set": { "executable": "filesys", "args": [ From 20567c0c20cd070a488d37e8196e09303f663e5a Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 2 Jan 2025 09:55:05 -0800 Subject: [PATCH 06/14] Add delete for file --- resources/filesys/file.dsc.resource.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/resources/filesys/file.dsc.resource.json b/resources/filesys/file.dsc.resource.json index f9ffd47ec..b3487198a 100644 --- a/resources/filesys/file.dsc.resource.json +++ b/resources/filesys/file.dsc.resource.json @@ -26,6 +26,17 @@ ], "input": "stdin" }, + "delete": { + "executable": "filesys", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + } "schema": { "command": { "executable": "filesys", From 54f5060c2ce9d0d6620e8591730edfef53e89cbb Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 3 Jan 2025 11:29:37 -0800 Subject: [PATCH 07/14] dsc/examples/filesys_delete.dsc.yaml --- resources/filesys/directory.dsc.resource.json | 22 +++++++ resources/filesys/file.dsc.resource.json | 13 +++- resources/filesys/src/config.rs | 2 +- resources/filesys/src/file_helper.rs | 66 ------------------- resources/filesys/src/main.rs | 22 ++++--- 5 files changed, 47 insertions(+), 78 deletions(-) diff --git a/resources/filesys/directory.dsc.resource.json b/resources/filesys/directory.dsc.resource.json index f6006c2ad..76294036e 100644 --- a/resources/filesys/directory.dsc.resource.json +++ b/resources/filesys/directory.dsc.resource.json @@ -26,6 +26,28 @@ ], "input": "stdin" }, + "delete": { + "executable": "filesys", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, + "export": { + "executable": "filesys", + "args": [ + "export", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, "schema": { "command": { "executable": "filesys", diff --git a/resources/filesys/file.dsc.resource.json b/resources/filesys/file.dsc.resource.json index b3487198a..ea7efc8cc 100644 --- a/resources/filesys/file.dsc.resource.json +++ b/resources/filesys/file.dsc.resource.json @@ -36,7 +36,18 @@ } ], "input": "stdin" - } + }, + "export": { + "executable": "filesys", + "args": [ + "export", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, "schema": { "command": { "executable": "filesys", diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs index 22047e3eb..3ee437c83 100644 --- a/resources/filesys/src/config.rs +++ b/resources/filesys/src/config.rs @@ -36,7 +36,7 @@ pub struct Directory { pub files: Option>, /// Recurse into subdirectories. - pub recurse: bool, + pub recurse: Option, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option, diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs index 5c8819271..40c67ec19 100644 --- a/resources/filesys/src/file_helper.rs +++ b/resources/filesys/src/file_helper.rs @@ -2,12 +2,10 @@ // Licensed under the MIT License. use crate::config::File; -use crate::config::Directory; use std::fs; use std::fs::File as fsFile; use std::path::Path; use tracing::{debug}; -use fs_extra::dir::get_size; impl File { /// Create a new `File`. @@ -26,24 +24,6 @@ impl File { } } -impl Directory { - /// Create a new `Directory`. - /// - /// # Arguments - /// - /// * `string` - The string for the Path - #[must_use] - pub fn new(path: &str) -> Directory { - Directory { - path: path.to_string(), - size: None, - files: None, - recurse: false, - exist: None, - } - } -} - pub fn get_file(file: &File) -> Result> { debug!("In get_file"); match compare_file_state(file) { @@ -105,52 +85,6 @@ pub fn export_file_path(file: &File) -> Result> } } -pub fn export_dir_path(dir: &Directory) -> Result> { - // Export the file or directory - let path = Path::new(dir.path.as_str()); - - match path.exists() { - false => { - return Ok(Directory { path: path.to_str().unwrap().to_string(), size: None, files: None, recurse: dir.recurse, exist: Some(false) }); - } - _ => {} - } - - match path.is_dir() { - true => { - let files: Vec = { - let dir = fs::read_dir(path)?; - let mut files = Vec::new(); - for entry in dir { - let entry = entry?; - let path = entry.path(); - let f = File::new(path.to_str().unwrap()); - files.push(get_file(&f)?); - } - files - }; - - let dir_size = get_size(path)?; - - Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: Some(files), recurse: dir.recurse, exist: Some(true) }) - } - false => { - let path = Path::new(path); - let f = File::new(path.to_str().unwrap()); - let file = get_file(&f)?; - let parent = path.parent(); - match parent { - Some(parent) => { - Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file].into(), recurse: dir.recurse, exist: Some(true) }) - } - _ => { - return Err("Path is not a file or directory")?; - } - } - } - } -} - pub fn delete_file(file: &File) -> Result<(), Box> { match compare_file_state(file) { Ok(f) => { diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index 124c69303..534467100 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -6,12 +6,14 @@ use clap::Parser; use std::process::exit; use tracing::{debug, error}; use crate::config::{File, Directory, FileContent}; -use file_helper::{get_file, set_file, delete_file, export_file_path, export_dir_path}; +use file_helper::{get_file, set_file, delete_file, export_file_path}; +use dir_helpers::{get_dir, set_dir, delete_dir, export_dir_path}; use schemars::schema_for; mod args; pub mod config; mod file_helper; +mod dir_helpers; const EXIT_SUCCESS: i32 = 0; const EXIT_INVALID_INPUT: i32 = 2; @@ -31,9 +33,9 @@ fn main() { None => { let dir = match is_directory_type(input.as_str()) { Some(dir) => { - // let dir = get_file(&dir).unwrap(); - // let json = serde_json::to_string(&dir).unwrap(); - // println!("{}", json); + let dir = get_dir(&dir).unwrap(); + let json = serde_json::to_string(&dir).unwrap(); + println!("{}", json); } None => { let filecontent = match is_fillecontent_type(input.as_str()) { @@ -64,9 +66,9 @@ fn main() { None => { let dir = match is_directory_type(input.as_str()) { Some(dir) => { - // let dir = delete_file(&dir).unwrap(); - // let json = serde_json::to_string(&dir).unwrap(); - // println!("{}", json); + let dir = delete_dir(&dir).unwrap(); + let json = serde_json::to_string(&dir).unwrap(); + println!("{}", json); } None => { let filecontent = match is_fillecontent_type(input.as_str()) { @@ -98,9 +100,9 @@ fn main() { None => { let dir = match is_directory_type(input.as_str()) { Some(dir) => { - // let dir = get_file(&dir).unwrap(); - // let json = serde_json::to_string(&dir).unwrap(); - // println!("{}", json); + let dir = set_dir(&dir).unwrap(); + let json = serde_json::to_string(&dir).unwrap(); + println!("{}", json); } None => { let filecontent = match is_fillecontent_type(input.as_str()) { From 55d2efecaade65df4bf076c2e240f340a54b0774 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 3 Jan 2025 11:31:29 -0800 Subject: [PATCH 08/14] Directory resource --- dsc/examples/filesys_delete.dsc.yaml | 8 + dsc/examples/filesys_dir_create.dsc.yaml | 8 + dsc/examples/filesys_dir_delete.yaml | 8 + .../filesys_dir_delete_recurse.dsc.yaml | 9 + resources/filesys/src/dir_helpers.rs | 184 ++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 dsc/examples/filesys_delete.dsc.yaml create mode 100644 dsc/examples/filesys_dir_create.dsc.yaml create mode 100644 dsc/examples/filesys_dir_delete.yaml create mode 100644 dsc/examples/filesys_dir_delete_recurse.dsc.yaml create mode 100644 resources/filesys/src/dir_helpers.rs diff --git a/dsc/examples/filesys_delete.dsc.yaml b/dsc/examples/filesys_delete.dsc.yaml new file mode 100644 index 000000000..d8cb882d0 --- /dev/null +++ b/dsc/examples/filesys_delete.dsc.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Create empty file + type: Microsoft.DSC/File + properties: + path: "[path('d:\\', 'temp', 'a.txt')]" + _exist: false diff --git a/dsc/examples/filesys_dir_create.dsc.yaml b/dsc/examples/filesys_dir_create.dsc.yaml new file mode 100644 index 000000000..3cb85773b --- /dev/null +++ b/dsc/examples/filesys_dir_create.dsc.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Create empty directory + type: Microsoft.DSC/Directory + properties: + path: "[path('d:\\', 'temp', 'testdir')]" + _exist: true diff --git a/dsc/examples/filesys_dir_delete.yaml b/dsc/examples/filesys_dir_delete.yaml new file mode 100644 index 000000000..4dc1ba33c --- /dev/null +++ b/dsc/examples/filesys_dir_delete.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Delete empty directory + type: Microsoft.DSC/Directory + properties: + path: "[path('d:\\', 'temp', 'testdir')]" + _exist: false diff --git a/dsc/examples/filesys_dir_delete_recurse.dsc.yaml b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml new file mode 100644 index 000000000..d0bc16d5f --- /dev/null +++ b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml @@ -0,0 +1,9 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Delete non-empty directory + type: Microsoft.DSC/Directory + properties: + path: "[path('d:\\', 'temp', 'testdir')]" + recurse: true + _exist: false diff --git a/resources/filesys/src/dir_helpers.rs b/resources/filesys/src/dir_helpers.rs new file mode 100644 index 000000000..5af8634c6 --- /dev/null +++ b/resources/filesys/src/dir_helpers.rs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::config::File; +use crate::config::Directory; +use crate::file_helper::get_file; +use std::fs; +use std::path::Path; +use tracing::{debug}; +use fs_extra::dir::get_size; + +impl Directory { + /// Create a new `Directory`. + /// + /// # Arguments + /// + /// * `string` - The string for the Path + #[must_use] + pub fn new(path: &str) -> Directory { + Directory { + path: path.to_string(), + size: None, + files: None, + recurse: Some(false), + exist: None, + } + } +} + +pub fn get_dir(dir: &Directory) -> Result> { + debug!("In get_dir"); + match compare_dir_state(dir) { + Ok(d) => { + Ok(d) + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn set_dir(dir: &Directory) -> Result> { + match compare_dir_state(dir) { + Ok(current_dir) => { + debug!("In set_dir"); + debug!("dir exist {:?}", dir.exist); + debug!("expected dir exist {:?}", dir.exist.unwrap_or(true)); + + match (current_dir.exist.unwrap_or(true), dir.exist.unwrap_or(true)) { + // if the current dir exists and expected state is exist == true, do nothing + (true, true) | (false, false) => { + return Ok(current_dir); + } + + // if the current dir exists and expected state is exist == true, create it + (true, false) => { + debug!("Deleting directory: {:?}", dir.path); + + if dir.recurse.unwrap_or(false) { + fs::remove_dir_all(dir.path.as_str())?; + } else { + fs::remove_dir(dir.path.as_str())?; + } + + return Ok(get_dir(&dir)?) + } + + // if the current dir does not exist and expected state is exist == true, create it + (false, true) => { + debug!("Creating directory: {:?}", dir.path); + fs::create_dir_all(dir.path.as_str())?; + return Ok(get_dir(&dir)?) + } + } + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn export_dir_path(dir: &Directory) -> Result> { + // Export the file or directory + let path = Path::new(dir.path.as_str()); + + match path.exists() { + false => { + return Ok(Directory { path: path.to_str().unwrap().to_string(), size: None, files: None, recurse: dir.recurse, exist: Some(false) }); + } + _ => {} + } + + match path.is_dir() { + true => { + let files: Vec = { + let dir = fs::read_dir(path)?; + let mut files = Vec::new(); + for entry in dir { + let entry = entry?; + let path = entry.path(); + let f = File::new(path.to_str().unwrap()); + files.push(get_file(&f)?); + } + files + }; + + let dir_size = get_size(path)?; + + Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: Some(files), recurse: dir.recurse, exist: Some(true) }) + } + false => { + let path = Path::new(path); + let f = File::new(path.to_str().unwrap()); + let file = get_file(&f)?; + let parent = path.parent(); + match parent { + Some(parent) => { + Ok(Directory { path: parent.to_str().unwrap().to_string(), size: file.size, files: vec![file].into(), recurse: dir.recurse, exist: Some(true) }) + } + _ => { + return Err("Path is not a file or directory")?; + } + } + } + } +} + +pub fn delete_dir(dir: &Directory) -> Result<(), Box> { + match compare_dir_state(dir) { + Ok(d) => { + + if d.exist == Some(false) { + return Ok(()); + } + + if d.recurse == Some(true) { + debug!("Deleting directory: {:?}", d.path); + fs::remove_dir_all(d.path)?; + return Ok(()); + } + else { + debug!("Deleting directory: {:?}", d.path); + fs::remove_dir(d.path)?; + return Ok(()); + } + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn compare_dir_state(dir: &Directory) -> Result> { + let path = Path::new(dir.path.as_str()); + + match path.exists() { + false => { + return Ok(Directory { path: path.to_str().unwrap().to_string(), size: None, files: None, recurse: dir.recurse, exist: Some(false) }); + } + true => { + match path.is_dir() { + false => { + return Err("Path is not a directory")?; + } + _ => {} + } + } + } + + let dir_size = get_size(path)?; + + match dir.size { + Some(size) => { + if size != dir_size { + Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: None, recurse: dir.recurse, exist: Some(true) }) + } else { + Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: None, recurse: dir.recurse, exist: Some(true) }) + } + } + None => { + Ok(Directory { path: path.to_str().unwrap().to_string(), size: Some(dir_size), files: None, recurse: dir.recurse, exist: Some(true) }) + } + } +} \ No newline at end of file From 9f3c89533f587e7cbea8cb55c78c85fe4fc6ec0e Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 13 Jan 2025 16:30:52 -0800 Subject: [PATCH 09/14] File content resource --- resources/filesys/Cargo.lock | 20 +++ resources/filesys/Cargo.toml | 2 + resources/filesys/src/config.rs | 20 +-- resources/filesys/src/file_helper.rs | 2 +- resources/filesys/src/filecontent_helper.rs | 136 ++++++++++++++++++++ resources/filesys/src/main.rs | 14 +- 6 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 resources/filesys/src/filecontent_helper.rs diff --git a/resources/filesys/Cargo.lock b/resources/filesys/Cargo.lock index fd0752154..242a87142 100644 --- a/resources/filesys/Cargo.lock +++ b/resources/filesys/Cargo.lock @@ -194,11 +194,31 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + [[package]] name = "filesys" version = "0.1.0" dependencies = [ "clap", + "encoding_rs", + "encoding_rs_io", "fs_extra", "schemars", "serde", diff --git a/resources/filesys/Cargo.toml b/resources/filesys/Cargo.toml index 2284966ac..361035053 100644 --- a/resources/filesys/Cargo.toml +++ b/resources/filesys/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] clap = { version ="4.5", features = ["derive"] } +encoding_rs = "0.8.35" +encoding_rs_io = "0.1.7" serde = "1.0" serde_json = "1.0" schemars = "0.8" diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs index 3ee437c83..b718d0824 100644 --- a/resources/filesys/src/config.rs +++ b/resources/filesys/src/config.rs @@ -36,6 +36,7 @@ pub struct Directory { pub files: Option>, /// Recurse into subdirectories. + #[serde(skip_serializing_if = "Option::is_none")] pub recurse: Option, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] @@ -49,15 +50,19 @@ pub struct FileContent /// The path to the file. pub path: String, - /// The file hash. - pub hash: String, + /// The file hash. If not provided, the hash is calculated from the content. + #[serde(skip_serializing_if = "Option::is_none")] + pub hash: Option, - /// The file encoding. - pub encoding: Encoding, + /// The file encoding. UTF-8 is the default. + #[serde(skip_serializing_if = "Option::is_none")] + pub encoding: Option, /// The file content. - pub content: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + /// If the file exists. True is the default. #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option, } @@ -66,9 +71,6 @@ pub struct FileContent pub enum Encoding { Utf8, Utf16, - Utf32, Ascii, - Base64, - Hex, Binary, -} \ No newline at end of file +} diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs index 40c67ec19..fe6973308 100644 --- a/resources/filesys/src/file_helper.rs +++ b/resources/filesys/src/file_helper.rs @@ -158,7 +158,7 @@ fn compare_file_state(file: &File) -> Result> { }; } -fn calculate_hash(path: &str) -> Result> { +pub fn calculate_hash(path: &str) -> Result> { let bytes = fs::read(path)?; let digest = sha256::digest(&bytes); Ok(digest) diff --git a/resources/filesys/src/filecontent_helper.rs b/resources/filesys/src/filecontent_helper.rs new file mode 100644 index 000000000..b7e5bb46c --- /dev/null +++ b/resources/filesys/src/filecontent_helper.rs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::config::{FileContent, Encoding}; +use std::path::Path; +use std::io; +use tracing::{debug}; + +impl Encoding { + pub fn to_encoding_rs(&self) -> Option<&'static encoding_rs::Encoding> { + match self { + Encoding::Utf8 => Some(encoding_rs::UTF_8), + Encoding::Utf16 => Some(encoding_rs::UTF_16LE), // or UTF_16BE depending on your needs + Encoding::Ascii => Some(encoding_rs::WINDOWS_1252), // ASCII is a subset of Windows-1252 + Encoding::Binary => None, + } + } +} + +impl FileContent { + /// Create a new `FileContent`. + /// + /// # Arguments + /// + /// * `string` - The string for the Path + #[must_use] + pub fn new(path: &str) -> FileContent { + FileContent { + path: path.to_string(), + content: None, + hash: None, + encoding: Some(Encoding::Utf8), + exist: None, + } + } +} + +pub fn get_file_content(filecontent: &FileContent) -> Result> { + debug!("In get_file_content"); + match compare_filecontent_state(filecontent) { + Ok(f) => { + Ok(f) + }, + Err(e) => { + Err(e)? + } + } +} + +pub fn set_file_content(filecontent: &FileContent) -> Result> { + // debug!("In set_file_content"); + // let path = Path::new(&filecontent.path); + // let content = filecontent.content.as_ref().unwrap_or(&String::new()); + // let encoding = filecontent.encoding.unwrap_or(Encoding::Utf8).to_encoding_rs().unwrap_or(encoding_rs::UTF_8); + // let mut file = fsFile::create(path)?; + // let mut encoder = encoding.new_encoder(); + // let mut bytes = vec![0; content.len() * encoding.new_encoder().max_buffer_length()]; + // let (bytes_written, _, _) = encoder.encode_to_slice(content, &mut bytes, true); + // file.write_all(&bytes[..bytes_written])?; + // Ok(filecontent.clone()) + Ok(filecontent.clone()) +} + +pub fn delete_file_content(filecontent: &FileContent) -> Result> { + debug!("In delete_file_content"); + let path = Path::new(&filecontent.path); + + let mut filecontent_to_delete = filecontent.clone(); + + if path.exists() { + + filecontent_to_delete.exist = Some(false); + filecontent_to_delete.content = None; + set_file_content(&filecontent)?; + } + + Ok(filecontent_to_delete) +} + +pub fn read_file_with_encoding(path: &Path, encoding: &'static encoding_rs::Encoding) -> io::Result { + let bytes = std::fs::read(path)?; + let (decoded_str, _encoding_used, had_errors) = encoding.decode(&bytes); + + if had_errors { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid encoding")); + } + + Ok(decoded_str.to_string()) +} + +pub fn compare_filecontent_state(filecontent: &FileContent) -> Result> { + debug!("In compare_filecontent_state"); + + let rs_encoding = filecontent.encoding.as_ref().unwrap_or(&Encoding::Utf8).to_encoding_rs().unwrap_or(encoding_rs::UTF_8); + + let path = Path::new(&filecontent.path); + if path.exists() { + let content = read_file_with_encoding(path, rs_encoding)?; + let content_hash = sha256::digest(content.as_bytes()); + let hash = filecontent.hash.as_ref().unwrap_or(&content_hash); + + match filecontent.hash.as_ref() { + Some(h) => { + if h.to_lowercase() == hash.to_lowercase() { + let mut updated_file_content = filecontent.clone(); + updated_file_content.hash = Some(hash.to_string()); + updated_file_content.content = Some(content); + updated_file_content.exist = Some(true); + + return Ok(updated_file_content) + } + else { + return Err("Hash does not match")?; + } + }, + None => { + let mut updated_file_content = filecontent.clone(); + updated_file_content.hash = Some(hash.to_string()); + updated_file_content.content = Some(content); + updated_file_content.exist = Some(true); + + return Ok(updated_file_content) + } + } + } + else { + match filecontent.exist { + Some(true) | None => { + return Err("File does not exist")?; + }, + Some(false) => { + return Ok(filecontent.clone()); + } + } + } +} diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index 534467100..4acd1c1e1 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -8,12 +8,14 @@ use tracing::{debug, error}; use crate::config::{File, Directory, FileContent}; use file_helper::{get_file, set_file, delete_file, export_file_path}; use dir_helpers::{get_dir, set_dir, delete_dir, export_dir_path}; +use filecontent_helper::{get_file_content, delete_file_content}; use schemars::schema_for; mod args; pub mod config; mod file_helper; mod dir_helpers; +mod filecontent_helper; const EXIT_SUCCESS: i32 = 0; const EXIT_INVALID_INPUT: i32 = 2; @@ -40,9 +42,9 @@ fn main() { None => { let filecontent = match is_fillecontent_type(input.as_str()) { Some(filecontent) => { - // let filecontent = get_file(&filecontent).unwrap(); - // let json = serde_json::to_string(&filecontent).unwrap(); - // println!("{}", json); + let filecontent = get_file_content(&filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); } None => { error!("Invalid input."); @@ -73,9 +75,9 @@ fn main() { None => { let filecontent = match is_fillecontent_type(input.as_str()) { Some(filecontent) => { - // let filecontent = delete_file(&filecontent).unwrap(); - // let json = serde_json::to_string(&filecontent).unwrap(); - // println!("{}", json); + let filecontent = delete_file_content(&filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); } None => { error!("Invalid input."); From 3b6a65120b634ca6a04cd4be91933878a3cd92c4 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 15 Jan 2025 14:35:47 -0800 Subject: [PATCH 10/14] File content resource --- dsc/examples/filesys_filecontent.yaml | 9 +++++ .../filesys/filecontent.dsc.resource.json | 11 ++++++ resources/filesys/src/filecontent_helper.rs | 36 +++++++++++++++---- resources/filesys/src/main.rs | 24 ++++++------- 4 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 dsc/examples/filesys_filecontent.yaml diff --git a/dsc/examples/filesys_filecontent.yaml b/dsc/examples/filesys_filecontent.yaml new file mode 100644 index 000000000..e4b93f300 --- /dev/null +++ b/dsc/examples/filesys_filecontent.yaml @@ -0,0 +1,9 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: Set file content + type: Microsoft.DSC/FileContent + properties: + path: "[path('d:\\', 'temp', 'a.txt')]" + content: "Hello, World!" + _exist: true diff --git a/resources/filesys/filecontent.dsc.resource.json b/resources/filesys/filecontent.dsc.resource.json index 6f5aeed3f..31091ead7 100644 --- a/resources/filesys/filecontent.dsc.resource.json +++ b/resources/filesys/filecontent.dsc.resource.json @@ -26,6 +26,17 @@ ], "input": "stdin" }, + "delete": { + "executable": "filesys", + "args": [ + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "input": "stdin" + }, "schema": { "command": { "executable": "filesys", diff --git a/resources/filesys/src/filecontent_helper.rs b/resources/filesys/src/filecontent_helper.rs index b7e5bb46c..c21394745 100644 --- a/resources/filesys/src/filecontent_helper.rs +++ b/resources/filesys/src/filecontent_helper.rs @@ -4,6 +4,7 @@ use crate::config::{FileContent, Encoding}; use std::path::Path; use std::io; +use std::io::Write; use tracing::{debug}; impl Encoding { @@ -48,16 +49,35 @@ pub fn get_file_content(filecontent: &FileContent) -> Result Result> { - // debug!("In set_file_content"); - // let path = Path::new(&filecontent.path); - // let content = filecontent.content.as_ref().unwrap_or(&String::new()); - // let encoding = filecontent.encoding.unwrap_or(Encoding::Utf8).to_encoding_rs().unwrap_or(encoding_rs::UTF_8); - // let mut file = fsFile::create(path)?; + debug!("In set_file_content"); + let path = Path::new(&filecontent.path); + //let content = filecontent.content.as_ref().unwrap_or(&String::new()); + //let encoding = filecontent.encoding.unwrap_or(Encoding::Utf8).to_encoding_rs().unwrap_or(encoding_rs::UTF_8); + let file_expected_exists = filecontent.exist.unwrap_or(true); + + if path.exists() && !file_expected_exists { + std::fs::remove_file(path)?; + return Ok(filecontent.clone()) + } + else if !path.exists() && !file_expected_exists { + return Ok(filecontent.clone()) + } + + let mut file = std::fs::File::create(path)?; + // let mut encoder = encoding.new_encoder(); // let mut bytes = vec![0; content.len() * encoding.new_encoder().max_buffer_length()]; // let (bytes_written, _, _) = encoder.encode_to_slice(content, &mut bytes, true); // file.write_all(&bytes[..bytes_written])?; - // Ok(filecontent.clone()) + + match &filecontent.content { + Some(content) => { + file.write_all(content.as_bytes())?; + }, + None => { + } + } + Ok(filecontent.clone()) } @@ -126,7 +146,9 @@ pub fn compare_filecontent_state(filecontent: &FileContent) -> Result { - return Err("File does not exist")?; + let mut updated_file_content = filecontent.clone(); + updated_file_content.exist = Some(false); + return Ok(updated_file_content); }, Some(false) => { return Ok(filecontent.clone()); diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index 4acd1c1e1..df0aa5de8 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -8,7 +8,7 @@ use tracing::{debug, error}; use crate::config::{File, Directory, FileContent}; use file_helper::{get_file, set_file, delete_file, export_file_path}; use dir_helpers::{get_dir, set_dir, delete_dir, export_dir_path}; -use filecontent_helper::{get_file_content, delete_file_content}; +use filecontent_helper::{get_file_content, set_file_content, delete_file_content}; use schemars::schema_for; mod args; @@ -40,7 +40,7 @@ fn main() { println!("{}", json); } None => { - let filecontent = match is_fillecontent_type(input.as_str()) { + let filecontent = match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = get_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); @@ -73,7 +73,7 @@ fn main() { println!("{}", json); } None => { - let filecontent = match is_fillecontent_type(input.as_str()) { + let filecontent = match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = delete_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); @@ -107,11 +107,11 @@ fn main() { println!("{}", json); } None => { - let filecontent = match is_fillecontent_type(input.as_str()) { + let filecontent = match is_filecontent_type(input.as_str()) { Some(filecontent) => { - // let filecontent = get_file(&filecontent).unwrap(); - // let json = serde_json::to_string(&filecontent).unwrap(); - // println!("{}", json); + let filecontent = set_file_content(&filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); } None => { error!("Invalid input."); @@ -142,11 +142,11 @@ fn main() { debug!("File exported successfully."); } None => { - let filecontent = match is_fillecontent_type(input.as_str()) { + let filecontent = match is_filecontent_type(input.as_str()) { Some(filecontent) => { - // let filecontent = get_file(&filecontent).unwrap(); - // let json = serde_json::to_string(&filecontent).unwrap(); - // println!("{}", json); + let filecontent = get_file_content(&filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); } None => { error!("Invalid input."); @@ -200,7 +200,7 @@ fn is_directory_type(input: &str) -> Option { Some(dir) } -fn is_fillecontent_type(input: &str) -> Option { +fn is_filecontent_type(input: &str) -> Option { let filecontent: FileContent = match serde_json::from_str(input) { Ok(input) => input, Err(_) => return None, From b1f744cdbe66af76b7ee60e43f60e0f7b7189d59 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 10 Mar 2025 15:38:33 -0700 Subject: [PATCH 11/14] Update test yamls --- dsc/examples/filesys_create.dsc.yaml | 2 +- dsc/examples/filesys_delete.dsc.yaml | 2 +- dsc/examples/filesys_dir_create.dsc.yaml | 2 +- dsc/examples/filesys_dir_delete.yaml | 2 +- dsc/examples/filesys_dir_delete_recurse.dsc.yaml | 2 +- dsc/examples/filesys_filecontent.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dsc/examples/filesys_create.dsc.yaml b/dsc/examples/filesys_create.dsc.yaml index aa5cccfc5..0021f6ee5 100644 --- a/dsc/examples/filesys_create.dsc.yaml +++ b/dsc/examples/filesys_create.dsc.yaml @@ -4,5 +4,5 @@ resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: "[path('d:\\', 'temp', 'a.txt')]" + path: "[path(envar(TEMP), 'test-file-resource.txt')]" _exist: true diff --git a/dsc/examples/filesys_delete.dsc.yaml b/dsc/examples/filesys_delete.dsc.yaml index d8cb882d0..1f8af4fc8 100644 --- a/dsc/examples/filesys_delete.dsc.yaml +++ b/dsc/examples/filesys_delete.dsc.yaml @@ -4,5 +4,5 @@ resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: "[path('d:\\', 'temp', 'a.txt')]" + path: "[path(envar(TEMP), 'test-file-resource.txt')]" _exist: false diff --git a/dsc/examples/filesys_dir_create.dsc.yaml b/dsc/examples/filesys_dir_create.dsc.yaml index 3cb85773b..b21437458 100644 --- a/dsc/examples/filesys_dir_create.dsc.yaml +++ b/dsc/examples/filesys_dir_create.dsc.yaml @@ -4,5 +4,5 @@ resources: - name: Create empty directory type: Microsoft.DSC/Directory properties: - path: "[path('d:\\', 'temp', 'testdir')]" + path: "[path(envar(TEMP), 'test-file-resource.txt')]" _exist: true diff --git a/dsc/examples/filesys_dir_delete.yaml b/dsc/examples/filesys_dir_delete.yaml index 4dc1ba33c..f137be25d 100644 --- a/dsc/examples/filesys_dir_delete.yaml +++ b/dsc/examples/filesys_dir_delete.yaml @@ -4,5 +4,5 @@ resources: - name: Delete empty directory type: Microsoft.DSC/Directory properties: - path: "[path('d:\\', 'temp', 'testdir')]" + path: "[path(envar(TEMP), 'testdir')]" _exist: false diff --git a/dsc/examples/filesys_dir_delete_recurse.dsc.yaml b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml index d0bc16d5f..def041875 100644 --- a/dsc/examples/filesys_dir_delete_recurse.dsc.yaml +++ b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml @@ -4,6 +4,6 @@ resources: - name: Delete non-empty directory type: Microsoft.DSC/Directory properties: - path: "[path('d:\\', 'temp', 'testdir')]" + path: "[path(envar(TEMP), 'testdir')]" recurse: true _exist: false diff --git a/dsc/examples/filesys_filecontent.yaml b/dsc/examples/filesys_filecontent.yaml index e4b93f300..05245bce5 100644 --- a/dsc/examples/filesys_filecontent.yaml +++ b/dsc/examples/filesys_filecontent.yaml @@ -4,6 +4,6 @@ resources: - name: Set file content type: Microsoft.DSC/FileContent properties: - path: "[path('d:\\', 'temp', 'a.txt')]" + path: "[path(envar(TEMP), 'test-file-resource.txt')]" content: "Hello, World!" _exist: true From c8a1d5394ebb9b6c5c7b354f4c5372f11249054f Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 11 Mar 2025 16:41:35 -0700 Subject: [PATCH 12/14] Fix file resource --- dsc/examples/filesys_create.dsc.yaml | 5 +- dsc/examples/filesys_delete.dsc.yaml | 5 +- dsc/examples/filesys_dir_create.dsc.yaml | 4 +- ...elete.yaml => filesys_dir_delete.dsc.yaml} | 4 +- .../filesys_dir_delete_recurse.dsc.yaml | 4 +- ...tent.yaml => filesys_filecontent.dsc.yaml} | 5 +- dsc/tests/filesys.tests.ps1 | 89 +++++++++++++++++++ pal/Cargo.toml | 2 +- resources/filesys/src/config.rs | 5 ++ resources/filesys/src/dir_helpers.rs | 4 +- resources/filesys/src/file_helper.rs | 42 +++++---- resources/filesys/src/filecontent_helper.rs | 3 +- resources/filesys/src/main.rs | 16 ++-- 13 files changed, 145 insertions(+), 43 deletions(-) rename dsc/examples/{filesys_dir_delete.yaml => filesys_dir_delete.dsc.yaml} (56%) rename dsc/examples/{filesys_filecontent.yaml => filesys_filecontent.dsc.yaml} (56%) create mode 100644 dsc/tests/filesys.tests.ps1 diff --git a/dsc/examples/filesys_create.dsc.yaml b/dsc/examples/filesys_create.dsc.yaml index 0021f6ee5..a88b75adc 100644 --- a/dsc/examples/filesys_create.dsc.yaml +++ b/dsc/examples/filesys_create.dsc.yaml @@ -1,8 +1,9 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: "[path(envar(TEMP), 'test-file-resource.txt')]" + path: "[envvar('TEMP')]" + name: 'test-file-resource.txt' _exist: true diff --git a/dsc/examples/filesys_delete.dsc.yaml b/dsc/examples/filesys_delete.dsc.yaml index 1f8af4fc8..587c234ff 100644 --- a/dsc/examples/filesys_delete.dsc.yaml +++ b/dsc/examples/filesys_delete.dsc.yaml @@ -1,8 +1,9 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: "[path(envar(TEMP), 'test-file-resource.txt')]" + path: '[path(envvar('TEMP'))]'' + name: 'test-file-resource.txt' _exist: false diff --git a/dsc/examples/filesys_dir_create.dsc.yaml b/dsc/examples/filesys_dir_create.dsc.yaml index b21437458..457f46110 100644 --- a/dsc/examples/filesys_dir_create.dsc.yaml +++ b/dsc/examples/filesys_dir_create.dsc.yaml @@ -1,8 +1,8 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Create empty directory type: Microsoft.DSC/Directory properties: - path: "[path(envar(TEMP), 'test-file-resource.txt')]" + path: "[path(envvar('TEMP'), 'test-directory')]" _exist: true diff --git a/dsc/examples/filesys_dir_delete.yaml b/dsc/examples/filesys_dir_delete.dsc.yaml similarity index 56% rename from dsc/examples/filesys_dir_delete.yaml rename to dsc/examples/filesys_dir_delete.dsc.yaml index f137be25d..d45b092fd 100644 --- a/dsc/examples/filesys_dir_delete.yaml +++ b/dsc/examples/filesys_dir_delete.dsc.yaml @@ -1,8 +1,8 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Delete empty directory type: Microsoft.DSC/Directory properties: - path: "[path(envar(TEMP), 'testdir')]" + path: "[path(envvar('TEMP'), 'test-dir-resource')]" _exist: false diff --git a/dsc/examples/filesys_dir_delete_recurse.dsc.yaml b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml index def041875..40bdeda81 100644 --- a/dsc/examples/filesys_dir_delete_recurse.dsc.yaml +++ b/dsc/examples/filesys_dir_delete_recurse.dsc.yaml @@ -1,9 +1,9 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Delete non-empty directory type: Microsoft.DSC/Directory properties: - path: "[path(envar(TEMP), 'testdir')]" + path: "[path(envvar('TEMP'), 'test-dir-resource')]" recurse: true _exist: false diff --git a/dsc/examples/filesys_filecontent.yaml b/dsc/examples/filesys_filecontent.dsc.yaml similarity index 56% rename from dsc/examples/filesys_filecontent.yaml rename to dsc/examples/filesys_filecontent.dsc.yaml index 05245bce5..a855c15b2 100644 --- a/dsc/examples/filesys_filecontent.yaml +++ b/dsc/examples/filesys_filecontent.dsc.yaml @@ -1,9 +1,10 @@ # Example configuration mixing native app resources with classic PS resources -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Set file content type: Microsoft.DSC/FileContent properties: - path: "[path(envar(TEMP), 'test-file-resource.txt')]" + path: '[path(envvar('TEMP'))]'' + name: 'test-file-resource.txt' content: "Hello, World!" _exist: true diff --git a/dsc/tests/filesys.tests.ps1 b/dsc/tests/filesys.tests.ps1 new file mode 100644 index 000000000..3d40ad667 --- /dev/null +++ b/dsc/tests/filesys.tests.ps1 @@ -0,0 +1,89 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'FileSys resoure tests' { + BeforeAll { + $testDir = Join-Path $env:TEMP 'test-dir-resource' + $testFile = Join-Path $testDir 'test-file-resource.txt' + $testFileName = 'test-file-resource.txt' + } + + It 'Filesys resource can create file' { + if (Test-Path $testFile) { + Remove-Item -Path $testFile -Force + } + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_create.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $path = $resultJson.results.result.afterState.path + $name = $resultJson.results.result.afterState.name + + $path | Should -Be $env:TEMP + (Join-Path $path $name) | Should -Exist + Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.FileInfo' + } + + It 'Filesys resource can create directory' { + $resultJson = dsc config set -f "../examples/filesys_dir_create.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $resultJson.results.result.afterState.path | Should -Exist + Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.DirectoryInfo' + + } + + It 'Filesys resource can create file with content' { + $resultJson = dsc config set -f "../examples/filesys_filecontent.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + + $resultFilePath = $resultJson.results.result.afterState.path + $resultFilePath | Should -Exist + Get-Content $resultFilePath | Should -Be "Hello, World!" + } + + It 'Filesys resource can delete a file' { + if (-not (Test-Path $testFile)) { + New-Item -Path $testFile -ItemType File -Force | Out-Null + } + + $resultJson = dsc config set -f "../examples/filesys_delete.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $resultFilePath = $resultJson.results.result.afterState.path + $resultFilePath | Should -Not -Exist + } + + It 'Filesys resource can delete an empty directory' -Pending { + if (-not (Test-Path $testDir)) { + New-Item -Path $testDir -ItemType Directory -Force | Out-Null + } + + $resultJson = dsc config set -f "../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $resultDirPath = $resultJson.results.result.afterState.path + $resultDirPath | Should -Not -Exist + } + + It 'Filesys resource can delete a non-empty directory' -Pending { + if (-not (Test-Path $testDir)) { + New-Item -Path $testDir -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path $testDir $testFileName) -ItemType File -Force | Out-Null + } + + $resultJson = dsc config set -f "../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $resultDirPath = $resultJson.results.result.afterState.path + $resultDirPath | Should -Not -Exist + } + + It 'Filesys resource can delete a directory recursively' -Pending { + if (-not (Test-Path $testDir)) { + $dirPath = New-Item -Path $testDir -ItemType Directory -Force | Out-Null + $subDirPath = New-Item -Path (Join-Path $dirPath 'test-subdir') -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path $subDirPath $testFileName) -ItemType File -Force | Out-Null + } + + $resultJson = dsc config set -f "../examples/filesys_dir_delete_recursive.dsc.yaml" | ConvertFrom-Json + $resultJson.hadErrors | Should -BeFalse + $resultDirPath = $resultJson.results.result.afterState.path + $resultDirPath | Should -Not -Exist + } +} \ No newline at end of file diff --git a/pal/Cargo.toml b/pal/Cargo.toml index a75d6c5bd..ab00c3b5b 100644 --- a/pal/Cargo.toml +++ b/pal/Cargo.toml @@ -13,4 +13,4 @@ opt-level = 2 lto = true [build-dependencies] -cc = "1.1" +cc = "~1.1" diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs index b718d0824..1aebfabef 100644 --- a/resources/filesys/src/config.rs +++ b/resources/filesys/src/config.rs @@ -10,6 +10,8 @@ pub struct File { /// The path to the file. pub path: String, + pub name: String, + /// The file size. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, @@ -50,6 +52,9 @@ pub struct FileContent /// The path to the file. pub path: String, + /// The file name. + pub name: String, + /// The file hash. If not provided, the hash is calculated from the content. #[serde(skip_serializing_if = "Option::is_none")] pub hash: Option, diff --git a/resources/filesys/src/dir_helpers.rs b/resources/filesys/src/dir_helpers.rs index 5af8634c6..1d4c93d83 100644 --- a/resources/filesys/src/dir_helpers.rs +++ b/resources/filesys/src/dir_helpers.rs @@ -98,7 +98,7 @@ pub fn export_dir_path(dir: &Directory) -> Result Result { let path = Path::new(path); - let f = File::new(path.to_str().unwrap()); + let f = File::new(path.to_str().unwrap(), path.file_name().unwrap().to_str().unwrap()); let file = get_file(&f)?; let parent = path.parent(); match parent { diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs index fe6973308..e46dbaece 100644 --- a/resources/filesys/src/file_helper.rs +++ b/resources/filesys/src/file_helper.rs @@ -14,9 +14,10 @@ impl File { /// /// * `string` - The string for the Path #[must_use] - pub fn new(path: &str) -> File { + pub fn new(path: &str, name: &str) -> File { File { path: path.to_string(), + name: name.to_string(), size: None, hash: None, exist: None, @@ -25,7 +26,7 @@ impl File { } pub fn get_file(file: &File) -> Result> { - debug!("In get_file"); + println!("In get_file"); match compare_file_state(file) { Ok(f) => { Ok(f) @@ -40,30 +41,33 @@ pub fn set_file(file: &File) -> Result> { match compare_file_state(file) { Ok(_) => { debug!("In set_file"); - debug!("file exist {:?}", file_exists(file.path.as_str())); + debug!("file exist {:?}", file_exists(file.path.as_str(), file.name.as_str())); debug!("expected file exist {:?}", file.exist.unwrap_or(true)); - match (file_exists(file.path.as_str()), file.exist.unwrap_or(true)) { + let resolved_path = Path::new(file.path.as_str()).join(file.name.as_str()); + + match (file_exists(file.path.as_str(), file.name.as_str()), file.exist.unwrap_or(true)) { + // if the current file exists and expected state is exist == false, delete it (true, false) => { - debug!("Deleting file: {:?}", file.path); - fs::remove_file(file.path.as_str())?; + debug!("Deleting file: {:?}", resolved_path); + fs::remove_file(resolved_path)?; Ok(get_file(&file)?) } // if the current file does not exist and expected state is exist == true, create it (false, true) => { - debug!("Creating file: {:?}", file.path); - fsFile::create(file.path.as_str())?; - let new_file = File::new(file.path.as_str()); + debug!("Creating file: {:?}", resolved_path); + fsFile::create(resolved_path)?; + let new_file = File::new(file.path.as_str(), file.name.as_str()); Ok(get_file(&new_file)?) } // if the current file exists and expected state is exist == true or both are false update and return (true, true) | (false, false) => { - debug!("Updating file: {:?}", file.path); - let new_file = File::new(file.path.as_str()); + println!("Updating file: {:?}", resolved_path); + let new_file = File::new(file.path.as_str(), file.name.as_str()); Ok(get_file(&new_file)?) } } @@ -99,8 +103,8 @@ pub fn delete_file(file: &File) -> Result<(), Box> { } fn compare_file_state(file: &File) -> Result> { - let resolved_path = Path::new(file.path.as_str()); - debug!("Resolved path: {:?}", resolved_path); + let resolved_path = Path::new(file.path.as_str()).join(file.name.as_str()); + println!("Resolved path: {:?}", resolved_path); match resolved_path.is_dir() { true => { // debug!("Path is a directory"); @@ -111,9 +115,9 @@ fn compare_file_state(file: &File) -> Result> { } false => {} } - let f: fsFile = match fsFile::open(resolved_path) { + let f: fsFile = match fsFile::open(resolved_path.clone()) { Ok(f) => { - debug!("File found: {:?}", file.path); + debug!("File found: {:?}", resolved_path.clone()); f }, Err(e) => { @@ -129,12 +133,12 @@ fn compare_file_state(file: &File) -> Result> { } }; - let hash = calculate_hash(file.path.as_str())?; + let hash = calculate_hash(resolved_path.to_str().unwrap())?; match file.hash.as_ref() { Some(h) => { if h.to_lowercase() != hash.to_lowercase() { - debug!("Hash mismatch: {:?}", file.path); + debug!("Hash mismatch"); let mut updated_file = file.clone(); updated_file.exist = Some(false); return Ok(updated_file) @@ -164,7 +168,7 @@ pub fn calculate_hash(path: &str) -> Result> Ok(digest) } -fn file_exists(path: &str) -> bool { - let resolved_path = Path::new(path); +fn file_exists(path: &str, name: &str) -> bool { + let resolved_path = Path::new(path).join(name); return resolved_path.exists(); } diff --git a/resources/filesys/src/filecontent_helper.rs b/resources/filesys/src/filecontent_helper.rs index c21394745..764625be7 100644 --- a/resources/filesys/src/filecontent_helper.rs +++ b/resources/filesys/src/filecontent_helper.rs @@ -25,9 +25,10 @@ impl FileContent { /// /// * `string` - The string for the Path #[must_use] - pub fn new(path: &str) -> FileContent { + pub fn new(path: &str, name: &str) -> FileContent { FileContent { path: path.to_string(), + name: name.to_string(), content: None, hash: None, encoding: Some(Encoding::Utf8), diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index df0aa5de8..2e9a3e852 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -33,14 +33,14 @@ fn main() { println!("{}", json); } None => { - let dir = match is_directory_type(input.as_str()) { + match is_directory_type(input.as_str()) { Some(dir) => { let dir = get_dir(&dir).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - let filecontent = match is_filecontent_type(input.as_str()) { + match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = get_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); @@ -66,14 +66,14 @@ fn main() { println!("{}", json); } None => { - let dir = match is_directory_type(input.as_str()) { + match is_directory_type(input.as_str()) { Some(dir) => { let dir = delete_dir(&dir).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - let filecontent = match is_filecontent_type(input.as_str()) { + match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = delete_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); @@ -100,14 +100,14 @@ fn main() { debug!("File set successfully."); } None => { - let dir = match is_directory_type(input.as_str()) { + match is_directory_type(input.as_str()) { Some(dir) => { let dir = set_dir(&dir).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - let filecontent = match is_filecontent_type(input.as_str()) { + match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = set_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); @@ -134,7 +134,7 @@ fn main() { debug!("File exported successfully."); } None => { - let dir = match is_directory_type(input.as_str()) { + match is_directory_type(input.as_str()) { Some(dir) => { let exported_dir = export_dir_path(&dir).unwrap(); let json = serde_json::to_string(&exported_dir).unwrap(); @@ -142,7 +142,7 @@ fn main() { debug!("File exported successfully."); } None => { - let filecontent = match is_filecontent_type(input.as_str()) { + match is_filecontent_type(input.as_str()) { Some(filecontent) => { let filecontent = get_file_content(&filecontent).unwrap(); let json = serde_json::to_string(&filecontent).unwrap(); From a6ff0bf957d95010b7b443b3abfb4884666ea5b9 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 14 Mar 2025 07:04:33 -0700 Subject: [PATCH 13/14] Fixes and tests --- dsc/examples/filesys_create.dsc.yaml | 3 +- dsc/examples/filesys_create_parent.dsc.yaml | 8 + dsc/examples/filesys_delete.dsc.yaml | 3 +- dsc/examples/filesys_dir_create.dsc.yaml | 2 +- .../filesys_dir_create_parent.dsc.yaml | 8 + dsc/examples/filesys_filecontent.dsc.yaml | 3 +- .../filesys_filecontent_parent.dsc.yaml | 9 + dsc/tests/filesys.tests.ps1 | 97 +++++-- resources/filesys/directory.dsc.resource.json | 16 +- resources/filesys/file.dsc.resource.json | 16 +- .../filesys/filecontent.dsc.resource.json | 12 +- resources/filesys/src/args.rs | 8 + resources/filesys/src/config.rs | 5 - resources/filesys/src/dir_helpers.rs | 20 +- resources/filesys/src/file_helper.rs | 41 +-- resources/filesys/src/filecontent_helper.rs | 10 +- resources/filesys/src/main.rs | 264 +++++++++++------- 17 files changed, 351 insertions(+), 174 deletions(-) create mode 100644 dsc/examples/filesys_create_parent.dsc.yaml create mode 100644 dsc/examples/filesys_dir_create_parent.dsc.yaml create mode 100644 dsc/examples/filesys_filecontent_parent.dsc.yaml diff --git a/dsc/examples/filesys_create.dsc.yaml b/dsc/examples/filesys_create.dsc.yaml index a88b75adc..3b93ea952 100644 --- a/dsc/examples/filesys_create.dsc.yaml +++ b/dsc/examples/filesys_create.dsc.yaml @@ -4,6 +4,5 @@ resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: "[envvar('TEMP')]" - name: 'test-file-resource.txt' + path: "[path(envvar('TEMP'), 'test-file-resource.txt')]" _exist: true diff --git a/dsc/examples/filesys_create_parent.dsc.yaml b/dsc/examples/filesys_create_parent.dsc.yaml new file mode 100644 index 000000000..244af7e89 --- /dev/null +++ b/dsc/examples/filesys_create_parent.dsc.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Create empty file + type: Microsoft.DSC/File + properties: + path: "[path(envvar('TEMP'), 'test-dir-resource', 'test-file-resource.txt')]" + _exist: true diff --git a/dsc/examples/filesys_delete.dsc.yaml b/dsc/examples/filesys_delete.dsc.yaml index 587c234ff..aefb61a64 100644 --- a/dsc/examples/filesys_delete.dsc.yaml +++ b/dsc/examples/filesys_delete.dsc.yaml @@ -4,6 +4,5 @@ resources: - name: Create empty file type: Microsoft.DSC/File properties: - path: '[path(envvar('TEMP'))]'' - name: 'test-file-resource.txt' + path: "[path(envvar('TEMP'), 'test-file-resource.txt')]" _exist: false diff --git a/dsc/examples/filesys_dir_create.dsc.yaml b/dsc/examples/filesys_dir_create.dsc.yaml index 457f46110..c6d91d1a8 100644 --- a/dsc/examples/filesys_dir_create.dsc.yaml +++ b/dsc/examples/filesys_dir_create.dsc.yaml @@ -4,5 +4,5 @@ resources: - name: Create empty directory type: Microsoft.DSC/Directory properties: - path: "[path(envvar('TEMP'), 'test-directory')]" + path: "[path(envvar('TEMP'), 'test-dir-resource')]" _exist: true diff --git a/dsc/examples/filesys_dir_create_parent.dsc.yaml b/dsc/examples/filesys_dir_create_parent.dsc.yaml new file mode 100644 index 000000000..4502926f7 --- /dev/null +++ b/dsc/examples/filesys_dir_create_parent.dsc.yaml @@ -0,0 +1,8 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Create empty directory + type: Microsoft.DSC/Directory + properties: + path: "[path(envvar('TEMP'), 'test-dir-resource', 'test-sub-dir-resource')]" + _exist: true diff --git a/dsc/examples/filesys_filecontent.dsc.yaml b/dsc/examples/filesys_filecontent.dsc.yaml index a855c15b2..44faae1ec 100644 --- a/dsc/examples/filesys_filecontent.dsc.yaml +++ b/dsc/examples/filesys_filecontent.dsc.yaml @@ -4,7 +4,6 @@ resources: - name: Set file content type: Microsoft.DSC/FileContent properties: - path: '[path(envvar('TEMP'))]'' - name: 'test-file-resource.txt' + path: "[path(envvar('TEMP'), 'test-file-resource.txt')]" content: "Hello, World!" _exist: true diff --git a/dsc/examples/filesys_filecontent_parent.dsc.yaml b/dsc/examples/filesys_filecontent_parent.dsc.yaml new file mode 100644 index 000000000..72174b8dc --- /dev/null +++ b/dsc/examples/filesys_filecontent_parent.dsc.yaml @@ -0,0 +1,9 @@ +# Example configuration mixing native app resources with classic PS resources +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Set file content + type: Microsoft.DSC/FileContent + properties: + path: "[path(envvar('TEMP'), 'test-dir-resource', 'test-file-resource.txt')]" + content: "Hello, World!" + _exist: true diff --git a/dsc/tests/filesys.tests.ps1 b/dsc/tests/filesys.tests.ps1 index 3d40ad667..2daae2a52 100644 --- a/dsc/tests/filesys.tests.ps1 +++ b/dsc/tests/filesys.tests.ps1 @@ -14,25 +14,28 @@ Describe 'FileSys resoure tests' { } $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_create.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $path = $resultJson.results.result.afterState.path - $name = $resultJson.results.result.afterState.name - - $path | Should -Be $env:TEMP - (Join-Path $path $name) | Should -Exist + $path | Should -Exist Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.FileInfo' } It 'Filesys resource can create directory' { - $resultJson = dsc config set -f "../examples/filesys_dir_create.dsc.yaml" | ConvertFrom-Json + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse + } + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_create.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultJson.results.result.afterState.path | Should -Exist Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.DirectoryInfo' - } It 'Filesys resource can create file with content' { - $resultJson = dsc config set -f "../examples/filesys_filecontent.dsc.yaml" | ConvertFrom-Json + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_filecontent.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultFilePath = $resultJson.results.result.afterState.path @@ -45,45 +48,93 @@ Describe 'FileSys resoure tests' { New-Item -Path $testFile -ItemType File -Force | Out-Null } - $resultJson = dsc config set -f "../examples/filesys_delete.dsc.yaml" | ConvertFrom-Json + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_delete.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultFilePath = $resultJson.results.result.afterState.path $resultFilePath | Should -Not -Exist } - It 'Filesys resource can delete an empty directory' -Pending { - if (-not (Test-Path $testDir)) { - New-Item -Path $testDir -ItemType Directory -Force | Out-Null + It 'Filesys resource can delete an empty directory' { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse } - $resultJson = dsc config set -f "../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + New-Item -Path $testDir -ItemType Directory -Force | Out-Null + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultDirPath = $resultJson.results.result.afterState.path $resultDirPath | Should -Not -Exist } - It 'Filesys resource can delete a non-empty directory' -Pending { - if (-not (Test-Path $testDir)) { - New-Item -Path $testDir -ItemType Directory -Force | Out-Null - New-Item -Path (Join-Path $testDir $testFileName) -ItemType File -Force | Out-Null + It 'Filesys resource cannot delete a non-empty directory' -Pending { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse } - $resultJson = dsc config set -f "../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + New-Item -Path $testDir -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path $testDir $testFileName) -ItemType File -Force | Out-Null + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultDirPath = $resultJson.results.result.afterState.path $resultDirPath | Should -Not -Exist } - It 'Filesys resource can delete a directory recursively' -Pending { - if (-not (Test-Path $testDir)) { - $dirPath = New-Item -Path $testDir -ItemType Directory -Force | Out-Null - $subDirPath = New-Item -Path (Join-Path $dirPath 'test-subdir') -ItemType Directory -Force | Out-Null - New-Item -Path (Join-Path $subDirPath $testFileName) -ItemType File -Force | Out-Null + It 'Filesys resource can delete a directory recursively' { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse } - $resultJson = dsc config set -f "../examples/filesys_dir_delete_recursive.dsc.yaml" | ConvertFrom-Json + $dirPath = New-Item -Path $testDir -ItemType Directory -Force + $subDirPath = New-Item -Path (Join-Path $dirPath 'test-subdir') -ItemType Directory -Force + New-Item -Path (Join-Path $subDirPath $testFileName) -ItemType File -Force | Out-Null + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_delete_recurse.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 $resultJson.hadErrors | Should -BeFalse $resultDirPath = $resultJson.results.result.afterState.path $resultDirPath | Should -Not -Exist } + + It 'Can create file if parent directory does not exist' { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse + } + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_create_parent.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $resultJson.hadErrors | Should -BeFalse + $resultJson.results.result.afterState.path | Should -Exist + Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.FileInfo' + } + + It 'Can create file with content if parent directory does not exist' { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse + } + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_filecontent_parent.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $resultJson.hadErrors | Should -BeFalse + + $resultFilePath = $resultJson.results.result.afterState.path + $resultFilePath | Should -Exist + Get-Content $resultFilePath | Should -Be "Hello, World!" + } + + It 'Can create directory if parent directory does not exist' { + if (Test-Path $testDir) { + Remove-Item -Path $testDir -Force -Recurse + } + + $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_create_parent.dsc.yaml" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $resultJson.hadErrors | Should -BeFalse + $resultJson.results.result.afterState.path | Should -Exist + Get-Item $resultJson.results.result.afterState.path | Should -BeOfType 'System.IO.DirectoryInfo' + } } \ No newline at end of file diff --git a/resources/filesys/directory.dsc.resource.json b/resources/filesys/directory.dsc.resource.json index 76294036e..1d3502c56 100644 --- a/resources/filesys/directory.dsc.resource.json +++ b/resources/filesys/directory.dsc.resource.json @@ -10,7 +10,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "directory" ], "input": "stdin", "implementsPretest": false @@ -22,7 +24,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "directory" ], "input": "stdin" }, @@ -33,7 +37,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "directory" ], "input": "stdin" }, @@ -44,7 +50,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "directory" ], "input": "stdin" }, diff --git a/resources/filesys/file.dsc.resource.json b/resources/filesys/file.dsc.resource.json index ea7efc8cc..1bdcab38e 100644 --- a/resources/filesys/file.dsc.resource.json +++ b/resources/filesys/file.dsc.resource.json @@ -10,7 +10,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file" ], "input": "stdin", "implementsPretest": false @@ -22,7 +24,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file" ], "input": "stdin" }, @@ -33,7 +37,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file" ], "input": "stdin" }, @@ -44,7 +50,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file" ], "input": "stdin" }, diff --git a/resources/filesys/filecontent.dsc.resource.json b/resources/filesys/filecontent.dsc.resource.json index 31091ead7..8e86ba8e1 100644 --- a/resources/filesys/filecontent.dsc.resource.json +++ b/resources/filesys/filecontent.dsc.resource.json @@ -10,7 +10,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file-content" ], "input": "stdin", "implementsPretest": false @@ -22,7 +24,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file-content" ], "input": "stdin" }, @@ -33,7 +37,9 @@ { "jsonInputArg": "--input", "mandatory": true - } + }, + "--schema-type", + "file-content" ], "input": "stdin" }, diff --git a/resources/filesys/src/args.rs b/resources/filesys/src/args.rs index 1a872e9a5..3e9bb5b62 100644 --- a/resources/filesys/src/args.rs +++ b/resources/filesys/src/args.rs @@ -17,24 +17,32 @@ pub enum SubCommand { Get { #[clap(short, long, required = true, help = "The path to the file.")] input: String, + #[clap(short, long, default_value = "file", help = "The type of file system resource.")] + schema_type: FileSystemObjectType, }, #[clap(name = "delete", about = "Delete the file on disk.", arg_required_else_help = true)] Delete { #[clap(short, long, required = true, help = "The path to the file.")] input: String, + #[clap(short, long, default_value = "file", help = "The type of file system resource.")] + schema_type: FileSystemObjectType, }, #[clap(name = "set", about = "Set the current state of file or directory.", arg_required_else_help = true)] Set { #[clap(short, long, required = true, help = "The path to the file or directory.")] input : String, + #[clap(short, long, default_value = "file", help = "The type of file system resource.")] + schema_type: FileSystemObjectType, }, #[clap(name = "export", about = "Exports the files and directories under the specified path", arg_required_else_help = true)] Export { #[clap(short, long, required = true, help = "The path to the file or directory.")] input: String, + #[clap(short, long, default_value = "file", help = "The type of file system resource.")] + schema_type: FileSystemObjectType, }, #[clap(name = "schema", about = "Retrieve JSON schema.")] diff --git a/resources/filesys/src/config.rs b/resources/filesys/src/config.rs index 1aebfabef..b718d0824 100644 --- a/resources/filesys/src/config.rs +++ b/resources/filesys/src/config.rs @@ -10,8 +10,6 @@ pub struct File { /// The path to the file. pub path: String, - pub name: String, - /// The file size. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, @@ -52,9 +50,6 @@ pub struct FileContent /// The path to the file. pub path: String, - /// The file name. - pub name: String, - /// The file hash. If not provided, the hash is calculated from the content. #[serde(skip_serializing_if = "Option::is_none")] pub hash: Option, diff --git a/resources/filesys/src/dir_helpers.rs b/resources/filesys/src/dir_helpers.rs index 1d4c93d83..ae16a3053 100644 --- a/resources/filesys/src/dir_helpers.rs +++ b/resources/filesys/src/dir_helpers.rs @@ -98,7 +98,7 @@ pub fn export_dir_path(dir: &Directory) -> Result Result { let path = Path::new(path); - let f = File::new(path.to_str().unwrap(), path.file_name().unwrap().to_str().unwrap()); + let f = File::new(path.to_str().unwrap()); let file = get_file(&f)?; let parent = path.parent(); match parent { @@ -140,6 +140,22 @@ pub fn delete_dir(dir: &Directory) -> Result<(), Box> { } else { debug!("Deleting directory: {:?}", d.path); + + // Check if the directory is empty + let entries = fs::read_dir(d.path.to_string())?; + let mut is_empty = true; + for entry in entries { + let entry = entry?; + if entry.path().is_dir() { + is_empty = false; + break; + } + } + + if !is_empty { + return Err("Directory is not empty")?; + } + fs::remove_dir(d.path)?; return Ok(()); } diff --git a/resources/filesys/src/file_helper.rs b/resources/filesys/src/file_helper.rs index e46dbaece..7b29c436f 100644 --- a/resources/filesys/src/file_helper.rs +++ b/resources/filesys/src/file_helper.rs @@ -14,10 +14,9 @@ impl File { /// /// * `string` - The string for the Path #[must_use] - pub fn new(path: &str, name: &str) -> File { + pub fn new(path: &str) -> File { File { path: path.to_string(), - name: name.to_string(), size: None, hash: None, exist: None, @@ -26,7 +25,7 @@ impl File { } pub fn get_file(file: &File) -> Result> { - println!("In get_file"); + debug!("In get_file"); match compare_file_state(file) { Ok(f) => { Ok(f) @@ -41,12 +40,12 @@ pub fn set_file(file: &File) -> Result> { match compare_file_state(file) { Ok(_) => { debug!("In set_file"); - debug!("file exist {:?}", file_exists(file.path.as_str(), file.name.as_str())); + debug!("file exist {:?}", file_exists(file.path.as_str())); debug!("expected file exist {:?}", file.exist.unwrap_or(true)); - let resolved_path = Path::new(file.path.as_str()).join(file.name.as_str()); + let resolved_path = Path::new(file.path.as_str()); - match (file_exists(file.path.as_str(), file.name.as_str()), file.exist.unwrap_or(true)) { + match (file_exists(file.path.as_str()), file.exist.unwrap_or(true)) { // if the current file exists and expected state is exist == false, delete it (true, false) => { @@ -58,16 +57,23 @@ pub fn set_file(file: &File) -> Result> { // if the current file does not exist and expected state is exist == true, create it (false, true) => { debug!("Creating file: {:?}", resolved_path); + + if let Some(parent) = resolved_path.parent() { + if !parent.exists() { + fs::create_dir_all(parent)?; + } + } + fsFile::create(resolved_path)?; - let new_file = File::new(file.path.as_str(), file.name.as_str()); + let new_file = File::new(file.path.as_str()); Ok(get_file(&new_file)?) } // if the current file exists and expected state is exist == true or both are false update and return (true, true) | (false, false) => { - println!("Updating file: {:?}", resolved_path); - let new_file = File::new(file.path.as_str(), file.name.as_str()); + debug!("Updating file: {:?}", resolved_path); + let new_file = File::new(file.path.as_str()); Ok(get_file(&new_file)?) } } @@ -103,21 +109,17 @@ pub fn delete_file(file: &File) -> Result<(), Box> { } fn compare_file_state(file: &File) -> Result> { - let resolved_path = Path::new(file.path.as_str()).join(file.name.as_str()); - println!("Resolved path: {:?}", resolved_path); + let resolved_path = Path::new(file.path.as_str()); + debug!("Resolved path: {:?}", resolved_path); match resolved_path.is_dir() { true => { - // debug!("Path is a directory"); - // let mut updated_file = file.clone(); - // updated_file.exist = Some(false); - // return Ok(updated_file) return Err("Path is a directory")? } false => {} } - let f: fsFile = match fsFile::open(resolved_path.clone()) { + let f: fsFile = match fsFile::open(resolved_path) { Ok(f) => { - debug!("File found: {:?}", resolved_path.clone()); + debug!("File found: {:?}", resolved_path); f }, Err(e) => { @@ -168,7 +170,6 @@ pub fn calculate_hash(path: &str) -> Result> Ok(digest) } -fn file_exists(path: &str, name: &str) -> bool { - let resolved_path = Path::new(path).join(name); - return resolved_path.exists(); +fn file_exists(path: &str) -> bool { + return Path::new(path).exists(); } diff --git a/resources/filesys/src/filecontent_helper.rs b/resources/filesys/src/filecontent_helper.rs index 764625be7..050aca5c8 100644 --- a/resources/filesys/src/filecontent_helper.rs +++ b/resources/filesys/src/filecontent_helper.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use crate::config::{FileContent, Encoding}; +use std::fs; use std::path::Path; use std::io; use std::io::Write; @@ -25,10 +26,9 @@ impl FileContent { /// /// * `string` - The string for the Path #[must_use] - pub fn new(path: &str, name: &str) -> FileContent { + pub fn new(path: &str) -> FileContent { FileContent { path: path.to_string(), - name: name.to_string(), content: None, hash: None, encoding: Some(Encoding::Utf8), @@ -64,6 +64,12 @@ pub fn set_file_content(filecontent: &FileContent) -> Result { + args::SubCommand::Get { input, schema_type } => { debug!("Getting at path: {}", input); - - match is_file_type(input.as_str()) { - Some(file) => { - let file = get_file(&file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match schema_type { + args::FileSystemObjectType::File => { + debug!("Getting file at path: {}", input); + match parse_file(input) { + Some(parsed_file) => { + let file = get_file(&parsed_file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file."); + exit(EXIT_INVALID_INPUT); + } + } } - None => { - match is_directory_type(input.as_str()) { - Some(dir) => { - let dir = get_dir(&dir).unwrap(); + args::FileSystemObjectType::Directory => { + debug!("Getting directory at path: {}", input); + match parse_directory(input) { + Some(parsed_directory) => { + let dir = get_dir(&parsed_directory).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - match is_filecontent_type(input.as_str()) { - Some(filecontent) => { - let filecontent = get_file_content(&filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); - } - None => { - error!("Invalid input."); - exit(EXIT_INVALID_INPUT); - } - }; - } - }; + error!("Invalid input for directory."); + exit(EXIT_INVALID_INPUT); + } + } + } + args::FileSystemObjectType::FileContent => { + debug!("Getting file content at path: {}", input); + match parse_filecontent(input) { + Some(parsed_filecontent) => { + let filecontent = get_file_content(&parsed_filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file content."); + exit(EXIT_INVALID_INPUT); + } + } } }; } - args::SubCommand::Delete { input } => { + + args::SubCommand::Delete { input, schema_type} => { debug!("Deleting file at path: {}", input); - match is_file_type(input.as_str()) { - Some(file) => { - let file = delete_file(&file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match schema_type { + args::FileSystemObjectType::File => { + debug!("Deleting file at path: {}", input); + match parse_file(input) { + Some(parsed_file) => { + let file = delete_file(&parsed_file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file."); + exit(EXIT_INVALID_INPUT); + } + } } - None => { - match is_directory_type(input.as_str()) { - Some(dir) => { - let dir = delete_dir(&dir).unwrap(); + args::FileSystemObjectType::Directory => { + debug!("Deleting directory at path: {}", input); + match parse_directory(input) { + Some(parsed_directory) => { + let dir = delete_dir(&parsed_directory).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - match is_filecontent_type(input.as_str()) { - Some(filecontent) => { - let filecontent = delete_file_content(&filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); - } - None => { - error!("Invalid input."); - exit(EXIT_INVALID_INPUT); - } - }; - } - }; + error!("Invalid input for directory."); + exit(EXIT_INVALID_INPUT); + } + } + } + args::FileSystemObjectType::FileContent => { + debug!("Deleting file content at path: {}", input); + match parse_filecontent(input) { + Some(parsed_filecontent) => { + let filecontent = delete_file_content(&parsed_filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file content."); + exit(EXIT_INVALID_INPUT); + } + } } }; } - args::SubCommand::Set { input } => { + args::SubCommand::Set { input, schema_type } => { debug!("Setting file at path: {}", input); - - match is_file_type(input.as_str()) { - Some(file) => { - let file = set_file(&file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); - debug!("File set successfully."); + match schema_type { + args::FileSystemObjectType::File => { + debug!("Setting file at path: {}", input); + match parse_file(input) { + Some(parsed_file) => { + let file = set_file(&parsed_file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file."); + exit(EXIT_INVALID_INPUT); + } + } } - None => { - match is_directory_type(input.as_str()) { - Some(dir) => { - let dir = set_dir(&dir).unwrap(); + args::FileSystemObjectType::Directory => { + debug!("Setting directory at path: {}", input); + match parse_directory(input) { + Some(parsed_directory) => { + let dir = set_dir(&parsed_directory).unwrap(); let json = serde_json::to_string(&dir).unwrap(); println!("{}", json); } None => { - match is_filecontent_type(input.as_str()) { - Some(filecontent) => { - let filecontent = set_file_content(&filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); - } - None => { - error!("Invalid input."); - exit(EXIT_INVALID_INPUT); - } - }; - } - }; + error!("Invalid input for directory."); + exit(EXIT_INVALID_INPUT); + } + } + } + args::FileSystemObjectType::FileContent => { + debug!("Setting file content at path: {}", input); + match parse_filecontent(input) { + Some(parsed_filecontent) => { + let filecontent = set_file_content(&parsed_filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file content."); + exit(EXIT_INVALID_INPUT); + } + } } }; } - args::SubCommand::Export { input } => { + args::SubCommand::Export { input, schema_type } => { debug!("Exporting file at path: {}", input); - match is_file_type(input.as_str()) { - Some(file) => { - let file = export_file_path(&file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); - debug!("File exported successfully."); + match schema_type { + args::FileSystemObjectType::File => { + debug!("Exporting file at path: {}", input); + match parse_file(input) { + Some(parsed_file) => { + let file = export_file_path(&parsed_file).unwrap(); + let json = serde_json::to_string(&file).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for file."); + exit(EXIT_INVALID_INPUT); + } + } + } + args::FileSystemObjectType::Directory => { + debug!("Exporting directory at path: {}", input); + match parse_directory(input) { + Some(parsed_directory) => { + let dir = export_dir_path(&parsed_directory).unwrap(); + let json = serde_json::to_string(&dir).unwrap(); + println!("{}", json); + } + None => { + error!("Invalid input for directory."); + exit(EXIT_INVALID_INPUT); + } + } } - None => { - match is_directory_type(input.as_str()) { - Some(dir) => { - let exported_dir = export_dir_path(&dir).unwrap(); - let json = serde_json::to_string(&exported_dir).unwrap(); + args::FileSystemObjectType::FileContent => { + debug!("Exporting file content at path: {}", input); + match parse_filecontent(input) { + Some(parsed_filecontent) => { + let filecontent = get_file_content(&parsed_filecontent).unwrap(); + let json = serde_json::to_string(&filecontent).unwrap(); println!("{}", json); - debug!("File exported successfully."); } None => { - match is_filecontent_type(input.as_str()) { - Some(filecontent) => { - let filecontent = get_file_content(&filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); - } - None => { - error!("Invalid input."); - exit(EXIT_INVALID_INPUT); - } - }; - } - }; + error!("Invalid input for file content."); + exit(EXIT_INVALID_INPUT); + } + } } }; } @@ -182,8 +238,8 @@ fn main() { exit(EXIT_SUCCESS); } -fn is_file_type(input: &str) -> Option { - let file: File = match serde_json::from_str(input) { +fn parse_file(input: String) -> Option { + let file: File = match serde_json::from_str(input.to_string().as_str()) { Ok(input) => input, Err(_) => return None, }; @@ -191,8 +247,8 @@ fn is_file_type(input: &str) -> Option { Some(file) } -fn is_directory_type(input: &str) -> Option { - let dir: Directory = match serde_json::from_str(input) { +fn parse_directory(input: String) -> Option { + let dir: Directory = match serde_json::from_str(input.to_string().as_str()) { Ok(input) => input, Err(_) => return None, }; @@ -200,8 +256,8 @@ fn is_directory_type(input: &str) -> Option { Some(dir) } -fn is_filecontent_type(input: &str) -> Option { - let filecontent: FileContent = match serde_json::from_str(input) { +fn parse_filecontent(input: String) -> Option { + let filecontent: FileContent = match serde_json::from_str(input.to_string().as_str()) { Ok(input) => input, Err(_) => return None, }; From 65503147619c8e62e5256ee5d898f9b8ffd88957 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 17 Mar 2025 15:11:35 -0700 Subject: [PATCH 14/14] Fixes and tests --- dsc/tests/filesys.tests.ps1 | 8 +- resources/filesys/src/main.rs | 246 ++++++++++++++++++++++++++++------ 2 files changed, 206 insertions(+), 48 deletions(-) diff --git a/dsc/tests/filesys.tests.ps1 b/dsc/tests/filesys.tests.ps1 index 2daae2a52..99c6bd75c 100644 --- a/dsc/tests/filesys.tests.ps1 +++ b/dsc/tests/filesys.tests.ps1 @@ -69,7 +69,7 @@ Describe 'FileSys resoure tests' { $resultDirPath | Should -Not -Exist } - It 'Filesys resource cannot delete a non-empty directory' -Pending { + It 'Filesys resource cannot delete a non-empty directory' { if (Test-Path $testDir) { Remove-Item -Path $testDir -Force -Recurse } @@ -78,10 +78,8 @@ Describe 'FileSys resoure tests' { New-Item -Path (Join-Path $testDir $testFileName) -ItemType File -Force | Out-Null $resultJson = dsc config set -f "$PSScriptRoot/../examples/filesys_dir_delete.dsc.yaml" | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $resultJson.hadErrors | Should -BeFalse - $resultDirPath = $resultJson.results.result.afterState.path - $resultDirPath | Should -Not -Exist + $LASTEXITCODE | Should -Not -Be 0 + $testDir | Should -Exist } It 'Filesys resource can delete a directory recursively' { diff --git a/resources/filesys/src/main.rs b/resources/filesys/src/main.rs index 0f14198ff..45c329b42 100644 --- a/resources/filesys/src/main.rs +++ b/resources/filesys/src/main.rs @@ -18,7 +18,8 @@ mod dir_helpers; mod filecontent_helper; const EXIT_SUCCESS: i32 = 0; -const EXIT_INVALID_INPUT: i32 = 2; +const EXIT_INVALID_INPUT: i32 = 1; +const EXIT_JSON_SERIALIZATION_FAILED: i32 = 2; fn main() { let args = Args::parse(); @@ -30,9 +31,21 @@ fn main() { debug!("Getting file at path: {}", input); match parse_file(input) { Some(parsed_file) => { - let file = get_file(&parsed_file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match get_file(&parsed_file) { + Ok(file) => { + match serde_json::to_string(&file) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } + } + Err(e) => { + error!("Failed to get file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file."); @@ -44,9 +57,21 @@ fn main() { debug!("Getting directory at path: {}", input); match parse_directory(input) { Some(parsed_directory) => { - let dir = get_dir(&parsed_directory).unwrap(); - let json = serde_json::to_string(&dir).unwrap(); - println!("{}", json); + match get_dir(&parsed_directory) { + Ok(dir) => { + match serde_json::to_string(&dir) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize directory: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } + } + Err(e) => { + error!("Failed to get directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for directory."); @@ -58,9 +83,21 @@ fn main() { debug!("Getting file content at path: {}", input); match parse_filecontent(input) { Some(parsed_filecontent) => { - let filecontent = get_file_content(&parsed_filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); + match get_file_content(&parsed_filecontent) { + Ok(filecontent) => { + match serde_json::to_string(&filecontent) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file content: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } + } + Err(e) => { + error!("Failed to get file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file content."); @@ -79,9 +116,21 @@ fn main() { debug!("Deleting file at path: {}", input); match parse_file(input) { Some(parsed_file) => { - let file = delete_file(&parsed_file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match delete_file(&parsed_file) { + Ok(file) => { + match serde_json::to_string(&file) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to delete file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file."); @@ -93,9 +142,21 @@ fn main() { debug!("Deleting directory at path: {}", input); match parse_directory(input) { Some(parsed_directory) => { - let dir = delete_dir(&parsed_directory).unwrap(); - let json = serde_json::to_string(&dir).unwrap(); - println!("{}", json); + match delete_dir(&parsed_directory) { + Ok(dir) => { + match serde_json::to_string(&dir) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to delete directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for directory."); @@ -107,9 +168,21 @@ fn main() { debug!("Deleting file content at path: {}", input); match parse_filecontent(input) { Some(parsed_filecontent) => { - let filecontent = delete_file_content(&parsed_filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); + match delete_file_content(&parsed_filecontent) { + Ok(filecontent) => { + match serde_json::to_string(&filecontent) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to delete file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file content."); @@ -126,9 +199,21 @@ fn main() { debug!("Setting file at path: {}", input); match parse_file(input) { Some(parsed_file) => { - let file = set_file(&parsed_file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match set_file(&parsed_file) { + Ok(file) => { + match serde_json::to_string(&file) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to set file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file."); @@ -140,9 +225,21 @@ fn main() { debug!("Setting directory at path: {}", input); match parse_directory(input) { Some(parsed_directory) => { - let dir = set_dir(&parsed_directory).unwrap(); - let json = serde_json::to_string(&dir).unwrap(); - println!("{}", json); + match set_dir(&parsed_directory) { + Ok(dir) => { + match serde_json::to_string(&dir) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to set directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for directory."); @@ -154,9 +251,21 @@ fn main() { debug!("Setting file content at path: {}", input); match parse_filecontent(input) { Some(parsed_filecontent) => { - let filecontent = set_file_content(&parsed_filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); + match set_file_content(&parsed_filecontent) { + Ok(filecontent) => { + match serde_json::to_string(&filecontent) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to set file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file content."); @@ -174,9 +283,21 @@ fn main() { debug!("Exporting file at path: {}", input); match parse_file(input) { Some(parsed_file) => { - let file = export_file_path(&parsed_file).unwrap(); - let json = serde_json::to_string(&file).unwrap(); - println!("{}", json); + match export_file_path(&parsed_file) { + Ok(file) => { + match serde_json::to_string(&file) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to export file: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file."); @@ -188,9 +309,21 @@ fn main() { debug!("Exporting directory at path: {}", input); match parse_directory(input) { Some(parsed_directory) => { - let dir = export_dir_path(&parsed_directory).unwrap(); - let json = serde_json::to_string(&dir).unwrap(); - println!("{}", json); + match export_dir_path(&parsed_directory) { + Ok(dir) => { + match serde_json::to_string(&dir) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to export directory: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for directory."); @@ -202,9 +335,21 @@ fn main() { debug!("Exporting file content at path: {}", input); match parse_filecontent(input) { Some(parsed_filecontent) => { - let filecontent = get_file_content(&parsed_filecontent).unwrap(); - let json = serde_json::to_string(&filecontent).unwrap(); - println!("{}", json); + match get_file_content(&parsed_filecontent) { + Ok(filecontent) => { + match serde_json::to_string(&filecontent) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } + } + Err(e) => { + error!("Failed to export file content: {}", e); + exit(EXIT_INVALID_INPUT); + } + } } None => { error!("Invalid input for file content."); @@ -218,18 +363,33 @@ fn main() { match schema_type { args::FileSystemObjectType::File => { let schema = schema_for!(File); - let json = serde_json::to_string(&schema).unwrap(); - println!("{}", json); + match serde_json::to_string(&schema) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file schema: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } } args::FileSystemObjectType::Directory => { let schema = schema_for!(Directory); - let json = serde_json::to_string(&schema).unwrap(); - println!("{}", json); + match serde_json::to_string(&schema){ + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize directory schema: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } } args::FileSystemObjectType::FileContent => { let schema = schema_for!(FileContent); - let json = serde_json::to_string(&schema).unwrap(); - println!("{}", json); + match serde_json::to_string(&schema) { + Ok(json) => println!("{}", json), + Err(e) => { + error!("Failed to serialize file content schema: {}", e); + exit(EXIT_JSON_SERIALIZATION_FAILED); + } + } } } }