SourceFlow.cpp (15247B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Resources.h" 5 #include "SourceFlow.h" 6 #include "PromptFlow.h" 7 #include "TableOutput.h" 8 #include "WorkflowBase.h" 9 10 namespace AppInstaller::CLI::Workflow 11 { 12 using namespace AppInstaller::CLI::Execution; 13 using namespace AppInstaller::Settings; 14 using namespace AppInstaller::Utility::literals; 15 16 void GetSourceList(Execution::Context& context) 17 { 18 context.Add<Execution::Data::SourceList>(Repository::Source::GetCurrentSources()); 19 } 20 21 void GetSourceListWithFilter(Execution::Context& context) 22 { 23 auto currentSources = Repository::Source::GetCurrentSources(); 24 if (context.Args.Contains(Args::Type::SourceName)) 25 { 26 auto name = Utility::LocIndString{ context.Args.GetArg(Args::Type::SourceName) }; 27 28 for (auto const& source : currentSources) 29 { 30 if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) 31 { 32 std::vector<Repository::SourceDetails> sources; 33 sources.emplace_back(source); 34 context.Add<Execution::Data::SourceList>(std::move(sources)); 35 return; 36 } 37 } 38 39 context.Reporter.Error() << Resource::String::SourceListNoneFound(name) << std::endl; 40 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); 41 } 42 else 43 { 44 context.Add<Execution::Data::SourceList>(std::move(currentSources)); 45 } 46 } 47 48 void CheckSourceListAgainstAdd(Execution::Context& context) 49 { 50 auto sourceList = context.Get<Execution::Data::SourceList>(); 51 std::string_view name = context.Args.GetArg(Args::Type::SourceName); 52 std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); 53 std::string_view type = context.Args.GetArg(Args::Type::SourceType); 54 55 // In the absence of a specified type, the default is Microsoft.PreIndexed.Package for comparison. 56 // The default type assignment to the source takes place during the add operation (Source::Add in Repository.cpp). 57 // This is necessary for the comparison to function correctly; otherwise, it would allow the addition of multiple 58 // sources with different names but the same argument for all default type cases. 59 // For example, the following commands would be allowed, but they acts as different alias to same source: 60 // winget source add "mysource1" "https:\\mysource" --trust - level trusted 61 // winget source add "mysource2" "https:\\mysource" --trust - level trusted 62 if (type.empty()) 63 { 64 type = Repository::Source::GetDefaultSourceType(); 65 } 66 67 for (const auto& details : sourceList) 68 { 69 if (Utility::ICUCaseInsensitiveEquals(details.Name, name)) 70 { 71 if (details.Arg == arg) 72 { 73 // Name and arg match, indicate this to the user and bail. 74 context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << 75 " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; 76 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); 77 } 78 else 79 { 80 context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << 81 " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; 82 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); 83 } 84 } 85 86 if (!details.Arg.empty() && details.Arg == arg && details.Type == type) 87 { 88 context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentName << std::endl << 89 " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; 90 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS); 91 } 92 } 93 } 94 95 void AddSource(Execution::Context& context) 96 { 97 auto& sourceToAdd = context.Get<Execution::Data::Source>(); 98 auto details = sourceToAdd.GetDetails(); 99 100 context.Reporter.Info() << 101 Resource::String::SourceAddBegin << std::endl << 102 " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; 103 104 auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; 105 if (!context.Reporter.ExecuteWithProgress(addFunction)) 106 { 107 context.Reporter.Info() << Resource::String::Cancelled << std::endl; 108 } 109 else 110 { 111 context.Reporter.Info() << Resource::String::Done << std::endl; 112 } 113 } 114 115 void CreateSourceForSourceAdd(Execution::Context& context) 116 { 117 try 118 { 119 std::string_view name = context.Args.GetArg(Args::Type::SourceName); 120 std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); 121 std::string_view type = context.Args.GetArg(Args::Type::SourceType); 122 bool isExplicit = context.Args.Contains(Args::Type::SourceExplicit); 123 124 Repository::SourceTrustLevel trustLevel = Repository::SourceTrustLevel::None; 125 if (context.Args.Contains(Execution::Args::Type::SourceTrustLevel)) 126 { 127 std::vector<std::string> trustLevelArgs = Utility::Split(std::string{ context.Args.GetArg(Execution::Args::Type::SourceTrustLevel) }, '|', true); 128 trustLevel = Repository::ConvertToSourceTrustLevelFlag(trustLevelArgs); 129 } 130 131 Repository::Source sourceToAdd{ name, arg, type, trustLevel, isExplicit}; 132 133 if (context.Args.Contains(Execution::Args::Type::CustomHeader)) 134 { 135 std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; 136 if (!sourceToAdd.SetCustomHeader(customHeader)) 137 { 138 context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; 139 } 140 } 141 142 if (sourceToAdd.GetInformation().Authentication.Type == Authentication::AuthenticationType::Unknown) 143 { 144 context.Reporter.Error() << Resource::String::SourceAddFailedAuthenticationNotSupported << std::endl; 145 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); 146 } 147 148 context << Workflow::HandleSourceAgreements(sourceToAdd); 149 if (context.IsTerminated()) 150 { 151 return; 152 } 153 154 context.Add<Execution::Data::Source>(std::move(sourceToAdd)); 155 } 156 catch (...) 157 { 158 context.Reporter.Error() << Resource::String::SourceAddOpenSourceFailed << std::endl; 159 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED); 160 } 161 } 162 163 void ListSources(Execution::Context& context) 164 { 165 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 166 167 if (context.Args.Contains(Args::Type::SourceName)) 168 { 169 // If a source name was specified, list full details of the one and only source. 170 const Repository::SourceDetails& source = sources[0]; 171 172 Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); 173 174 table.OutputLine({ Resource::LocString(Resource::String::SourceListName), source.Name }); 175 table.OutputLine({ Resource::LocString(Resource::String::SourceListType), source.Type }); 176 table.OutputLine({ Resource::LocString(Resource::String::SourceListArg), source.Arg }); 177 table.OutputLine({ Resource::LocString(Resource::String::SourceListData), source.Data }); 178 table.OutputLine({ Resource::LocString(Resource::String::SourceListIdentifier), source.Identifier }); 179 table.OutputLine({ Resource::LocString(Resource::String::SourceListTrustLevel), Repository::GetSourceTrustLevelForDisplay(source.TrustLevel)}); 180 table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(source.Explicit) }}); 181 182 if (source.LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0)) 183 { 184 table.OutputLine({ 185 Resource::LocString(Resource::String::SourceListUpdated), 186 Resource::LocString(Resource::String::SourceListUpdatedNever) 187 }); 188 } 189 else 190 { 191 std::ostringstream strstr; 192 strstr << source.LastUpdateTime; 193 table.OutputLine({ Resource::LocString(Resource::String::SourceListUpdated), strstr.str() }); 194 } 195 196 table.Complete(); 197 } 198 else 199 { 200 if (sources.empty()) 201 { 202 context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; 203 } 204 else 205 { 206 Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg, Resource::String::SourceListExplicit }); 207 for (const auto& source : sources) 208 { 209 table.OutputLine({ source.Name, source.Arg, std::string{ Utility::ConvertBoolToString(source.Explicit) }}); 210 } 211 table.Complete(); 212 } 213 } 214 } 215 216 void UpdateSources(Execution::Context& context) 217 { 218 if (!context.Args.Contains(Args::Type::SourceName)) 219 { 220 context.Reporter.Info() << Resource::String::SourceUpdateAll << std::endl; 221 } 222 223 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 224 225 for (const auto& sd : sources) 226 { 227 Repository::Source source{ sd.Name }; 228 context.Reporter.Info() << Resource::String::SourceUpdateOne(Utility::LocIndView{ sd.Name }) << std::endl; 229 auto updateFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails> { return source.Update(progress); }; 230 auto sourceDetails = context.Reporter.ExecuteWithProgress(updateFunction); 231 if (!sourceDetails.empty()) 232 { 233 if (std::chrono::system_clock::now() < sourceDetails[0].DoNotUpdateBefore) 234 { 235 context.Reporter.Warn() << Resource::String::Unavailable << std::endl; 236 } 237 else 238 { 239 context.Reporter.Info() << Resource::String::Cancelled << std::endl; 240 } 241 } 242 else 243 { 244 context.Reporter.Info() << Resource::String::Done << std::endl; 245 } 246 } 247 } 248 249 void RemoveSources(Execution::Context& context) 250 { 251 // TODO: We currently only allow removing a single source. If that changes, 252 // we need to check all sources with the Group Policy before removing any of them. 253 if (!context.Args.Contains(Args::Type::SourceName)) 254 { 255 context.Reporter.Info() << Resource::String::SourceRemoveAll << std::endl; 256 } 257 258 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 259 for (const auto& sd : sources) 260 { 261 Repository::Source source{ sd.Name }; 262 context.Reporter.Info() << Resource::String::SourceRemoveOne(Utility::LocIndView{ sd.Name }) << std::endl; 263 auto removeFunction = [&](IProgressCallback& progress)->bool { return source.Remove(progress); }; 264 if (context.Reporter.ExecuteWithProgress(removeFunction)) 265 { 266 context.Reporter.Info() << Resource::String::Done << std::endl; 267 } 268 else 269 { 270 context.Reporter.Info() << Resource::String::Cancelled << std::endl; 271 } 272 } 273 } 274 275 void QueryUserForSourceReset(Execution::Context& context) 276 { 277 if (!context.Args.Contains(Execution::Args::Type::ForceSourceReset)) 278 { 279 context << GetSourceListWithFilter; 280 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 281 282 if (!sources.empty()) 283 { 284 context.Reporter.Info() << Resource::String::SourceResetListAndOverridePreamble << std::endl; 285 286 context << ListSources; 287 AICLI_TERMINATE_CONTEXT(E_ABORT); 288 } 289 } 290 } 291 292 void ResetSourceList(Execution::Context& context) 293 { 294 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 295 296 for (const auto& source : sources) 297 { 298 context.Reporter.Info() << Resource::String::SourceResetOne(Utility::LocIndView{ source.Name }); 299 Repository::Source::DropSource(source.Name); 300 context.Reporter.Info() << Resource::String::Done << std::endl; 301 } 302 } 303 304 void ResetAllSources(Execution::Context& context) 305 { 306 context.Reporter.Info() << Resource::String::SourceResetAll; 307 Repository::Source::DropSource({}); 308 context.Reporter.Info() << Resource::String::Done << std::endl; 309 } 310 311 void ExportSourceList(Execution::Context& context) 312 { 313 const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); 314 315 if (sources.empty()) 316 { 317 context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; 318 } 319 else 320 { 321 for (const auto& source : sources) 322 { 323 SourceFromPolicy s; 324 s.Name = source.Name; 325 s.Type = source.Type; 326 s.Arg = source.Arg; 327 s.Data = source.Data; 328 s.Identifier = source.Identifier; 329 330 std::vector<std::string_view> sourceTrustLevels = Repository::SourceTrustLevelFlagToList(source.TrustLevel); 331 s.TrustLevel = std::vector<std::string>(sourceTrustLevels.begin(), sourceTrustLevels.end()); 332 s.Explicit = source.Explicit; 333 context.Reporter.Info() << s.ToJsonString() << std::endl; 334 } 335 } 336 } 337 338 void ForceInstalledCacheUpdate(Execution::Context&) 339 { 340 // Creating this object is currently sufficient to mark the cache as needing an update for the next time it is opened. 341 Repository::Source ignore{ Repository::PredefinedSource::InstalledForceCacheUpdate }; 342 } 343 }