PinFlow.cpp (13443B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Resources.h" 5 #include "PinFlow.h" 6 #include "TableOutput.h" 7 #include <winget/PinningData.h> 8 #include <winget/RepositorySearch.h> 9 #include <winget/PackageVersionSelection.h> 10 11 using namespace AppInstaller::Repository; 12 13 namespace AppInstaller::CLI::Workflow 14 { 15 namespace 16 { 17 // Creates a Pin appropriate for the context based on the arguments provided 18 Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) 19 { 20 if (context.Args.Contains(Execution::Args::Type::GatedVersion)) 21 { 22 return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion)); 23 } 24 else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) 25 { 26 return Pinning::Pin::CreateBlockingPin(pinKey); 27 } 28 else 29 { 30 return Pinning::Pin::CreatePinningPin(pinKey); 31 } 32 } 33 34 void GetPinKeysForInstalled(const std::shared_ptr<IPackageVersion>& installedVersion, std::set<Pinning::PinKey>& pinKeys) 35 { 36 auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); 37 std::vector<Utility::LocIndString> propertyStrings; 38 39 if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType)) 40 { 41 propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); 42 } 43 else if (Manifest::DoesInstallerTypeUseProductCode(installedType)) 44 { 45 propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); 46 } 47 48 for (const auto& value : propertyStrings) 49 { 50 pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value)); 51 } 52 } 53 54 std::set<Pinning::PinKey> GetPinKeysForPackage(Execution::Context& context) 55 { 56 auto package = context.Get<Execution::Data::Package>(); 57 58 std::set<Pinning::PinKey> pinKeys; 59 60 if (context.Args.Contains(Execution::Args::Type::PinInstalled)) 61 { 62 auto installedVersion = GetInstalledVersion(package); 63 if (installedVersion) 64 { 65 GetPinKeysForInstalled(installedVersion, pinKeys); 66 } 67 } 68 else 69 { 70 auto availablePackages = package->GetAvailable(); 71 for (const auto& availablePackage : availablePackages) 72 { 73 pinKeys.emplace( 74 availablePackage->GetProperty(PackageProperty::Id).get(), 75 availablePackage->GetSource().GetIdentifier()); 76 } 77 } 78 79 return pinKeys; 80 } 81 82 // Gets a search request that can be used to find the installed package that corresponds with a pin. 83 SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey) 84 { 85 SearchRequest searchRequest; 86 if (pinKey.IsForInstalled()) 87 { 88 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId)); 89 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId)); 90 } 91 else 92 { 93 searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId); 94 } 95 96 return searchRequest; 97 } 98 } 99 100 void OpenPinningIndex::operator()(Execution::Context& context) const 101 { 102 auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite }; 103 if (!m_readOnly && !pinningData) 104 { 105 AICLI_LOG(CLI, Error, << "Unable to open pinning index."); 106 context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; 107 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); 108 } 109 110 context.Add<Execution::Data::PinningData>(std::move(pinningData)); 111 } 112 113 void GetAllPins(Execution::Context& context) 114 { 115 AICLI_LOG(CLI, Info, << "Getting all existing pins"); 116 context.Add<Execution::Data::Pins>(context.Get<Execution::Data::PinningData>().GetAllPins()); 117 } 118 119 void SearchPin(Execution::Context& context) 120 { 121 auto pinKeys = GetPinKeysForPackage(context); 122 123 auto package = context.Get<Execution::Data::Package>(); 124 auto pinningData = context.Get<Execution::Data::PinningData>(); 125 126 std::vector<Pinning::Pin> pins; 127 for (const auto& pinKey : pinKeys) 128 { 129 auto pin = pinningData.GetPin(pinKey); 130 if (pin) 131 { 132 pins.emplace_back(std::move(pin.value())); 133 } 134 } 135 136 context.Add<Execution::Data::Pins>(std::move(pins)); 137 } 138 139 void AddPin(Execution::Context& context) 140 { 141 auto pinKeys = GetPinKeysForPackage(context); 142 143 auto package = context.Get<Execution::Data::Package>(); 144 auto pinningData = context.Get<Execution::Data::PinningData>(); 145 auto installedVersion = context.Get<Execution::Data::InstalledPackageVersion>(); 146 147 std::vector<Pinning::Pin> pinsToAddOrUpdate; 148 for (const auto& pinKey : pinKeys) 149 { 150 auto pin = CreatePin(context, pinKey); 151 AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString()); 152 153 auto existingPin = pinningData.GetPin(pinKey); 154 if (existingPin) 155 { 156 Utility::LocIndString packageNameToReport; 157 if (pinKey.IsForInstalled() && installedVersion) 158 { 159 packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name); 160 } 161 else 162 { 163 auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); 164 if (availableVersion) 165 { 166 packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); 167 } 168 } 169 170 // Pin already exists. 171 // If it is the same, we do nothing. If it is different, check for the --force arg 172 if (pin == existingPin) 173 { 174 AICLI_LOG(CLI, Info, << "Pin already exists"); 175 context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl; 176 continue; 177 } 178 179 AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); 180 if (context.Args.Contains(Execution::Args::Type::Force)) 181 { 182 AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); 183 context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl; 184 pinsToAddOrUpdate.push_back(std::move(pin)); 185 } 186 else 187 { 188 context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl; 189 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); 190 } 191 } 192 else 193 { 194 pinsToAddOrUpdate.push_back(std::move(pin)); 195 } 196 } 197 198 if (!pinsToAddOrUpdate.empty()) 199 { 200 for (const auto& pin : pinsToAddOrUpdate) 201 202 { 203 pinningData.AddOrUpdatePin(pin); 204 } 205 206 context.Reporter.Info() << Resource::String::PinAdded << std::endl; 207 } 208 } 209 210 void RemovePin(Execution::Context& context) 211 { 212 auto package = context.Get<Execution::Data::Package>(); 213 auto pins = context.Get<Execution::Data::Pins>(); 214 215 auto pinningData = context.Get<Execution::Data::PinningData>(); 216 bool pinExists = false; 217 218 // Note that if a source was specified in the command line, 219 // that will be the only one we get version keys from. 220 // So, we remove pins from all sources unless one was provided. 221 for (const auto& pin : pins) 222 { 223 AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); 224 pinningData.RemovePin(pin.GetKey()); 225 pinExists = true; 226 } 227 228 if (!pinExists) 229 { 230 AICLI_LOG(CLI, Warning, << "Pin does not exist"); 231 context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; 232 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); 233 } 234 235 context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; 236 } 237 238 void ReportPins(Execution::Context& context) 239 { 240 const auto& pins = context.Get<Execution::Data::Pins>(); 241 if (pins.empty()) 242 { 243 context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; 244 return; 245 } 246 247 Execution::TableOutput<6> table(context.Reporter, 248 { 249 Resource::String::SearchName, 250 Resource::String::SearchId, 251 Resource::String::SearchVersion, 252 Resource::String::SearchSource, 253 Resource::String::PinType, 254 Resource::String::PinVersion, 255 }); 256 257 const auto& source = context.Get<Execution::Data::Source>(); 258 for (const auto& pin : pins) 259 { 260 const auto& pinKey = pin.GetKey(); 261 auto searchRequest = GetSearchRequestForPin(pin.GetKey()); 262 auto searchResult = source.Search(searchRequest); 263 for (const auto& match : searchResult.Matches) 264 { 265 Utility::LocIndString packageName; 266 Utility::LocIndString sourceName; 267 Utility::LocIndString version; 268 269 if (pinKey.IsForInstalled()) 270 { 271 sourceName = Resource::LocString{ Resource::String::PinInstalledSource }; 272 } 273 else 274 { 275 // This ensures we get the info from the right source if it exists on multiple 276 auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); 277 if (availablePackage) 278 { 279 auto availableVersion = availablePackage->GetLatestVersion(); 280 if (availableVersion) 281 { 282 packageName = availableVersion->GetProperty(PackageVersionProperty::Name); 283 sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); 284 } 285 } 286 } 287 288 auto installedVersion = GetInstalledVersion(match.Package); 289 if (installedVersion) 290 { 291 packageName = installedVersion->GetProperty(PackageVersionProperty::Name); 292 version = installedVersion->GetProperty(PackageVersionProperty::Version); 293 } 294 295 table.OutputLine({ 296 packageName, 297 pinKey.PackageId, 298 version, 299 sourceName, 300 std::string{ ToString(pin.GetType()) }, 301 pin.GetGatedVersion().ToString(), 302 }); 303 } 304 } 305 306 table.Complete(); 307 } 308 309 void ResetAllPins(Execution::Context& context) 310 { 311 AICLI_LOG(CLI, Info, << "Resetting all pins"); 312 context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; 313 314 std::string sourceId; 315 if (context.Args.Contains(Execution::Args::Type::Source)) 316 { 317 auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); 318 auto sources = Source::GetCurrentSources(); 319 for (const auto& source : sources) 320 { 321 if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) 322 { 323 sourceId = source.Identifier; 324 break; 325 } 326 } 327 } 328 329 if (context.Get<Execution::Data::PinningData>().ResetAllPins(sourceId)) 330 { 331 context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; 332 } 333 else 334 { 335 context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; 336 } 337 } 338 }