winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }