winget-cli

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

DscCommandBase.cpp (11178B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "DscCommandBase.h"
      5 #include "DscCommand.h"
      6 #include <winget/Runtime.h>
      7 #include <winget/StdErrLogger.h>
      8 
      9 #define WINGET_DSC_FUNCTION_FOREACH(_macro_) \
     10     _macro_(Get); \
     11     _macro_(Set); \
     12     _macro_(WhatIf); \
     13     _macro_(Test); \
     14     _macro_(Delete); \
     15     _macro_(Export); \
     16     _macro_(Validate); \
     17     _macro_(Resolve); \
     18     _macro_(Adapter); \
     19     _macro_(Schema); \
     20 
     21 namespace AppInstaller::CLI
     22 {
     23     namespace
     24     {
     25         std::string GetFunctionManifestString(DscFunctions function)
     26         {
     27             THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function));
     28 
     29             switch (function)
     30             {
     31             case DscFunctions::Get: return "get";
     32             case DscFunctions::Set: return "set";
     33             case DscFunctions::WhatIf: return "whatIf";
     34             case DscFunctions::Test: return "test";
     35             case DscFunctions::Delete: return "delete";
     36             case DscFunctions::Export: return "export";
     37             case DscFunctions::Validate: return "validate";
     38             case DscFunctions::Resolve: return "resolve";
     39             case DscFunctions::Adapter: return "adapter";
     40             case DscFunctions::Schema: return "schema";
     41             }
     42 
     43             THROW_HR(E_NOTIMPL);
     44         }
     45 
     46         std::string GetFunctionArgumentString(DscFunctions function)
     47         {
     48             return std::string{ "--" } + GetFunctionManifestString(function);
     49         }
     50 
     51         bool FunctionSpecifiesInput(DscFunctions function)
     52         {
     53             switch (function)
     54             {
     55             case DscFunctions::Get:
     56             case DscFunctions::Set:
     57             case DscFunctions::WhatIf:
     58             case DscFunctions::Test:
     59             case DscFunctions::Delete:
     60             case DscFunctions::Export:
     61             case DscFunctions::Validate:
     62             case DscFunctions::Resolve:
     63                 return true;
     64             }
     65 
     66             return false;
     67         }
     68 
     69         bool FunctionIsSetLike(DscFunctions function)
     70         {
     71             switch (function)
     72             {
     73             case DscFunctions::Set:
     74             case DscFunctions::WhatIf:
     75                 return true;
     76             }
     77 
     78             return false;
     79         }
     80 
     81         bool FunctionSpecifiesReturn(DscFunctions function)
     82         {
     83             switch (function)
     84             {
     85             case DscFunctions::Set:
     86             case DscFunctions::WhatIf:
     87             case DscFunctions::Test:
     88                 return true;
     89             }
     90 
     91             return false;
     92         }
     93 
     94         std::optional<std::string> GetReturnType(DscFunctionModifiers modifiers)
     95         {
     96             if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsStateAndDiff))
     97             {
     98                 return "stateAndDiff";
     99             }
    100 
    101             if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsState))
    102             {
    103                 return "state";
    104             }
    105 
    106             return std::nullopt;
    107         }
    108 
    109         Json::Value CreateJsonDefinitionFor(std::string_view name, DscFunctions function, DscFunctionModifiers modifiers)
    110         {
    111             THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function));
    112             THROW_HR_IF(E_NOTIMPL, function == DscFunctions::Adapter);
    113 
    114             Json::Value result{ Json::ValueType::objectValue };
    115 
    116 #ifndef USE_PROD_CLSIDS
    117             result["executable"] = "wingetdev";
    118 #else
    119             result["executable"] = "winget";
    120 #endif
    121 
    122             Json::Value args{ Json::ValueType::arrayValue };
    123             args.append(std::string{ DscCommand::StaticName() });
    124             args.append(std::string{ name });
    125             args.append(GetFunctionArgumentString(function));
    126             result["args"] = std::move(args);
    127 
    128             if (FunctionSpecifiesInput(function))
    129             {
    130                 result["input"] = "stdin";
    131             }
    132 
    133             if (FunctionIsSetLike(function))
    134             {
    135                 if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ImplementsPretest))
    136                 {
    137                     result["implementsPretest"] = true;
    138                 }
    139 
    140                 if (WI_IsFlagSet(modifiers, DscFunctionModifiers::HandlesExist))
    141                 {
    142                     result["handlesExist"] = true;
    143                 }
    144             }
    145 
    146             if (FunctionSpecifiesReturn(function))
    147             {
    148                 std::optional<std::string> returnType = GetReturnType(modifiers);
    149 
    150                 if (returnType)
    151                 {
    152                     result["return"] = returnType.value();
    153                 }
    154             }
    155 
    156             if (function == DscFunctions::Schema)
    157             {
    158                 Json::Value newResult{ Json::ValueType::objectValue };
    159                 newResult["command"] = std::move(result);
    160                 result = std::move(newResult);
    161             }
    162 
    163             return result;
    164         }
    165     }
    166 
    167     DscCommandBase::DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers) :
    168         Command(resourceName, parent, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers)
    169     {
    170         // Limits on current implementation
    171         THROW_HR_IF(E_NOTIMPL, kind != DscResourceKind::Resource);
    172         THROW_HR_IF(E_NOTIMPL, WI_IsFlagSet(functions, DscFunctions::Adapter));
    173     }
    174 
    175     std::vector<Argument> DscCommandBase::GetArguments() const
    176     {
    177         std::vector<Argument> result;
    178 
    179 #define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \
    180         if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \
    181         { \
    182             result.emplace_back(Execution::Args::Type::DscResourceFunction ## _function_, Resource::String::DscResourceFunctionDescription ## _function_, ArgumentType::Flag); \
    183         }
    184 
    185         WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT);
    186 
    187 #undef WINGET_DSC_FUNCTION_ARGUMENT
    188 
    189         result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag);
    190         result.emplace_back(Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard);
    191 
    192         return result;
    193     }
    194 
    195     Utility::LocIndView DscCommandBase::HelpLink() const
    196     {
    197         return "https://aka.ms/winget-dsc-resources"_liv;
    198     }
    199 
    200     void DscCommandBase::ExecuteInternal(Execution::Context& context) const
    201     {
    202         context.Reporter.SetChannel(Execution::Reporter::Channel::Json);
    203         Logging::StdErrLogger::Add();
    204 
    205 #define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \
    206         if (context.Args.Contains(Execution::Args::Type::DscResourceFunction ## _function_)) \
    207         { \
    208             return ResourceFunction ## _function_(context); \
    209         }
    210 
    211         WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT);
    212         WINGET_DSC_FUNCTION_ARGUMENT(Manifest);
    213 
    214 #undef WINGET_DSC_FUNCTION_ARGUMENT
    215     }
    216 
    217 #define WINGET_DSC_FUNCTION_METHOD(_function_) \
    218     void DscCommandBase::ResourceFunction ## _function_(Execution::Context&) const \
    219     { \
    220         THROW_HR(E_NOTIMPL); \
    221     } \
    222 
    223     WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_METHOD);
    224 
    225     void DscCommandBase::WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const
    226     {
    227         Json::Value json{ Json::ValueType::objectValue };
    228 
    229         // TODO: Move to release schema when released (there should be an aka.ms link as well, but it wasn't active yet)
    230         //json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/resource/manifest.json";
    231         json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json";
    232         json["type"] = std::string{ ModuleName() } + '/' + ResourceType();
    233         json["description"] = LongDescription().get();
    234         json["version"] = Runtime::GetClientVersion().get();
    235 
    236         Json::Value tags{ Json::ValueType::arrayValue };
    237         tags.append("WinGet");
    238         json["tags"] = std::move(tags);
    239 
    240 #define WINGET_DSC_FUNCTION_MANIFEST(_function_) \
    241         if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \
    242         { \
    243             json[GetFunctionManifestString(DscFunctions::_function_)] = CreateJsonDefinitionFor(Name(), DscFunctions::_function_, m_modifiers); \
    244         }
    245 
    246         WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_MANIFEST);
    247 
    248 #undef WINGET_DSC_FUNCTION_MANIFEST
    249 
    250         Json::StreamWriterBuilder writerBuilder;
    251         writerBuilder.settings_["indentation"] = "  ";
    252         std::string jsonString = Json::writeString(writerBuilder, json);
    253 
    254         if (!filePath.empty())
    255         {
    256             std::ofstream stream{ filePath, std::ios::binary };
    257             stream.write(jsonString.c_str(), jsonString.length());
    258         }
    259         else
    260         {
    261             context.Reporter.Json() << jsonString;
    262         }
    263     }
    264 
    265     void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const
    266     {
    267         std::filesystem::path path;
    268         if (context.Args.Contains(Execution::Args::Type::OutputFile))
    269         {
    270             path = std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) };
    271         }
    272         WriteManifest(context, path);
    273     }
    274 
    275 #undef WINGET_DSC_FUNCTION_METHOD
    276 
    277     std::optional<Json::Value> DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const
    278     {
    279         // Don't attempt to read from an interactive stream as this will just block
    280         if (!context.Reporter.InputStreamIsInteractive())
    281         {
    282             AICLI_LOG(CLI, Verbose, << "Reading Json from input stream...");
    283 
    284             Json::Value result;
    285             Json::CharReaderBuilder builder;
    286             Json::String errors;
    287             if (Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors))
    288             {
    289                 AICLI_LOG(CLI, Info, << "Json from input stream:\n" << Json::writeString(Json::StreamWriterBuilder{}, result));
    290                 return result;
    291             }
    292 
    293             AICLI_LOG(CLI, Error, << "Failed to read input JSON: " << errors);
    294         }
    295 
    296         if (terminateContextOnError)
    297         {
    298             AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, std::nullopt);
    299         }
    300         else
    301         {
    302             return std::nullopt;
    303         }
    304     }
    305 
    306     void DscCommandBase::WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const
    307     {
    308         Json::StreamWriterBuilder writerBuilder;
    309         writerBuilder.settings_["indentation"] = "";
    310         writerBuilder.settings_["commentStyle"] = "None";
    311         writerBuilder.settings_["emitUTF8"] = true;
    312         context.Reporter.Json() << Json::writeString(writerBuilder, value) << std::endl;
    313     }
    314 }