DscTestFileResource.cpp (8402B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "DscTestFileResource.h" 5 #include "DscComposableObject.h" 6 7 using namespace AppInstaller::Utility::literals; 8 9 namespace AppInstaller::CLI 10 { 11 namespace 12 { 13 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PathProperty, std::string, Path, "path", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The absolute path to a file."_lis); 14 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ContentProperty, std::string, Content, "content", "The content of the file."_lis); 15 16 using TestFileObject = DscComposableObject<StandardExistProperty, StandardInDesiredStateProperty, PathProperty, ContentProperty>; 17 18 struct TestFileFunctionData 19 { 20 TestFileFunctionData(const std::optional<Json::Value>& json) : Input(json), Output(Input.CopyForOutput()) 21 { 22 Path = Utility::ConvertToUTF16(Input.Path().value()); 23 THROW_HR_IF(E_INVALIDARG, !Path.is_absolute()); 24 } 25 26 TestFileObject Input; 27 TestFileObject Output; 28 std::filesystem::path Path; 29 30 // Fills the Output object with the current state 31 void Get() 32 { 33 if (std::filesystem::exists(Path) && std::filesystem::is_regular_file(Path)) 34 { 35 Output.Exist(true); 36 37 std::ifstream stream{ Path, std::ios::binary }; 38 Output.Content(Utility::ReadEntireStream(stream)); 39 } 40 else 41 { 42 Output.Exist(false); 43 } 44 } 45 46 // Determines if the current Output values match the Input values state. 47 bool Test() 48 { 49 // Need to populate Output before calling 50 THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); 51 52 if (Input.ShouldExist()) 53 { 54 if (Output.Exist().value()) 55 { 56 return ContentMatches(); 57 } 58 else 59 { 60 return false; 61 } 62 } 63 else 64 { 65 return !Output.Exist().value(); 66 } 67 } 68 69 Json::Value DiffJson() 70 { 71 // Need to populate Output before calling 72 THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); 73 74 Json::Value result{ Json::ValueType::arrayValue }; 75 76 if (Input.ShouldExist() != Output.Exist().value()) 77 { 78 result.append(std::string{ StandardExistProperty::Name() }); 79 } 80 else 81 { 82 if (!ContentMatches()) 83 { 84 result.append(std::string{ ContentProperty::Name() }); 85 } 86 } 87 88 return result; 89 } 90 91 private: 92 bool ContentMatches() 93 { 94 bool hasInput = Input.Content().has_value() && !Input.Content().value().empty(); 95 bool hasOutput = Output.Content().has_value() && !Output.Content().value().empty(); 96 97 return 98 (hasInput && hasOutput && Input.Content().value() == Output.Content().value()) || 99 (!hasInput && !hasOutput); 100 } 101 }; 102 } 103 104 DscTestFileResource::DscTestFileResource(std::string_view parent) : 105 DscCommandBase(parent, "test-file", DscResourceKind::Resource, 106 DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, 107 DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) 108 { 109 } 110 111 Resource::LocString DscTestFileResource::ShortDescription() const 112 { 113 return "[TEST] File content resource"_lis; 114 } 115 116 Resource::LocString DscTestFileResource::LongDescription() const 117 { 118 return "[TEST] This resource is only available for tests. It provides file content configuration."_lis; 119 } 120 121 std::string DscTestFileResource::ResourceType() const 122 { 123 return "TestFile"; 124 } 125 126 void DscTestFileResource::ResourceFunctionGet(Execution::Context& context) const 127 { 128 if (auto json = GetJsonFromInput(context)) 129 { 130 TestFileFunctionData data{ json }; 131 132 data.Get(); 133 134 WriteJsonOutputLine(context, data.Output.ToJson()); 135 } 136 } 137 138 void DscTestFileResource::ResourceFunctionSet(Execution::Context& context) const 139 { 140 if (auto json = GetJsonFromInput(context)) 141 { 142 TestFileFunctionData data{ json }; 143 144 data.Get(); 145 146 if (!data.Test()) 147 { 148 bool exists = std::filesystem::exists(data.Path); 149 if (exists) 150 { 151 // Don't delete a directory or other special files in this test resource 152 THROW_WIN32_IF(ERROR_DIRECTORY_NOT_SUPPORTED, !std::filesystem::is_regular_file(data.Path)); 153 } 154 155 if (data.Input.ShouldExist()) 156 { 157 std::filesystem::create_directories(data.Path.parent_path()); 158 159 std::ofstream stream{ data.Path, std::ios::binary | std::ios::trunc }; 160 if (data.Input.Content()) 161 { 162 stream.write(data.Input.Content().value().c_str(), data.Input.Content().value().length()); 163 } 164 } 165 else if (exists) 166 { 167 std::filesystem::remove(data.Path); 168 } 169 } 170 171 // Capture the diff before updating the output 172 auto diff = data.DiffJson(); 173 174 data.Output.Exist(data.Input.ShouldExist()); 175 if (data.Output.Exist().value()) 176 { 177 data.Output.Content(data.Input.Content().value_or("")); 178 } 179 180 WriteJsonOutputLine(context, data.Output.ToJson()); 181 WriteJsonOutputLine(context, diff); 182 } 183 } 184 185 void DscTestFileResource::ResourceFunctionTest(Execution::Context& context) const 186 { 187 if (auto json = GetJsonFromInput(context)) 188 { 189 TestFileFunctionData data{ json }; 190 191 data.Get(); 192 data.Output.InDesiredState(data.Test()); 193 194 WriteJsonOutputLine(context, data.Output.ToJson()); 195 WriteJsonOutputLine(context, data.DiffJson()); 196 } 197 } 198 199 void DscTestFileResource::ResourceFunctionExport(Execution::Context& context) const 200 { 201 if (auto json = GetJsonFromInput(context)) 202 { 203 TestFileFunctionData data{ json }; 204 205 if (std::filesystem::exists(data.Path)) 206 { 207 if (std::filesystem::is_regular_file(data.Path)) 208 { 209 data.Get(); 210 WriteJsonOutputLine(context, data.Output.ToJson()); 211 } 212 else if (std::filesystem::is_directory(data.Path)) 213 { 214 for (const auto& file : std::filesystem::directory_iterator{ data.Path }) 215 { 216 if (std::filesystem::is_regular_file(file)) 217 { 218 TestFileObject output; 219 output.Path(file.path().u8string()); 220 221 std::ifstream stream{ file.path(), std::ios::binary}; 222 output.Content(Utility::ReadEntireStream(stream)); 223 224 WriteJsonOutputLine(context, output.ToJson()); 225 } 226 } 227 } 228 } 229 } 230 } 231 232 void DscTestFileResource::ResourceFunctionSchema(Execution::Context& context) const 233 { 234 WriteJsonOutputLine(context, TestFileObject::Schema(ResourceType())); 235 } 236 }