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 }