DscSourceResource.cpp (15911B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "DscSourceResource.h" 5 #include "DscComposableObject.h" 6 #include "Resources.h" 7 #include "Workflows/SourceFlow.h" 8 #include <winget/RepositorySource.h> 9 10 using namespace AppInstaller::Utility::literals; 11 using namespace AppInstaller::Repository; 12 13 namespace AppInstaller::CLI 14 { 15 namespace 16 { 17 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(NameProperty, std::string, SourceName, "name", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionSourceName); 18 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ArgumentProperty, std::string, Argument, "argument", Resource::String::DscResourcePropertyDescriptionSourceArgument); 19 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(TypeProperty, std::string, Type, "type", Resource::String::DscResourcePropertyDescriptionSourceType); 20 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(TrustLevelProperty, std::string, TrustLevel, "trustLevel", Resource::String::DscResourcePropertyDescriptionSourceTrustLevel, ({ "undefined", "none", "trusted" }), "undefined"); 21 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ExplicitProperty, bool, Explicit, "explicit", Resource::String::DscResourcePropertyDescriptionSourceExplicit); 22 WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements); 23 24 using SourceResourceObject = DscComposableObject<StandardExistProperty, StandardInDesiredStateProperty, NameProperty, ArgumentProperty, TypeProperty, TrustLevelProperty, ExplicitProperty, AcceptAgreementsProperty>; 25 26 std::string TrustLevelStringFromFlags(SourceTrustLevel trustLevel) 27 { 28 return WI_IsFlagSet(trustLevel, SourceTrustLevel::Trusted) ? "trusted" : "none"; 29 } 30 31 // The values as the resource uses them. 32 enum class ResourceTrustLevel 33 { 34 Undefined, 35 Invalid, 36 None, 37 Trusted 38 }; 39 40 ResourceTrustLevel EffectiveTrustLevel(const std::optional<std::string>& input) 41 { 42 if (!input) 43 { 44 return ResourceTrustLevel::Undefined; 45 } 46 47 std::string inputValue = Utility::ToLower(input.value()); 48 if (inputValue == "undefined") 49 { 50 return ResourceTrustLevel::Undefined; 51 } 52 else if (inputValue == "none") 53 { 54 return ResourceTrustLevel::None; 55 } 56 else if (inputValue == "trusted") 57 { 58 return ResourceTrustLevel::Trusted; 59 } 60 else 61 { 62 return ResourceTrustLevel::Invalid; 63 } 64 } 65 66 struct SourceFunctionData 67 { 68 SourceFunctionData(Execution::Context& context, const std::optional<Json::Value>& json, bool ignoreFieldRequirements = false) : 69 Input(json, ignoreFieldRequirements), 70 ParentContext(context) 71 { 72 Reset(); 73 } 74 75 const SourceResourceObject Input; 76 SourceResourceObject Output; 77 Execution::Context& ParentContext; 78 std::unique_ptr<Execution::Context> SubContext; 79 80 // Reset the state that is modified by Get 81 void Reset() 82 { 83 Output = Input.CopyForOutput(); 84 85 SubContext = ParentContext.CreateSubContext(); 86 SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); 87 88 if (Input.AcceptAgreements().value_or(false)) 89 { 90 SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); 91 } 92 } 93 94 // Fills the Output object with the current state 95 void Get() 96 { 97 auto currentSources = Repository::Source::GetCurrentSources(); 98 const std::string& name = Input.SourceName().value(); 99 100 Output.Exist(false); 101 102 for (auto const& source : currentSources) 103 { 104 if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) 105 { 106 Output.Exist(true); 107 Output.Argument(source.Arg); 108 Output.Type(source.Type); 109 Output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); 110 Output.Explicit(source.Explicit); 111 112 std::vector<Repository::SourceDetails> sources; 113 sources.emplace_back(source); 114 SubContext->Add<Execution::Data::SourceList>(std::move(sources)); 115 break; 116 } 117 } 118 119 AICLI_LOG(CLI, Verbose, << "Source::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); 120 } 121 122 void Add() 123 { 124 AICLI_LOG(CLI, Verbose, << "Source::Add invoked"); 125 126 if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) 127 { 128 SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); 129 } 130 131 THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !Input.Argument().has_value()); 132 SubContext->Args.AddArg(Execution::Args::Type::SourceArg, Input.Argument().value()); 133 134 if (Input.Type()) 135 { 136 SubContext->Args.AddArg(Execution::Args::Type::SourceType, Input.Type().value()); 137 } 138 139 ResourceTrustLevel effectiveTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); 140 THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, effectiveTrustLevel == ResourceTrustLevel::Invalid); 141 if (effectiveTrustLevel == ResourceTrustLevel::Trusted) 142 { 143 SubContext->Args.AddArg(Execution::Args::Type::SourceTrustLevel, TrustLevelStringFromFlags(SourceTrustLevel::Trusted)); 144 } 145 146 if (Input.Explicit().value_or(false)) 147 { 148 SubContext->Args.AddArg(Execution::Args::Type::SourceExplicit); 149 } 150 151 *SubContext << 152 Workflow::EnsureRunningAsAdmin << 153 Workflow::CreateSourceForSourceAdd << 154 Workflow::AddSource; 155 } 156 157 void Remove() 158 { 159 AICLI_LOG(CLI, Verbose, << "Source::Remove invoked"); 160 161 if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) 162 { 163 SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); 164 } 165 166 *SubContext << 167 Workflow::EnsureRunningAsAdmin << 168 Workflow::RemoveSources; 169 } 170 171 void Replace() 172 { 173 AICLI_LOG(CLI, Verbose, << "Source::Replace invoked"); 174 Remove(); 175 Add(); 176 } 177 178 // Determines if the current Output values match the Input values state. 179 bool Test() 180 { 181 // Need to populate Output before calling 182 THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); 183 184 if (Input.ShouldExist()) 185 { 186 if (Output.Exist().value()) 187 { 188 AICLI_LOG(CLI, Verbose, << "Source::Test needed to inspect these properties: Argument(" << TestArgument() << "), Type(" << TestType() << "), TrustLevel(" << TestTrustLevel() << "), Explicit(" << TestExplicit() << ")"); 189 return TestArgument() && TestType() && TestTrustLevel() && TestExplicit(); 190 } 191 else 192 { 193 AICLI_LOG(CLI, Verbose, << "Source::Test was false because the source is not present"); 194 return false; 195 } 196 } 197 else 198 { 199 AICLI_LOG(CLI, Verbose, << "Source::Test desired the source to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); 200 return !Output.Exist().value(); 201 } 202 } 203 204 Json::Value DiffJson() 205 { 206 // Need to populate Output before calling 207 THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); 208 209 Json::Value result{ Json::ValueType::arrayValue }; 210 211 if (Input.ShouldExist() != Output.Exist().value()) 212 { 213 result.append(std::string{ StandardExistProperty::Name() }); 214 } 215 else 216 { 217 if (!TestArgument()) 218 { 219 result.append(std::string{ ArgumentProperty::Name() }); 220 } 221 222 if (!TestType()) 223 { 224 result.append(std::string{ TypeProperty::Name() }); 225 } 226 227 if (!TestTrustLevel()) 228 { 229 result.append(std::string{ TrustLevelProperty::Name() }); 230 } 231 232 if (!TestExplicit()) 233 { 234 result.append(std::string{ ExplicitProperty::Name() }); 235 } 236 } 237 238 return result; 239 } 240 241 bool TestArgument() 242 { 243 if (Input.Argument()) 244 { 245 if (Output.Argument()) 246 { 247 return Input.Argument().value() == Output.Argument().value(); 248 } 249 else 250 { 251 return false; 252 } 253 } 254 else 255 { 256 return true; 257 } 258 } 259 260 bool TestType() 261 { 262 if (Input.Type()) 263 { 264 if (Output.Type()) 265 { 266 return Utility::CaseInsensitiveEquals(Input.Type().value(), Output.Type().value()); 267 } 268 else 269 { 270 return false; 271 } 272 } 273 else 274 { 275 return true; 276 } 277 } 278 279 bool TestTrustLevel() 280 { 281 auto inputTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); 282 283 if (inputTrustLevel != ResourceTrustLevel::Undefined) 284 { 285 return inputTrustLevel == EffectiveTrustLevel(Output.TrustLevel()); 286 } 287 else 288 { 289 return true; 290 } 291 } 292 293 bool TestExplicit() 294 { 295 if (Input.Explicit()) 296 { 297 if (Output.Explicit()) 298 { 299 return Input.Explicit().value() == Output.Explicit().value(); 300 } 301 else 302 { 303 return false; 304 } 305 } 306 else 307 { 308 return true; 309 } 310 } 311 }; 312 } 313 314 DscSourceResource::DscSourceResource(std::string_view parent) : 315 DscCommandBase(parent, "source", DscResourceKind::Resource, 316 DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, 317 DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) 318 { 319 } 320 321 Resource::LocString DscSourceResource::ShortDescription() const 322 { 323 return Resource::String::DscSourceResourceShortDescription; 324 } 325 326 Resource::LocString DscSourceResource::LongDescription() const 327 { 328 return Resource::String::DscSourceResourceLongDescription; 329 } 330 331 std::string DscSourceResource::ResourceType() const 332 { 333 return "Source"; 334 } 335 336 void DscSourceResource::ResourceFunctionGet(Execution::Context& context) const 337 { 338 if (auto json = GetJsonFromInput(context)) 339 { 340 SourceFunctionData data{ context, json }; 341 342 data.Get(); 343 344 WriteJsonOutputLine(context, data.Output.ToJson()); 345 } 346 } 347 348 void DscSourceResource::ResourceFunctionSet(Execution::Context& context) const 349 { 350 if (auto json = GetJsonFromInput(context)) 351 { 352 SourceFunctionData data{ context, json }; 353 354 data.Get(); 355 356 // Capture the diff before updating the output 357 auto diff = data.DiffJson(); 358 359 if (!data.Test()) 360 { 361 if (data.Input.ShouldExist()) 362 { 363 if (data.Output.Exist().value()) 364 { 365 AICLI_LOG(CLI, Info, << "Replacing source with new information"); 366 data.Replace(); 367 } 368 else 369 { 370 AICLI_LOG(CLI, Info, << "Adding source as it was not found"); 371 data.Add(); 372 } 373 } 374 else 375 { 376 AICLI_LOG(CLI, Info, << "Removing source as desired"); 377 data.Remove(); 378 } 379 380 if (data.SubContext->IsTerminated()) 381 { 382 data.ParentContext.Terminate(data.SubContext->GetTerminationHR()); 383 return; 384 } 385 386 data.Reset(); 387 data.Get(); 388 } 389 390 WriteJsonOutputLine(context, data.Output.ToJson()); 391 WriteJsonOutputLine(context, diff); 392 } 393 } 394 395 void DscSourceResource::ResourceFunctionTest(Execution::Context& context) const 396 { 397 if (auto json = GetJsonFromInput(context)) 398 { 399 SourceFunctionData data{ context, json }; 400 401 data.Get(); 402 data.Output.InDesiredState(data.Test()); 403 404 WriteJsonOutputLine(context, data.Output.ToJson()); 405 WriteJsonOutputLine(context, data.DiffJson()); 406 } 407 } 408 409 void DscSourceResource::ResourceFunctionExport(Execution::Context& context) const 410 { 411 auto currentSources = Repository::Source::GetCurrentSources(); 412 413 for (auto const& source : currentSources) 414 { 415 SourceResourceObject output; 416 output.SourceName(source.Name); 417 output.Argument(source.Arg); 418 output.Type(source.Type); 419 output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); 420 output.Explicit(source.Explicit); 421 WriteJsonOutputLine(context, output.ToJson()); 422 } 423 } 424 425 void DscSourceResource::ResourceFunctionSchema(Execution::Context& context) const 426 { 427 WriteJsonOutputLine(context, SourceResourceObject::Schema(ResourceType())); 428 } 429 }