Skip to content

Commit 6e092a6

Browse files
Added dscv3 user-settings resource (microsoft#5421)
1 parent a31f773 commit 6e092a6

File tree

9 files changed

+647
-2
lines changed

9 files changed

+647
-2
lines changed

src/AppInstallerCLICore/AppInstallerCLICore.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@
309309
<ClInclude Include="Commands\DscSourceResource.h" />
310310
<ClInclude Include="Commands\DscTestFileResource.h" />
311311
<ClInclude Include="Commands\DscTestJsonResource.h" />
312+
<ClInclude Include="Commands\DscUserSettingsFileResource.h" />
312313
<ClInclude Include="Commands\ErrorCommand.h" />
313314
<ClInclude Include="Commands\ExperimentalCommand.h" />
314315
<ClInclude Include="Commands\ExportCommand.h" />
@@ -396,6 +397,7 @@
396397
<ClCompile Include="Commands\DscSourceResource.cpp" />
397398
<ClCompile Include="Commands\DscTestFileResource.cpp" />
398399
<ClCompile Include="Commands\DscTestJsonResource.cpp" />
400+
<ClCompile Include="Commands\DscUserSettingsFileResource.cpp" />
399401
<ClCompile Include="Commands\ErrorCommand.cpp" />
400402
<ClCompile Include="Commands\FontCommand.cpp" />
401403
<ClCompile Include="Commands\ImportCommand.cpp" />

src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@
287287
<ClInclude Include="Commands\DscSourceResource.h">
288288
<Filter>Commands\Configuration</Filter>
289289
</ClInclude>
290+
<ClInclude Include="Commands\DscUserSettingsFileResource.h">
291+
<Filter>Commands\Configuration</Filter>
292+
</ClInclude>
290293
</ItemGroup>
291294
<ItemGroup>
292295
<ClCompile Include="pch.cpp">
@@ -541,6 +544,9 @@
541544
<ClCompile Include="Commands\DscSourceResource.cpp">
542545
<Filter>Commands\Configuration</Filter>
543546
</ClCompile>
547+
<ClCompile Include="Commands\DscUserSettingsFileResource.cpp">
548+
<Filter>Commands\Configuration</Filter>
549+
</ClCompile>
544550
</ItemGroup>
545551
<ItemGroup>
546552
<None Include="PropertySheet.props" />

src/AppInstallerCLICore/Commands/DscCommand.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "pch.h"
44
#include "DscCommand.h"
55
#include "DscPackageResource.h"
6+
#include "DscUserSettingsFileResource.h"
67
#include "DscSourceResource.h"
78

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

src/AppInstallerCLICore/Resources.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ namespace AppInstaller::CLI::Resource
236236
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption);
237237
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest);
238238
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode);
239+
WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileShortDescription);
240+
WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileLongDescription);
241+
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileSettings);
242+
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileAction);
239243
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName);
240244
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument);
241245
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType);

src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ namespace AppInstallerCLIE2ETests
1010
using System.Text.Json.Serialization;
1111
using AppInstallerCLIE2ETests.Helpers;
1212
using NUnit.Framework;
13-
using WinRT;
1413

1514
/// <summary>
1615
/// `Configure` command tests.

0 commit comments

Comments
 (0)