|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT License. |
| 3 | +#include "pch.h" |
| 4 | +#include "DscPackageResource.h" |
| 5 | +#include "DscComposableObject.h" |
| 6 | +#include "Resources.h" |
| 7 | + |
| 8 | +using namespace AppInstaller::Utility::literals; |
| 9 | + |
| 10 | +namespace AppInstaller::CLI |
| 11 | +{ |
| 12 | + namespace |
| 13 | + { |
| 14 | + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The identifier of the package."); |
| 15 | + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, "The source of the package."); |
| 16 | + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", "The version of the package."); |
| 17 | + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", "The method for matching the identifier with a package.", ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); |
| 18 | + |
| 19 | + using PackageResourceObject = DscComposableObject<StandardExistProperty, StandardInDesiredStateProperty, IdProperty, SourceProperty>; |
| 20 | + |
| 21 | + struct PackageFunctionData |
| 22 | + { |
| 23 | + PackageFunctionData(const std::optional<Json::Value>& json) : Input(json), Output(Input.CopyForOutput()) |
| 24 | + { |
| 25 | + } |
| 26 | + |
| 27 | + PackageResourceObject Input; |
| 28 | + PackageResourceObject Output; |
| 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 | + DscPackageResource::DscPackageResource(std::string_view parent) : |
| 105 | + DscCommandBase(parent, "package", DscResourceKind::Resource, |
| 106 | + DscFunctions::Get | DscFunctions::Set | DscFunctions::WhatIf | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, |
| 107 | + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) |
| 108 | + { |
| 109 | + } |
| 110 | + |
| 111 | + Resource::LocString DscPackageResource::ShortDescription() const |
| 112 | + { |
| 113 | + return Resource::String::DscPackageResourceShortDescription; |
| 114 | + } |
| 115 | + |
| 116 | + Resource::LocString DscPackageResource::LongDescription() const |
| 117 | + { |
| 118 | + return Resource::String::DscPackageResourceLongDescription; |
| 119 | + } |
| 120 | + |
| 121 | + std::string DscPackageResource::ResourceType() const |
| 122 | + { |
| 123 | + return "Package"; |
| 124 | + } |
| 125 | + |
| 126 | + void DscPackageResource::ResourceFunctionGet(Execution::Context& context) const |
| 127 | + { |
| 128 | + if (auto json = GetJsonFromInput(context)) |
| 129 | + { |
| 130 | + anon::TestFileFunctionData data{ json }; |
| 131 | + |
| 132 | + data.Get(); |
| 133 | + |
| 134 | + WriteJsonOutputLine(context, data.Output.ToJson()); |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + void DscPackageResource::ResourceFunctionSet(Execution::Context& context) const |
| 139 | + { |
| 140 | + if (auto json = GetJsonFromInput(context)) |
| 141 | + { |
| 142 | + anon::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 DscPackageResource::ResourceFunctionTest(Execution::Context& context) const |
| 186 | + { |
| 187 | + if (auto json = GetJsonFromInput(context)) |
| 188 | + { |
| 189 | + anon::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 DscPackageResource::ResourceFunctionExport(Execution::Context& context) const |
| 200 | + { |
| 201 | + if (auto json = GetJsonFromInput(context)) |
| 202 | + { |
| 203 | + anon::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 | + anon::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 DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const |
| 233 | + { |
| 234 | + WriteJsonOutputLine(context, anon::TestFileObject::Schema(ResourceType())); |
| 235 | + } |
| 236 | +} |
0 commit comments