winget-cli

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

DscUserSettingsFileResource.cpp (9704B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "DscUserSettingsFileResource.h"
      5 #include "DscComposableObject.h"
      6 #include "Resources.h"
      7 #include "AppInstallerStrings.h"
      8 
      9 using namespace AppInstaller::Utility::literals;
     10 using namespace AppInstaller::Settings;
     11 
     12 #define ACTION_FULL "Full"
     13 #define ACTION_PARTIAL "Partial"
     14 
     15 namespace AppInstaller::CLI
     16 {
     17     namespace
     18     {
     19         WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SettingsProperty, Json::Value, Settings, "settings", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionUserSettingsFileSettings);
     20         WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(ActionProperty, std::string, Action, "action", Resource::String::DscResourcePropertyDescriptionUserSettingsFileAction, ({ ACTION_PARTIAL, ACTION_FULL }), ACTION_PARTIAL);
     21 
     22         using UserSettingsFileResourceObject = DscComposableObject<StandardInDesiredStateProperty, SettingsProperty, ActionProperty>;
     23 
     24         struct UserSettingsFileFunctionData
     25         {
     26             UserSettingsFileFunctionData()
     27                 : UserSettingsFileFunctionData(std::nullopt, true)
     28             {
     29             }
     30 
     31             UserSettingsFileFunctionData(const std::optional<Json::Value>& json, bool ignoreFieldRequirements = false) :
     32                 Input(json, ignoreFieldRequirements),
     33                 _userSettingsPath(UserSettings::SettingsFilePath())
     34             {
     35             }
     36 
     37             const UserSettingsFileResourceObject Input;
     38             UserSettingsFileResourceObject Output;
     39 
     40             void Get()
     41             {
     42                 Output.Settings(GetUserSettings());
     43             }
     44 
     45             bool Test()
     46             {
     47                 return GetResolvedInput() == Output.Settings();
     48             }
     49 
     50             Json::Value DiffJson()
     51             {
     52                 Json::Value result{ Json::ValueType::arrayValue };
     53 
     54                 if (!Test())
     55                 {
     56                     result.append(std::string{ SettingsProperty::Name() });
     57                 }
     58 
     59                 return result;
     60             }
     61 
     62             const Json::Value& GetResolvedInput()
     63             {
     64                 THROW_HR_IF(E_UNEXPECTED, !Input.Settings().has_value());
     65                 if (!_resolvedInputUserSettings)
     66                 {
     67                     if(Input.Action().has_value() && Utility::CaseInsensitiveEquals(Input.Action().value(), ACTION_FULL))
     68                     {
     69                         Output.Action(ACTION_FULL);
     70                         _resolvedInputUserSettings = Input.Settings();
     71                     }
     72                     else
     73                     {
     74                         Output.Action(ACTION_PARTIAL);
     75                         _resolvedInputUserSettings = MergeUserSettingsFiles(*Input.Settings());
     76                     }
     77                 }
     78 
     79                 return *_resolvedInputUserSettings;
     80             }
     81 
     82             bool WriteOutput()
     83             {
     84                 THROW_HR_IF(E_UNEXPECTED, !Output.Settings().has_value());
     85                 std::ofstream file(_userSettingsPath, std::ios::binary);
     86                 if (file)
     87                 {
     88                     Json::StreamWriterBuilder writer;
     89                     writer["indentation"] = "  ";
     90                     file << Json::writeString(writer, *Output.Settings());
     91                     return true;
     92                 }
     93 
     94                 AICLI_LOG(Config, Error, << "Failed to open or create user settings file: " << _userSettingsPath);
     95                 return false;
     96             }
     97 
     98         private:
     99             std::filesystem::path _userSettingsPath;
    100             std::optional<Json::Value> _userSettings;
    101             std::optional<Json::Value> _resolvedInputUserSettings;
    102 
    103             Json::Value MergeUserSettingsFiles(const Json::Value& overlay)
    104             {
    105                 Json::Value mergedUserSettingsFile = GetUserSettings();
    106                 MergeUserSettingsFiles(mergedUserSettingsFile, overlay);
    107                 return mergedUserSettingsFile;
    108             }
    109 
    110             // Merges the overlay settings into the target settings.
    111             void MergeUserSettingsFiles(Json::Value& target, const Json::Value& overlay)
    112             {
    113                 // If either is not an object, we can't merge.
    114                 if (!overlay.isObject() || !target.isObject())
    115                 {
    116                     return;
    117                 }
    118 
    119                 // Iterate through the overlay settings and merge them into the target.
    120                 for (const auto& overlayKey : overlay.getMemberNames())
    121                 {
    122                     const Json::Value& overlayValue = overlay[overlayKey];
    123                     if (target.isMember(overlayKey))
    124                     {
    125                         Json::Value& targetValue = target[overlayKey];
    126                         if (targetValue.isObject() && overlayValue.isObject())
    127                         {
    128                             // Recursively merge the objects.
    129                             MergeUserSettingsFiles(targetValue, overlayValue);
    130                         }
    131                         else
    132                         {
    133                             // Replace the value in the target.
    134                             // Note: Arrays are not merged, they are replaced.
    135                             target[overlayKey] = overlayValue;
    136                         }
    137                     }
    138                     else
    139                     {
    140                         // Add the overlay key to the target.
    141                         target[overlayKey] = overlayValue;
    142                     }
    143                 }
    144             }
    145 
    146             const Json::Value& GetUserSettings()
    147             {
    148                 if (!_userSettings)
    149                 {
    150                     _userSettings = Json::objectValue;
    151                     std::ifstream file(_userSettingsPath, std::ios::binary);
    152                     if (file)
    153                     {
    154                         Json::CharReaderBuilder builder;
    155                         std::string errs;
    156                         Json::Value jsonRoot;
    157                         if (Json::parseFromStream(builder, file, &jsonRoot, &errs))
    158                         {
    159                             _userSettings = jsonRoot;
    160                         }
    161                         else
    162                         {
    163                             AICLI_LOG(Config, Warning, << "Failed to parse user settings file: " << _userSettingsPath << ", error: " << errs);
    164                         }
    165                     }
    166                     else
    167                     {
    168                         AICLI_LOG(Config, Warning, << "Failed to open user settings file: " << _userSettingsPath);
    169                     }
    170                 }
    171 
    172                 return *_userSettings;
    173             }
    174         };
    175     }
    176 
    177     DscUserSettingsFileResource::DscUserSettingsFileResource(std::string_view parent) :
    178         DscCommandBase(parent, "user-settings-file", DscResourceKind::Resource,
    179             DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema,
    180             DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff)
    181     {
    182     }
    183 
    184     Resource::LocString DscUserSettingsFileResource::ShortDescription() const
    185     {
    186         return Resource::String::DscUserSettingsFileShortDescription;
    187     }
    188 
    189     Resource::LocString DscUserSettingsFileResource::LongDescription() const
    190     {
    191         return Resource::String::DscUserSettingsFileLongDescription;
    192     }
    193 
    194     std::string DscUserSettingsFileResource::ResourceType() const
    195     {
    196         return "UserSettingsFile";
    197     }
    198 
    199     void DscUserSettingsFileResource::ResourceFunctionGet(Execution::Context& context) const
    200     {
    201         ResourceFunctionExport(context);
    202     }
    203 
    204     void DscUserSettingsFileResource::ResourceFunctionSet(Execution::Context& context) const
    205     {
    206         if (auto json = GetJsonFromInput(context))
    207         {
    208             UserSettingsFileFunctionData data{ json };
    209 
    210             data.Get();
    211 
    212             // Capture the diff before updating the output
    213             auto diff = data.DiffJson();
    214 
    215             if (!data.Test())
    216             {
    217                 data.Output.Settings(data.GetResolvedInput());
    218                 if (!data.WriteOutput())
    219                 {
    220                     AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED));
    221                     return;
    222                 }
    223             }
    224 
    225             WriteJsonOutputLine(context, data.Output.ToJson());
    226             WriteJsonOutputLine(context, diff);
    227         }
    228     }
    229 
    230     void DscUserSettingsFileResource::ResourceFunctionTest(Execution::Context& context) const
    231     {
    232         if (auto json = GetJsonFromInput(context))
    233         {
    234             UserSettingsFileFunctionData data{ json };
    235 
    236             data.Get();
    237             data.Output.InDesiredState(data.Test());
    238 
    239             WriteJsonOutputLine(context, data.Output.ToJson());
    240             WriteJsonOutputLine(context, data.DiffJson());
    241         }
    242     }
    243 
    244     void DscUserSettingsFileResource::ResourceFunctionExport(Execution::Context& context) const
    245     {
    246         UserSettingsFileFunctionData data;
    247 
    248         data.Get();
    249 
    250         WriteJsonOutputLine(context, data.Output.ToJson());
    251     }
    252 
    253     void DscUserSettingsFileResource::ResourceFunctionSchema(Execution::Context& context) const
    254     {
    255         WriteJsonOutputLine(context, UserSettingsFileResourceObject::Schema(ResourceType()));
    256     }
    257 }