// <inheritdoc /> public async Task <SiteExtensionInfo> GetLocalExtension(string id, bool checkLatest = true) { ITracer tracer = _traceFactory.GetTracer(); UIPackageMetadata package = null; using (tracer.Step("Now querying from local repo for package '{0}'.", id)) { package = await _localRepository.GetLatestPackageByIdFromSrcRepo(id); } if (package == null) { tracer.Trace("No package found from local repo with id: {0}.", id); return(null); } SiteExtensionInfo info; using (tracer.Step("Converting NuGet object to SiteExtensionInfo")) { info = await ConvertLocalPackageToSiteExtensionInfo(package, checkLatest, tracer : tracer); } SiteExtensionStatus armSettings = new SiteExtensionStatus(_environment.SiteExtensionSettingsPath, id, tracer); armSettings.FillSiteExtensionInfo(info); return(info); }
private async Task TryCheckLocalPackageLatestVersionFromRemote(SiteExtensionInfo info, bool checkLatest, ITracer tracer = null) { if (checkLatest) { try { // FindPackage gets back the latest version. SourceRepository remoteRepo = GetRemoteRepository(info.FeedUrl); UIPackageMetadata latestPackage = await remoteRepo.GetLatestPackageByIdFromSrcRepo(info.Id); if (latestPackage != null) { NuGetVersion currentVersion = NuGetVersion.Parse(info.Version); info.LocalIsLatestVersion = NuGetVersion.Parse(info.Version).Equals(latestPackage.Identity.Version); info.DownloadCount = latestPackage.DownloadCount; info.PublishedDateTime = latestPackage.Published; } } catch (Exception ex) { if (tracer != null) { tracer.TraceError(ex); } } } }
public async Task <SiteExtensionInfo> GetRemoteExtension(string id, string version, string feedUrl) { ITracer tracer = _traceFactory.GetTracer(); SourceRepository remoteRepo = GetRemoteRepository(feedUrl); UIPackageMetadata package = null; if (string.IsNullOrWhiteSpace(version)) { using (tracer.Step("Version is null, search latest package by id: {0}", id)) { package = await remoteRepo.GetLatestPackageByIdFromSrcRepo(id); } } else { using (tracer.Step("Search package by id: {0} and version: {1}", id, version)) { package = await remoteRepo.GetPackageByIdentity(id, version); } } if (package == null) { tracer.Trace("No package found with id: {0} and version: {1}", id, version); return(null); } var metadataResource = await _localRepository.GetResourceAndValidateAsync <UIMetadataResource>(); return(await ConvertRemotePackageToSiteExtensionInfo(package, feedUrl, metadataResource)); }
/// <summary> /// <para> Return true if any of below cases is satisfied:</para> /// <para> 1) Package with same version and from same feed already exist in local repo</para> /// <para> 2) If given feedUrl is null</para> /// <para> Try to use feed from local package, if feed from local package also null, fallback to default feed</para> /// <para> Check if version from query is same as local package</para> /// <para> 3) If given version and feedUrl are null</para> /// <para> Try to use feed from local package, if feed from local package also null, fallback to default feed</para> /// <para> Check if version from query is same as local package</para> /// </summary> private async Task <bool> IsSiteExtensionInstalled(string id, string version, string feedUrl) { JsonSettings siteExtensionSettings = GetSettingManager(id); string localPackageVersion = siteExtensionSettings.GetValue(_versionSetting); string localPackageFeedUrl = siteExtensionSettings.GetValue(_feedUrlSetting); bool isInstalled = false; // Try to use given feed // If given feed is null, try with feed that from local package // And GetRemoteRepository will fallback to use default feed if pass in feed param is null SourceRepository remoteRepo = GetRemoteRepository(feedUrl ?? localPackageFeedUrl); // case 1 and 2 if (!string.IsNullOrWhiteSpace(version) && version.Equals(localPackageVersion, StringComparison.OrdinalIgnoreCase) && remoteRepo.PackageSource.Source.Equals(localPackageFeedUrl, StringComparison.OrdinalIgnoreCase)) { isInstalled = true; } else if (string.IsNullOrWhiteSpace(version) && string.IsNullOrWhiteSpace(feedUrl)) { // case 3 UIPackageMetadata remotePackage = await remoteRepo.GetLatestPackageByIdFromSrcRepo(id); if (remotePackage != null) { isInstalled = remotePackage.Identity.Version.ToNormalizedString().Equals(localPackageVersion, StringComparison.OrdinalIgnoreCase); } } return(isInstalled); }
/// <summary> /// <para> Return false if the installation arguments are different. Otherwise,</para> /// <para> Return true if any of below cases is satisfied:</para> /// <para> 1) Package with same version and from same feed already exist in local repo</para> /// <para> 2) If given feedUrl is null</para> /// <para> Try to use feed from local package, if feed from local package also null, fallback to default feed</para> /// <para> Check if version from query is same as local package</para> /// <para> 3) If given version and feedUrl are null</para> /// <para> Try to use feed from local package, if feed from local package also null, fallback to default feed</para> /// <para> Check if version from query is same as local package</para> /// </summary> private async Task <bool> IsSiteExtensionInstalled(string id, string version, string feedUrl, string installationArgs) { JsonSettings siteExtensionSettings = GetSettingManager(id); string localPackageVersion = siteExtensionSettings.GetValue(_versionSetting); string localPackageFeedUrl = siteExtensionSettings.GetValue(_feedUrlSetting); string localPackageInstallationArgs = siteExtensionSettings.GetValue(_installationArgs); bool isInstalled = false; // Shortcircuit check: if the installation arguments do not match, then we should return false here to avoid other checks which are now unnecessary. if (!string.Equals(localPackageInstallationArgs, installationArgs)) { return(false); } // Try to use given feed // If given feed is null, try with feed that from local package // And GetRemoteRepository will fallback to use default feed if pass in feed param is null SourceRepository remoteRepo = GetRemoteRepository(feedUrl ?? localPackageFeedUrl); // case 1 and 2 if (!string.IsNullOrWhiteSpace(version) && version.Equals(localPackageVersion, StringComparison.OrdinalIgnoreCase) && remoteRepo.PackageSource.Source.Equals(localPackageFeedUrl, StringComparison.OrdinalIgnoreCase)) { isInstalled = true; } else if (string.IsNullOrWhiteSpace(version) && string.IsNullOrWhiteSpace(feedUrl)) { // case 3 UIPackageMetadata remotePackage = await remoteRepo.GetLatestPackageByIdFromSrcRepo(id); if (remotePackage != null) { isInstalled = remotePackage.Identity.Version.ToNormalizedString().Equals(localPackageVersion, StringComparison.OrdinalIgnoreCase) && string.Equals(localPackageInstallationArgs, installationArgs); } } return(isInstalled); }
private async Task <SiteExtensionInfo> TryInstallExtension(string id, string version, string feedUrl, SiteExtensionInfo.SiteExtensionType type, ITracer tracer, string installationArgs) { SiteExtensionInfo info = null; HttpStatusCode status = HttpStatusCode.OK; // final status when success bool alreadyInstalled = false; try { // Check if site extension already installed (id, version, feedUrl), if already install with correct installation arguments then return right away if (await this.IsSiteExtensionInstalled(id, version, feedUrl, installationArgs)) { // package already installed, return package from local repo. tracer.Trace("Package {0} with version {1} from {2} with installation arguments '{3}' already installed.", id, version, feedUrl, installationArgs); info = await GetLocalExtension(id); alreadyInstalled = true; } else { JsonSettings siteExtensionSettings = GetSettingManager(id); feedUrl = (string.IsNullOrEmpty(feedUrl) ? siteExtensionSettings.GetValue(_feedUrlSetting) : feedUrl); SourceRepository remoteRepo = GetRemoteRepository(feedUrl); UIPackageMetadata localPackage = null; UIPackageMetadata repoPackage = null; if (this.IsInstalledToWebRoot(id)) { // override WebRoot type from setting // WebRoot is a special type that install package to wwwroot, when perform update we need to update new content to wwwroot even if type is not specified type = SiteExtensionInfo.SiteExtensionType.WebRoot; } if (string.IsNullOrWhiteSpace(version)) { using (tracer.Step("Version is null, search latest package by id: {0}, will not search for unlisted package.", id)) { repoPackage = await remoteRepo.GetLatestPackageByIdFromSrcRepo(id); } } else { using (tracer.Step("Search package by id: {0} and version: {1}, will also search for unlisted package.", id, version)) { repoPackage = await remoteRepo.GetPackageByIdentity(id, version); } } if (repoPackage != null) { using (tracer.Step("Install package: {0}.", id)) { string installationDirectory = GetInstallationDirectory(id); localPackage = await InstallExtension(repoPackage, installationDirectory, feedUrl, type, tracer, installationArgs); siteExtensionSettings.SetValues(new KeyValuePair <string, JToken>[] { new KeyValuePair <string, JToken>(_versionSetting, localPackage.Identity.Version.ToNormalizedString()), new KeyValuePair <string, JToken>(_feedUrlSetting, feedUrl), new KeyValuePair <string, JToken>(_installUtcTimestampSetting, DateTime.UtcNow.ToString("u")), new KeyValuePair <string, JToken>(_packageType, Enum.GetName(typeof(SiteExtensionInfo.SiteExtensionType), type)), new KeyValuePair <string, JToken>(_installationArgs, installationArgs) }); } } info = await ConvertLocalPackageToSiteExtensionInfo(localPackage, checkLatest : true, tracer : tracer); } } catch (FileNotFoundException ex) { _analytics.UnexpectedException( ex, method: "PUT", path: string.Format(CultureInfo.InvariantCulture, "/api/siteextensions/{0}", id), result: Constants.SiteExtensionProvisioningStateFailed, message: string.Format(CultureInfo.InvariantCulture, "{{\"version\": {0}, \"feed_url\": {1}}}", version, feedUrl), trace: false); tracer.TraceError(ex); info = new SiteExtensionInfo(); info.Id = id; info.ProvisioningState = Constants.SiteExtensionProvisioningStateFailed; info.Comment = ex.ToString(); status = HttpStatusCode.NotFound; } catch (WebException ex) { _analytics.UnexpectedException( ex, method: "PUT", path: string.Format(CultureInfo.InvariantCulture, "/api/siteextensions/{0}", id), result: Constants.SiteExtensionProvisioningStateFailed, message: string.Format(CultureInfo.InvariantCulture, "{{\"version\": {0}, \"feed_url\": {1}}}", version, feedUrl), trace: false); tracer.TraceError(ex); info = new SiteExtensionInfo(); info.Id = id; info.ProvisioningState = Constants.SiteExtensionProvisioningStateFailed; info.Comment = ex.ToString(); status = HttpStatusCode.BadRequest; } catch (InvalidEndpointException ex) { _analytics.UnexpectedException(ex, trace: false); tracer.TraceError(ex); info = new SiteExtensionInfo(); info.Id = id; info.ProvisioningState = Constants.SiteExtensionProvisioningStateFailed; info.Comment = ex.ToString(); status = HttpStatusCode.BadRequest; } catch (Exception ex) { _analytics.UnexpectedException( ex, method: "PUT", path: string.Format(CultureInfo.InvariantCulture, "/api/siteextensions/{0}", id), result: Constants.SiteExtensionProvisioningStateFailed, message: string.Format(CultureInfo.InvariantCulture, "{{\"version\": {0}, \"feed_url\": {1}}}", version, feedUrl), trace: false); tracer.TraceError(ex); info = new SiteExtensionInfo(); info.Id = id; info.ProvisioningState = Constants.SiteExtensionProvisioningStateFailed; info.Comment = ex.ToString(); status = HttpStatusCode.BadRequest; } if (info == null) { // treat this as an error case since no result return from repo _analytics.UnexpectedException( new FileNotFoundException(id), method: "PUT", path: string.Format(CultureInfo.InvariantCulture, "/api/siteextensions/{0}", id), result: Constants.SiteExtensionProvisioningStateFailed, message: string.Format(CultureInfo.InvariantCulture, "{{\"version\": {0}, \"feed_url\": {1}}}", version, feedUrl), trace: false); info = new SiteExtensionInfo(); info.ProvisioningState = Constants.SiteExtensionProvisioningStateFailed; info.Comment = string.Format(Constants.SiteExtensionProvisioningStateNotFoundMessageFormat, id); status = HttpStatusCode.NotFound; } else if (!string.Equals(Constants.SiteExtensionProvisioningStateFailed, info.ProvisioningState, StringComparison.OrdinalIgnoreCase)) { info.ProvisioningState = Constants.SiteExtensionProvisioningStateSucceeded; info.Comment = null; } using (tracer.Step("Update arm settings for {0} installation. Status: {1}", id, status)) { SiteExtensionStatus armSettings = new SiteExtensionStatus(_environment.SiteExtensionSettingsPath, id, tracer); armSettings.ReadSiteExtensionInfo(info); armSettings.Status = status; armSettings.Operation = alreadyInstalled ? null : Constants.SiteExtensionOperationInstall; } return(info); }
public static async Task UpdateLocalPackage(this SourceRepository srcRepo, SourceRepository localRepo, PackageIdentity identity, string destinationFolder, string pathToLocalCopyOfNupkg, ITracer tracer) { tracer.Trace("Performing incremental package update for {0}", identity.Id); using (Stream newPackageStream = await srcRepo.GetPackageStream(identity)) { // update file var localPackage = await localRepo.GetLatestPackageByIdFromSrcRepo(identity.Id); if (localPackage == null) { throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "Package {0} not found from local repo.", identity.Id)); } using (Stream oldPackageStream = await localRepo.GetPackageStream(localPackage.Identity)) using (ZipFile oldPackageZip = ZipFile.Read(oldPackageStream)) using (ZipFile newPackageZip = ZipFile.Read(newPackageStream)) { // we only care about stuff under "content" folder IEnumerable <ZipEntry> oldContentEntries = oldPackageZip.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); IEnumerable <ZipEntry> newContentEntries = newPackageZip.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); List <ZipEntry> filesNeedToUpdate = new List <ZipEntry>(); Dictionary <string, ZipEntry> indexedOldFiles = new Dictionary <string, ZipEntry>(); foreach (var item in oldContentEntries) { indexedOldFiles.Add(item.FileName.ToLowerInvariant(), item); } foreach (var newEntry in newContentEntries) { var fileName = newEntry.FileName.ToLowerInvariant(); if (indexedOldFiles.ContainsKey(fileName)) { // file name existed, only update if file has been touched ZipEntry oldEntry = indexedOldFiles[fileName]; if (oldEntry.LastModified != newEntry.LastModified) { filesNeedToUpdate.Add(newEntry); } // remove from old index files buffer, the rest will be files that need to be deleted indexedOldFiles.Remove(fileName); } else { // new files filesNeedToUpdate.Add(newEntry); } } int substringStartIndex = @"content/".Length; foreach (var entry in filesNeedToUpdate) { string fullPath = Path.Combine(destinationFolder, entry.FileName.Substring(substringStartIndex)); if (entry.IsDirectory) { using (tracer.Step("Ensure directory: {0}", fullPath)) { FileSystemHelpers.EnsureDirectory(fullPath.Replace('/', '\\')); } continue; } using (tracer.Step("Adding/Updating file: {0}", fullPath)) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(fullPath)); using (Stream writeStream = FileSystemHelpers.OpenWrite(fullPath)) { // reset length of file stream writeStream.SetLength(0); // let the thread go with itself, so that once file finishes writing, doesn't need to request thread context from main thread await entry.OpenReader().CopyToAsync(writeStream).ConfigureAwait(false); } } } foreach (var entry in indexedOldFiles.Values) { string fullPath = Path.Combine(destinationFolder, entry.FileName.Substring(substringStartIndex)); if (entry.IsDirectory) { // in case the two zip file was created from different tool. some tool will include folder as seperate entry, some don`t. // to be sure that foder is meant to be deleted, double check there is no files under it var entryNameInLower = entry.FileName.ToLower(); if (!string.Equals(destinationFolder, fullPath, StringComparison.OrdinalIgnoreCase) && newContentEntries.FirstOrDefault(e => e.FileName.ToLowerInvariant().StartsWith(entryNameInLower)) == null) { using (tracer.Step("Deleting directory: {0}", fullPath)) { FileSystemHelpers.DeleteDirectorySafe(fullPath); } } continue; } using (tracer.Step("Deleting file: {0}", fullPath)) { FileSystemHelpers.DeleteFileSafe(fullPath); } } } // update nupkg newPackageStream.Position = 0; using (tracer.Step("Updating nupkg file.")) { WriteStreamToFile(newPackageStream, pathToLocalCopyOfNupkg); if (!identity.Version.Equals(localPackage.Identity.Version)) { using (tracer.Step("New package has difference version {0} from old package {1}. Remove old nupkg file.", identity.Version, localPackage.Identity.Version)) { // if version is difference, nupkg file name will be difference. will need to clean up the old one. var oldNupkg = pathToLocalCopyOfNupkg.Replace( string.Format(CultureInfo.InvariantCulture, "{0}.{1}.nupkg", identity.Id, identity.Version.ToNormalizedString()), string.Format(CultureInfo.InvariantCulture, "{0}.{1}.nupkg", localPackage.Identity.Id, localPackage.Identity.Version.ToNormalizedString())); FileSystemHelpers.DeleteFileSafe(oldNupkg); } } } } }