winget-cli

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

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 }