private void VerifyOrSetDefaultStorageDirectoryPath(string desireStorageDirectoryPath) { if (string.IsNullOrEmpty(desireStorageDirectoryPath)) { string storageDir = Path.Combine(Utilities.EnsureCoreToolsLocalData(), ".telemetry"); FileSystemHelpers.EnsureDirectory(storageDir); _storageDirectoryPath = Path.GetFullPath(storageDir); } else { if (!Path.IsPathRooted(desireStorageDirectoryPath)) { throw new ArgumentException($"{nameof(desireStorageDirectoryPath)} need to be rooted (full path)"); } _storageDirectoryPath = desireStorageDirectoryPath; } }
protected void NotifyShutdownJob() { try { if (_shutdownNotificationFilePath != null) { OperationManager.Attempt(() => { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(_shutdownNotificationFilePath)); FileSystemHelpers.WriteAllText(_shutdownNotificationFilePath, DateTime.UtcNow.ToString()); }); } } catch (Exception ex) { _analytics.UnexpectedException(ex); } }
protected void AddOryxBuildCommand(StringBuilder args, DeploymentContext context, string source, string destination) { // If it is express build, we don't directly need to write to /home/site/wwwroot // So, we build into a different directory to avoid overlap // Additionally, we didn't run kudusync, and can just build directly from repository path if (Flags == BuildOptimizationsFlags.UseExpressBuild) { source = context.RepositoryPath; destination = OryxBuildConstants.FunctionAppBuildSettings.ExpressBuildSetup; // It is important to clean and recreate the directory to make sure no overwrite occurs if (FileSystemHelpers.DirectoryExists(destination)) { FileSystemHelpers.DeleteDirectorySafe(destination); } FileSystemHelpers.EnsureDirectory(destination); } OryxArgumentsHelper.AddOryxBuildCommand(args, source, destination); }
public override async Task RunAsync() { if (SourceControl != SourceControl.Git) { throw new Exception("Only Git is supported right now for vsc"); } if (!string.IsNullOrEmpty(FolderName)) { var folderPath = Path.Combine(Environment.CurrentDirectory, FolderName); FileSystemHelpers.EnsureDirectory(folderPath); Environment.CurrentDirectory = folderPath; } WorkerRuntime workerRuntime; if (string.IsNullOrEmpty(WorkerRuntime)) { ColoredConsole.Write("Select a worker runtime: "); workerRuntime = SelectionMenuHelper.DisplaySelectionWizard(WorkerRuntimeLanguageHelper.AvailableWorkersList); ColoredConsole.WriteLine(TitleColor(workerRuntime.ToString())); } else { workerRuntime = WorkerRuntimeLanguageHelper.NormalizeWorkerRuntime(WorkerRuntime); } if (workerRuntime == Helpers.WorkerRuntime.dotnet) { await DotnetHelpers.DeployDotnetProject(Path.GetFileName(Environment.CurrentDirectory), Force); } else { await InitLanguageSpecificArtifacts(workerRuntime); await WriteFiles(); await WriteLocalSettingsJson(workerRuntime); } await WriteExtensionsJson(); await SetupSourceControl(); await WriteDockerfile(workerRuntime); PostInit(); }
public void Save(JObject json) { _lock.LockOperation(() => { if (!FileSystemHelpers.FileExists(_path)) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(_path)); } // opens file for FileAccess.Write but does allow other dirty read (FileShare.Read). // it is the most optimal where write is infrequent and dirty read is acceptable. using (var writer = new JsonTextWriter(new StreamWriter(FileSystemHelpers.OpenFile(_path, FileMode.Create, FileAccess.Write, FileShare.Read)))) { // prefer indented-readable format writer.Formatting = Formatting.Indented; json.WriteTo(writer); } }, "Updating setting", _timeout); }
public static void Persist(string siteName, string kind, string requestId, string status, string details) { var info = new DeploymentCompletedInfo { TimeStamp = $"{DateTime.UtcNow:s}Z", SiteName = siteName, Kind = kind, RequestId = requestId, Status = status, Details = details ?? string.Empty }; try { var path = Path.Combine(System.Environment.ExpandEnvironmentVariables(@"%HOME%"), "site", "deployments"); var file = Path.Combine(path, $"{Constants.LatestDeployment}.json"); var serializer = new JavaScriptSerializer(); var content = serializer.Serialize(info); FileSystemHelpers.EnsureDirectory(path); // write deployment info to %home%\site\deployments\LatestDeployment.json OperationManager.Attempt(() => FileSystemHelpers.Instance.File.WriteAllText(file, content)); // write to etw KuduEventSource.Log.DeploymentCompleted( info.SiteName, info.Kind, info.RequestId, info.Status, info.Details); } catch (Exception ex) { KuduEventSource.Log.KuduException( info.SiteName, string.Empty, string.Empty, string.Empty, string.Empty, $"{ex}"); } }
private void SetupAppServiceArtifacts(DeploymentContext context) { var tempArtifactDir = context.BuildTempPath; string framework = System.Environment.GetEnvironmentVariable(OryxBuildConstants.OryxEnvVars.FrameworkSetting); if (framework.StartsWith("DOTNETCORE", StringComparison.OrdinalIgnoreCase)) { tempArtifactDir = Path.Combine(context.BuildTempPath, "oryx-out"); } string sitePackages = "/home/data/SitePackages"; string deploymentsPath = $"/home/site/deployments/"; string artifactPath = $"/home/site/deployments/{context.CommitId}/artifact"; string packageNameFile = Path.Combine(sitePackages, "packagename.txt"); string packagePathFile = Path.Combine(sitePackages, "packagepath.txt"); FileSystemHelpers.EnsureDirectory(sitePackages); FileSystemHelpers.EnsureDirectory(artifactPath); string zipAppName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; string createdZip = PackageArtifactFromFolder(context, tempArtifactDir, tempArtifactDir, zipAppName, BuildArtifactType.Zip, numBuildArtifacts: -1); var copyExe = ExternalCommandFactory.BuildExternalCommandExecutable(tempArtifactDir, artifactPath, context.Logger); var copyToPath = Path.Combine(artifactPath, zipAppName); try { copyExe.ExecuteWithProgressWriter(context.Logger, context.Tracer, $"cp {createdZip} {copyToPath}"); } catch (Exception) { context.GlobalLogger.LogError(); throw; } // Gotta remove the old zips DeploymentHelper.PurgeOldDeploymentsIfNecessary(deploymentsPath, context.Tracer, totalAllowedDeployments: 10); File.WriteAllText(packageNameFile, zipAppName); File.WriteAllText(packagePathFile, artifactPath); }
/// <summary> /// Helper function to download package from given url, and place package (only 'content' folder from package) to given folder /// </summary> /// <param name="identity">Package identity</param> /// <param name="destinationFolder">Folder where we copy the package content (content folder only) to</param> /// <param name="pathToLocalCopyOfNupkg">File path where we copy the nudpk to</param> /// <returns></returns> public static async Task DownloadPackageToFolder(this SourceRepository srcRepo, PackageIdentity identity, string destinationFolder, string pathToLocalCopyOfNupkg) { var downloadResource = await srcRepo.GetResourceAndValidateAsync <DownloadResource>(); using (Stream packageStream = await srcRepo.GetPackageStream(identity)) { using (ZipFile zipFile = ZipFile.Read(packageStream)) { // we only care about stuff under "content" folder int substringStartIndex = @"content/".Length; IEnumerable <ZipEntry> contentEntries = zipFile.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); foreach (var entry in contentEntries) { string entryFileName = Uri.UnescapeDataString(entry.FileName); string fullPath = Path.Combine(destinationFolder, entryFileName.Substring(substringStartIndex)); if (entry.IsDirectory) { FileSystemHelpers.EnsureDirectory(fullPath.Replace('/', '\\')); continue; } 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); } } } // set position back to the head of stream packageStream.Position = 0; // save a copy of the nupkg at last WriteStreamToFile(packageStream, pathToLocalCopyOfNupkg); } }
public override async Task RunAsync() { if (!string.IsNullOrEmpty(FolderName)) { var folderPath = Path.Combine(Environment.CurrentDirectory, FolderName); FileSystemHelpers.EnsureDirectory(folderPath); Environment.CurrentDirectory = folderPath; } if (!Platforms.Contains(Platform)) { ColoredConsole.Error.WriteLine(ErrorColor($"platform {Platform} is not supported. Valid options are: {String.Join(",", Platforms)}")); return; } var dockerFilePath = Path.Combine(Environment.CurrentDirectory, "Dockerfile"); if (!FileSystemHelpers.FileExists(dockerFilePath)) { ColoredConsole.Error.WriteLine(ErrorColor($"Dockerfile not found in directory {Environment.CurrentDirectory}")); return; } var image = $"{Registry}/{Name}-azurefunc"; ColoredConsole.WriteLine("Building Docker image..."); await DockerHelpers.DockerBuild(image, Environment.CurrentDirectory); ColoredConsole.WriteLine("Pushing function image to registry..."); await DockerHelpers.DockerPush(image); var platform = PlatformFactory.CreatePlatform(Platform, ConfigPath); if (platform == null) { ColoredConsole.Error.WriteLine(ErrorColor($"Platform {Platform} is not supported")); return; } await platform.DeployContainerizedFunction(Name, image, MinInstances, MaxInstances); }
private static async Task <string> RestorePythonRequirements(string functionAppRoot) { var packagesLocation = Path.Combine(functionAppRoot, Constants.ExternalPythonPackages); FileSystemHelpers.EnsureDirectory(packagesLocation); var requirementsTxt = Path.Combine(functionAppRoot, Constants.RequirementsTxt); var packApp = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "tools", "python", "packapp"); var exe = new Executable("python", $"{packApp} --platform linux --python-version 36 --packages-dir-name {Constants.ExternalPythonPackages} {functionAppRoot}"); var sbErrors = new StringBuilder(); var exitCode = await exe.RunAsync(o => ColoredConsole.WriteLine(o), e => sbErrors.AppendLine(e)); if (exitCode != 0) { throw new CliException("There was an error restoring dependencies." + sbErrors.ToString()); } return(packagesLocation); }
public void SetPrivateKey(string key) { ITracer tracer = _traceFactory.GetTracer(); using (tracer.Step("SSHKeyManager.SetPrivateKey")) { FileSystemHelpers.EnsureDirectory(_sshPath); // Delete existing public key if (FileSystemHelpers.FileExists(_id_rsaPub)) { FileSystemHelpers.DeleteFileSafe(_id_rsaPub); } // bypass service key checking prompt (StrictHostKeyChecking=no). FileSystemHelpers.WriteAllText(_config, ConfigContent); // This overrides if file exists FileSystemHelpers.WriteAllText(_id_rsa, key); } }
public static async Task <string> EnsureExtensionsProjectExistsAsync(ISecretsManager secretsManager, bool csx, string extensionsDir = null) { if (GlobalCoreToolsSettings.CurrentWorkerRuntime == WorkerRuntime.dotnet && !csx) { return(DotnetHelpers.GetCsprojOrFsproj()); } if (String.IsNullOrEmpty(extensionsDir)) { extensionsDir = Environment.CurrentDirectory; } var extensionsProj = Path.Combine(extensionsDir, Constants.ExtenstionsCsProjFile); if (!FileSystemHelpers.FileExists(extensionsProj)) { FileSystemHelpers.EnsureDirectory(extensionsDir); await FileSystemHelpers.WriteAllTextToFileAsync(extensionsProj, await StaticResources.ExtensionsProject); } return(extensionsProj); }
private static async Task <Stream> InternalPreparePythonDeploymentInDocker(IEnumerable <string> files, string functionAppRoot) { var appContentPath = CopyToTemp(files, functionAppRoot); await DockerHelpers.DockerPull(Constants.DockerImages.LinuxPythonImageAmd64); var containerId = string.Empty; try { containerId = await DockerHelpers.DockerRun(Constants.DockerImages.LinuxPythonImageAmd64); await DockerHelpers.ExecInContainer(containerId, "mkdir -p /home/site/wwwroot/"); await DockerHelpers.CopyToContainer(containerId, $"{appContentPath}/.", "/home/site/wwwroot"); var scriptFilePath = Path.GetTempFileName(); await FileSystemHelpers.WriteAllTextToFileAsync(scriptFilePath, (await StaticResources.PythonDockerBuildScript).Replace("\r\n", "\n")); await DockerHelpers.CopyToContainer(containerId, scriptFilePath, Constants.StaticResourcesNames.PythonDockerBuild); await DockerHelpers.ExecInContainer(containerId, $"chmod +x /{Constants.StaticResourcesNames.PythonDockerBuild}"); await DockerHelpers.ExecInContainer(containerId, $"/{Constants.StaticResourcesNames.PythonDockerBuild}"); var tempDir = Path.Combine(Path.GetTempPath(), Path.GetTempFileName().Replace(".", "")); FileSystemHelpers.EnsureDirectory(tempDir); await DockerHelpers.CopyFromContainer(containerId, $"/app.zip", tempDir); return(FileSystemHelpers.OpenFile(Path.Combine(tempDir, "app.zip"), FileMode.Open)); } finally { if (!string.IsNullOrEmpty(containerId)) { await DockerHelpers.KillContainer(containerId, ignoreError : true); } } }
private static async Task <ProfileResultInfo> StopProfileInternalAsync(int processId, int profilingSessionId, bool ignoreProfileFile, ITracer tracer = null) { tracer = tracer ?? NullTracer.Instance; using (tracer.Step("ProfileManager.StopProfileInternalAsync")) { string profileFileFullPath = GetProfilePath(processId); string profileFileName = Path.GetFileName(profileFileFullPath); string arguments = string.Format("stop {0} /output:{1}", profilingSessionId, profileFileFullPath); var profileProcessResponse = await ExecuteProfilingCommandAsync(arguments, tracer); ProfileInfo removedId; if (profileProcessResponse.StatusCode != HttpStatusCode.OK) { _profilingList.TryRemove(processId, out removedId); return(profileProcessResponse); } FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(profileFileFullPath)); tracer.Step("profile was saved to {0} successfully.", profileFileFullPath); _profilingList.TryRemove(processId, out removedId); if (ignoreProfileFile) { try { FileSystemHelpers.DeleteFile(profileFileFullPath); } catch { } } DisposeTimerIfNecessary(); return(new ProfileResultInfo(HttpStatusCode.OK, string.Empty)); } }
public override async Task RunAsync() { if (SourceControl != SourceControl.Git) { throw new Exception("Only Git is supported right now for vsc"); } if (!string.IsNullOrEmpty(FolderName)) { var folderPath = Path.Combine(Environment.CurrentDirectory, FolderName); FileSystemHelpers.EnsureDirectory(folderPath); Environment.CurrentDirectory = folderPath; } await WriteFiles(); await WriteLaunchJson(); await SetupSourceControl(); await WriteDockerfile(); await WriteSample(); PostInit(); }
public override async Task RunAsync() { if (SourceControl != SourceControl.Git) { throw new Exception("Only Git is supported right now for vsc"); } if (!string.IsNullOrEmpty(FolderName)) { var folderPath = Path.Combine(Environment.CurrentDirectory, FolderName); FileSystemHelpers.EnsureDirectory(folderPath); Environment.CurrentDirectory = folderPath; } if (InitDockerOnly) { await InitDockerFileOnly(); } else { await InitFunctionAppProject(); } }
public static async Task <string> EnsureExtensionsProjectExistsAsync(ISecretsManager secretsManager, bool csx, string extensionsDir = null) { var workerRuntime = WorkerRuntimeLanguageHelper.GetCurrentWorkerRuntimeLanguage(secretsManager); if (workerRuntime == WorkerRuntime.dotnet && !csx) { return(DotnetHelpers.GetCsproj()); } if (String.IsNullOrEmpty(extensionsDir)) { extensionsDir = Environment.CurrentDirectory; } var extensionsProj = Path.Combine(extensionsDir, "extensions.csproj"); if (!FileSystemHelpers.FileExists(extensionsProj)) { FileSystemHelpers.EnsureDirectory(extensionsDir); await FileSystemHelpers.WriteAllTextToFileAsync(extensionsProj, await StaticResources.ExtensionsProject); } return(extensionsProj); }
private string ParseRequest(HttpContext context) { _filter = context.Request.QueryString[FilterQueryKey]; // path route as in logstream/{*path} without query strings string routePath = context.Request.RequestContext.RouteData.Values["path"] as string; // trim '/' routePath = String.IsNullOrEmpty(routePath) ? routePath : routePath.Trim('/'); // logstream at root if (String.IsNullOrEmpty(routePath)) { _enableTrace = true; return(_logPath); } // in case of application or http log, we ensure directory string firstPath = routePath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0]; bool isApplication = String.Equals(firstPath, "Application", StringComparison.OrdinalIgnoreCase); if (isApplication) { _enableTrace = true; FileSystemHelpers.EnsureDirectory(Path.Combine(_logPath, firstPath)); } else { bool isHttp = String.Equals(firstPath, "http", StringComparison.OrdinalIgnoreCase); if (isHttp) { FileSystemHelpers.EnsureDirectory(Path.Combine(_logPath, firstPath)); } } return(Path.Combine(_logPath, routePath)); }
public HttpResponseMessage MiniDump(int id, int dumpType = 0) { using (_tracer.Step("ProcessController.MiniDump")) { string sitePolicy = _settings.GetWebSitePolicy(); if ((MINIDUMP_TYPE)dumpType == MINIDUMP_TYPE.WithFullMemory && sitePolicy.Equals(FreeSitePolicy, StringComparison.OrdinalIgnoreCase)) { return(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, String.Format(CultureInfo.CurrentCulture, Resources.Error_FullMiniDumpNotSupported, sitePolicy))); } var process = GetProcessById(id); string dumpFile = Path.Combine(_environment.LogFilesPath, "minidump", "minidump.dmp"); FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(dumpFile)); FileSystemHelpers.DeleteFileSafe(_fileSystem, dumpFile); try { _tracer.Trace("MiniDump pid={0}, name={1}, file={2}", process.Id, process.ProcessName, dumpFile); process.MiniDump(dumpFile, (MINIDUMP_TYPE)dumpType); _tracer.Trace("MiniDump size={0}", new FileInfo(dumpFile).Length); } catch (Exception ex) { FileSystemHelpers.DeleteFileSafe(_fileSystem, dumpFile); return(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message)); } HttpResponseMessage response = Request.CreateResponse(); response.Content = new StreamContent(MiniDumpStream.OpenRead(dumpFile, _fileSystem)); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); response.Content.Headers.ContentDisposition.FileName = String.Format("{0}-{1:MM-dd-H:mm:ss}.dmp", process.ProcessName, DateTime.UtcNow); return(response); } }
public override async Task RunAsync() { if (SourceControl != SourceControl.Git) { throw new Exception("Only Git is supported right now for vsc"); } if (!string.IsNullOrEmpty(FolderName)) { var folderPath = Path.Combine(Environment.CurrentDirectory, FolderName); FileSystemHelpers.EnsureDirectory(folderPath); Environment.CurrentDirectory = folderPath; } var language = string.Empty; if (string.IsNullOrEmpty(Language)) { ColoredConsole.Write("Select a language: "); language = SelectionMenuHelper.DisplaySelectionWizard(availableLanguages.Keys); ColoredConsole.WriteLine(TitleColor(language)); } else { language = Language; } language = NormalizeLanguage(language); await InitLanguageSpecificArtifacts(language); await WriteFiles(); await WriteLocalSettingsJson(language); await WriteExtensionsJson(); await SetupSourceControl(); await WriteDockerfile(language); PostInit(); }
/// <summary> /// <para>Check if there is damanged leftover data</para> /// <para>If there is leftover data, will try to cleanup.</para> /// <para>If fail to cleanup, will move leftover data to Temp folder</para> /// </summary> private static void EnsureInstallationEnviroment(string installationDir, ITracer tracer) { // folder is there but nupkg is gone, means previous uninstallation must encounter some error bool isInstalledPackageBroken = FileSystemHelpers.DirectoryExists(installationDir) && FileSystemHelpers.GetFiles(installationDir, "*.nupkg").Length == 0; if (!isInstalledPackageBroken) { return; } using (tracer.Step("There was leftover data from previous uninstallation. Trying to cleanup now.")) { try { OperationManager.Attempt(() => FileSystemHelpers.DeleteDirectorySafe(installationDir, ignoreErrors: false)); return; } catch (Exception ex) { tracer.TraceError(ex); } FileSystemHelpers.EnsureDirectory(_toBeDeletedDirectoryPath); DirectoryInfo dirInfo = new DirectoryInfo(installationDir); string tmpFoder = Path.Combine( _toBeDeletedDirectoryPath, string.Format(CultureInfo.InvariantCulture, "{0}-{1}", dirInfo.Name, Guid.NewGuid().ToString("N").Substring(0, 8))); using (tracer.Step("Failed to cleanup. Moving leftover data to {0}", tmpFoder)) { // if failed, let exception bubble up to trigger bad request OperationManager.Attempt(() => FileSystemHelpers.MoveDirectory(installationDir, tmpFoder)); } } FileSystemHelpers.DeleteDirectoryContentsSafe(_toBeDeletedDirectoryPath); }
private void SetupAppServiceArtifacts(DeploymentContext context) { string sitePackages = "/home/data/SitePackages"; string deploymentsPath = $"/home/site/deployments/"; string artifactPath = $"/home/site/deployments/{context.CommitId}/artifact"; string packageNameFile = Path.Combine(sitePackages, "packagename.txt"); string packagePathFile = Path.Combine(sitePackages, "packagepath.txt"); FileSystemHelpers.EnsureDirectory(sitePackages); FileSystemHelpers.EnsureDirectory(artifactPath); string zipAppName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; context.Logger.Log($"Repository path is {repositoryPath}"); string createdZip = PackageArtifactFromFolder(context, repositoryPath, repositoryPath, zipAppName, BuildArtifactType.Zip, numBuildArtifacts: -1); context.Logger.Log($"Copying the deployment artifact to {zipAppName} file"); var copyExe = ExternalCommandFactory.BuildExternalCommandExecutable(repositoryPath, artifactPath, context.Logger); var copyToPath = Path.Combine(artifactPath, zipAppName); try { copyExe.ExecuteWithProgressWriter(context.Logger, context.Tracer, $"cp {createdZip} {copyToPath}"); } catch (Exception) { context.GlobalLogger.LogError(); throw; } // Gotta remove the old zips DeploymentHelper.PurgeOldDeploymentsIfNecessary(deploymentsPath, context.Tracer, totalAllowedDeployments: 10); File.WriteAllText(packageNameFile, zipAppName); File.WriteAllText(packagePathFile, artifactPath); }
public static async Task <string> EnsureExtensionsProjectExistsAsync() { var extensionsDir = Path.Combine(Environment.CurrentDirectory, "functions-extensions"); var extensionsProj = Path.Combine(extensionsDir, "extensions.csproj"); if (!FileSystemHelpers.FileExists(extensionsProj)) { FileSystemHelpers.EnsureDirectory(extensionsDir); var assembly = typeof(ExtensionsHelper).Assembly; var extensionsProjText = string.Empty; using (Stream resource = assembly.GetManifestResourceStream(assembly.GetName().Name + ".ExtensionsProj.txt")) using (var reader = new StreamReader(resource)) { while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); extensionsProjText += $"{line}{Environment.NewLine}"; } } await FileSystemHelpers.WriteAllTextToFileAsync(extensionsProj, extensionsProjText); } return(extensionsProj); }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build( ChangeSet changeSet, ITracer tracer, IDisposable deployStep, IRepository repository, DeploymentInfoBase deploymentInfo, DeploymentAnalytics deploymentAnalytics, bool fullBuildByDefault) { if (changeSet == null || String.IsNullOrEmpty(changeSet.Id)) { throw new ArgumentException("The changeSet.Id parameter is null or empty", "changeSet.Id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; string buildTempPath = null; string id = changeSet.Id; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; // Add in per-deploy default settings values based on the details of this deployment var perDeploymentDefaults = new Dictionary <string, string> { { SettingsKeys.DoBuildDuringDeployment, fullBuildByDefault.ToString() } }; var settingsProviders = _settings.SettingsProviders.Concat( new[] { new BasicSettingsProvider(perDeploymentDefaults, SettingsProvidersPriority.PerDeploymentDefault) }); var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repository.RepositoryPath, settingsProviders); string delayMaxInStr = perDeploymentSettings.GetValue(SettingsKeys.MaxRandomDelayInSec); if (!String.IsNullOrEmpty(delayMaxInStr)) { int maxDelay; if (!Int32.TryParse(delayMaxInStr, out maxDelay) || maxDelay < 0) { tracer.Trace("Invalid {0} value, expect a positive integer, received {1}", SettingsKeys.MaxRandomDelayInSec, delayMaxInStr); } else { tracer.Trace("{0} is set to {1}s", SettingsKeys.MaxRandomDelayInSec, maxDelay); int gap = _random.Next(maxDelay); using (tracer.Step("Randomization applied to {0}, Start sleeping for {1}s", maxDelay, gap)) { logger.Log(Resources.Log_DelayingBeforeDeployment, gap); await Task.Delay(TimeSpan.FromSeconds(gap)); } } } try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository, deploymentInfo); deploymentAnalytics.ProjectType = builder.ProjectType; tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); innerLogger.Log(ex); MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } // Create a directory for the script output temporary artifacts // Use tick count (in hex) instead of guid to keep the path for getting to long buildTempPath = Path.Combine(_environment.TempPath, DateTime.UtcNow.Ticks.ToString("x")); FileSystemHelpers.EnsureDirectory(buildTempPath); var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), IgnoreManifest = deploymentInfo != null && deploymentInfo.CleanupTargetDirectory, // Ignoring the manifest will cause kudusync to delete sub-directories / files // in the destination directory that are not present in the source directory, // without checking the manifest to see if the file was copied over to the destination // during a previous kudusync operation. This effectively performs a clean deployment // from the source to the destination directory. Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(deploymentInfo, _environment, perDeploymentSettings), BuildTempPath = buildTempPath, CommitId = id, Message = changeSet.Message }; if (context.PreviousManifestFilePath == null) { // this file (/site/firstDeploymentManifest) capture the last active deployment when disconnecting SCM context.PreviousManifestFilePath = Path.Combine(_environment.SiteRootPath, Constants.FirstDeploymentManifestFileName); if (!FileSystemHelpers.FileExists(context.PreviousManifestFilePath)) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } } PreDeployment(tracer); using (tracer.Step("Building")) { try { await builder.Build(context); builder.PostBuild(context); await RestartMainSiteIfNeeded(tracer, logger, deploymentInfo); await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger), deploymentInfo?.SyncFunctionsTriggersPath); TouchWatchedFileIfNeeded(_settings, deploymentInfo, context); FinishDeployment(id, deployStep); deploymentAnalytics.VsProjectId = TryGetVsProjectId(context); deploymentAnalytics.Result = DeployStatus.Success.ToString(); } catch (Exception ex) { MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } } } catch (Exception ex) { FailDeployment(tracer, deployStep, deploymentAnalytics, ex); } finally { // Clean the temp folder up CleanBuild(tracer, buildTempPath); } }
public void Initialize() { var tracer = _tracerFactory.GetTracer(); using (tracer.Step("LibGit2SharpRepository Initialize")) { var dotGitPath = LibGit2Sharp.Repository.Init(RepositoryPath); using (var repo = new LibGit2Sharp.Repository(dotGitPath)) { repo.Config.Set("core.autocrlf", true); // This speeds up git operations like 'git checkout', especially on slow drives like in Azure repo.Config.Set("core.preloadindex", true); repo.Config.Set("user.name", _settings.GetGitUsername()); repo.Config.Set("user.email", _settings.GetGitEmail()); // This is needed to make lfs work repo.Config.Set("filter.lfs.clean", "git-lfs clean %f"); repo.Config.Set("filter.lfs.smudge", "git-lfs smudge %f"); repo.Config.Set("filter.lfs.required", true); using (tracer.Step("Configure git server")) { // Allow getting pushes even though we're not bare repo.Config.Set("receive.denyCurrentBranch", "ignore"); } // to disallow browsing to this folder in case of in-place repo using (tracer.Step("Create deny users for .git folder")) { string content = "<?xml version=\"1.0\"" + @"?> <configuration> <system.web> <authorization> <deny users=" + "\"*\"" + @"/> </authorization> </system.web> <configuration>"; File.WriteAllText(Path.Combine(dotGitPath, "web.config"), content); } // Server env does not support interactive cred prompt; hence, we intercept any credential provision // for git fetch/clone with http/https scheme and return random invalid u/p forcing 'fatal: Authentication failed.' using (tracer.Step("Configure git-credential")) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(GitCredentialHookPath)); string content = @"#!/bin/sh if [ " + "\"$1\" = \"get\"" + @" ]; then echo username=dummyUser echo password=dummyPassword fi" + "\n"; File.WriteAllText(GitCredentialHookPath, content); repo.Config.Set("credential.helper", string.Format("!'{0}'", GitCredentialHookPath)); } } using (tracer.Step("Setup post receive hook")) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(PostReceiveHookPath)); string content = @"#!/bin/sh read i echo $i > pushinfo " + KnownEnvironment.KUDUCOMMAND + "\n"; File.WriteAllText(PostReceiveHookPath, content); } // NOTE: don't add any new init steps after creating the post receive hook, // as it's also used to mark that Init was fully executed } }
public HttpResponseMessage MiniDump(int id, int dumpType = 0, string format = null) { using (_tracer.Step("ProcessController.MiniDump")) { DumpFormat dumpFormat = ParseDumpFormat(format, DumpFormat.Raw); if (dumpFormat != DumpFormat.Raw && dumpFormat != DumpFormat.Zip) { return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Resources.Error_DumpFormatNotSupported, dumpFormat))); } string sitePolicy = _settings.GetWebSitePolicy(); if ((MINIDUMP_TYPE)dumpType == MINIDUMP_TYPE.WithFullMemory && sitePolicy.Equals(FreeSitePolicy, StringComparison.OrdinalIgnoreCase)) { return(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, String.Format(CultureInfo.CurrentCulture, Resources.Error_FullMiniDumpNotSupported, sitePolicy))); } var process = GetProcessById(id); string dumpFile = Path.Combine(_environment.LogFilesPath, "minidump", "minidump.dmp"); FileSystemHelpers.EnsureDirectory(_fileSystem, Path.GetDirectoryName(dumpFile)); FileSystemHelpers.DeleteFileSafe(_fileSystem, dumpFile); try { using (_tracer.Step(String.Format("MiniDump pid={0}, name={1}, file={2}", process.Id, process.ProcessName, dumpFile))) { process.MiniDump(dumpFile, (MINIDUMP_TYPE)dumpType); _tracer.Trace("MiniDump size={0}", new FileInfo(dumpFile).Length); } } catch (Exception ex) { _tracer.TraceError(ex); FileSystemHelpers.DeleteFileSafe(_fileSystem, dumpFile); return(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message)); } if (dumpFormat == DumpFormat.Raw) { string responseFileName = GetResponseFileName(process.ProcessName, "dmp"); HttpResponseMessage response = Request.CreateResponse(); response.Content = new StreamContent(FileStreamWrapper.OpenRead(dumpFile, _fileSystem)); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); response.Content.Headers.ContentDisposition.FileName = responseFileName; return(response); } else if (dumpFormat == DumpFormat.Zip) { string responseFileName = GetResponseFileName(process.ProcessName, "zip"); HttpResponseMessage response = Request.CreateResponse(); response.Content = ZipStreamContent.Create(responseFileName, _tracer, zip => { try { zip.AddFile(dumpFile, String.Empty); } finally { FileSystemHelpers.DeleteFileSafe(_fileSystem, dumpFile); } foreach (var fileName in new[] { "sos.dll", "mscordacwks.dll" }) { string filePath = Path.Combine(ProcessExtensions.ClrRuntimeDirectory, fileName); if (_fileSystem.File.Exists(filePath)) { zip.AddFile(filePath, String.Empty); } } }); return(response); } else { return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Resources.Error_DumpFormatNotSupported, dumpFormat))); } } }
public async Task <Site> CreateSiteAsync(string applicationName) { using (ServerManager iis = GetServerManager()) { try { var siteBindingCongfigs = new List <IBindingConfiguration>(); var svcSiteBindingCongfigs = new List <IBindingConfiguration>(); if (_context.Configuration != null && _context.Configuration.Bindings != null) { siteBindingCongfigs = _context.Configuration.Bindings.Where(b => b.SiteType == SiteType.Live).ToList(); svcSiteBindingCongfigs = _context.Configuration.Bindings.Where(b => b.SiteType == SiteType.Service).ToList(); } // Determine the host header values List <BindingInformation> siteBindings = BuildDefaultBindings(applicationName, siteBindingCongfigs).ToList(); List <BindingInformation> serviceSiteBindings = BuildDefaultBindings(applicationName, svcSiteBindingCongfigs).ToList(); // Create the service site for this site var serviceSite = CreateSiteAsync(iis, applicationName, GetServiceSite(applicationName), _context.Configuration.ServiceSitePath, serviceSiteBindings); // Create the main site string siteName = GetLiveSite(applicationName); string root = _context.Paths.GetApplicationPath(applicationName); string siteRoot = _context.Paths.GetLiveSitePath(applicationName); string webRoot = Path.Combine(siteRoot, Constants.WebRoot); FileSystemHelpers.EnsureDirectory(webRoot); File.WriteAllText(Path.Combine(webRoot, HostingStartHtml), HostingStartHtmlContents); var site = CreateSiteAsync(iis, applicationName, siteName, webRoot, siteBindings); // Map a path called _app to the site root under the service site MapServiceSitePath(iis, applicationName, Constants.MappedSite, root); // Commit the changes to iis iis.CommitChanges(); var serviceUrls = serviceSite.Bindings .Select(url => String.Format("{0}://{1}:{2}/", url.Protocol, String.IsNullOrEmpty(url.Host) ? "localhost" : url.Host, url.EndPoint.Port)) .ToList(); // Wait for the site to start await OperationManager.AttemptAsync(() => WaitForSiteAsync(serviceUrls.First())); // Set initial ScmType state to LocalGit var settings = new RemoteDeploymentSettingsManager(serviceUrls.First() + "api/settings"); await settings.SetValue(SettingsKeys.ScmType, ScmType.LocalGit); var siteUrls = site.Bindings .Select(url => String.Format("{0}://{1}:{2}/", url.Protocol, String.IsNullOrEmpty(url.Host) ? "localhost" : url.Host, url.EndPoint.Port)) .ToList(); return(new Site { ServiceUrls = serviceUrls, SiteUrls = siteUrls }); } catch { try { DeleteSiteAsync(applicationName).Wait(); } catch { // Don't let it throw if we're unable to delete a failed creation. } throw; } } }
public async Task ResetSiteContent(string applicationName) { const int MaxWaitSeconds = 300; using (ServerManager iis = GetServerManager()) { var appPool = iis.ApplicationPools.FirstOrDefault(ap => ap.Name == applicationName); if (appPool == null) { throw new InvalidOperationException($"Failed to recycle {applicationName} app pool. It does not exist!"); } // the app pool is running or starting, so stop it first. if (appPool.State == ObjectState.Started || appPool.State == ObjectState.Starting) { // wait for the app to finish before trying to stop for (int i = 0; i > MaxWaitSeconds && appPool.State == ObjectState.Starting; ++i) { await Task.Delay(1000); } // stop the app if it isn't already stopped if (appPool.State != ObjectState.Stopped) { appPool.Stop(); } } // wait for the app to stop for (int i = 0; appPool.State != ObjectState.Stopped; ++i) { await Task.Delay(1000); if (i > MaxWaitSeconds) { throw new InvalidOperationException($"Failed to recycle {applicationName} app pool. Its state '{appPool.State}' is not stopped!"); } } string root = _context.Paths.GetApplicationPath(applicationName); string siteRoot = _context.Paths.GetLiveSitePath(applicationName); foreach (var dir in Directory.GetDirectories(root).Concat(new[] { Path.Combine(Path.GetTempPath(), applicationName) })) { if (!Directory.Exists(dir)) { continue; } // use rmdir command since it handles both hidden and read-only files OperationManager.SafeExecute(() => Process.Start(new ProcessStartInfo { Arguments = $"/C rmdir /s /q \"{dir}\"", WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, FileName = "cmd.exe" })?.WaitForExit()); if (Directory.Exists(dir) && !dir.Contains(".deleted.")) { var dirName = Path.GetFileName(dir); OperationManager.Attempt(() => Process.Start(new ProcessStartInfo { Arguments = $"/C ren \"{dir}\" \"{dirName}.deleted.{Guid.NewGuid():N}\"", WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, FileName = "cmd.exe" })?.WaitForExit()); } } var webRoot = Path.Combine(siteRoot, Constants.WebRoot); FileSystemHelpers.EnsureDirectory(siteRoot); FileSystemHelpers.EnsureDirectory(webRoot); File.WriteAllText(Path.Combine(webRoot, HostingStartHtml), HostingStartHtmlContents); // start the app appPool.Start(); // wait for the app to stop for (int i = 0; appPool.State != ObjectState.Started; ++i) { await Task.Delay(1000); if (i > MaxWaitSeconds) { throw new InvalidOperationException($"Failed to recycle {applicationName} app pool. Its state '{appPool.State}' is not started!"); } } } }
public void Initialize() { var tracer = _tracerFactory.GetTracer(); using (tracer.Step("GitExeRepository.Initialize")) { Execute(tracer, "init"); Execute(tracer, "config core.autocrlf {0}", OSDetector.IsOnWindows() ? "true" : "false"); // This speeds up git operations like 'git checkout', especially on slow drives like in Azure Execute(tracer, "config core.preloadindex true"); Execute(tracer, @"config user.name ""{0}""", _settings.GetGitUsername()); Execute(tracer, @"config user.email ""{0}""", _settings.GetGitEmail()); // This is needed to make lfs work Execute(tracer, @"config filter.lfs.clean ""git-lfs clean %f"""); Execute(tracer, @"config filter.lfs.smudge ""git-lfs smudge %f"""); Execute(tracer, @"config filter.lfs.required true"); using (tracer.Step("Configure git server")) { // Allow getting pushes even though we're not bare Execute(tracer, "config receive.denyCurrentBranch ignore"); } // to disallow browsing to this folder in case of in-place repo using (tracer.Step("Create deny users for .git folder")) { string content = "<?xml version=\"1.0\"" + @"?> <configuration> <system.web> <authorization> <deny users=" + "\"*\"" + @"/> </authorization> </system.web> <configuration>"; File.WriteAllText(Path.Combine(_gitExe.WorkingDirectory, ".git", "web.config"), content); } // Server env does not support interactive cred prompt; hence, we intercept any credential provision // for git fetch/clone with http/https scheme and return random invalid u/p forcing 'fatal: Authentication failed.' using (tracer.Step("Configure git-credential")) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(GitCredentialHookPath)); string content = @"#!/bin/sh if [ " + "\"$1\" = \"get\"" + @" ]; then echo username=dummyUser echo password=dummyPassword fi" + "\n"; File.WriteAllText(GitCredentialHookPath, content); Execute(tracer, "config credential.helper \"!'{0}'\"", GitCredentialHookPath); } using (tracer.Step("Setup post receive hook")) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(PostReceiveHookPath)); //#!/bin/sh //read i //echo $i > pushinfo //KnownEnvironment.KUDUCOMMAND StringBuilder sb = new StringBuilder(); sb.AppendLine("#!/bin/sh"); sb.AppendLine("read i"); sb.AppendLine("echo $i > pushinfo"); sb.AppendLine(KnownEnvironment.KUDUCOMMAND); if (OSDetector.IsOnWindows()) { FileSystemHelpers.WriteAllText(PostReceiveHookPath, sb.ToString()); } else { FileSystemHelpers.WriteAllText(PostReceiveHookPath, sb.ToString().Replace("\r\n", "\n")); using (tracer.Step("Non-Windows enviroment, granting 755 permission to post-receive hook file")) { PermissionHelper.Chmod("755", PostReceiveHookPath, _environment, _settings, NullLogger.Instance); } } } // NOTE: don't add any new init steps after creating the post receive hook, // as it's also used to mark that Init was fully executed } }
public static async Task UpdateLocalPackage(string siteExntentionsRootPath, string packageId, string packageVersion, string destinationFolder, string pathToLocalCopyOfNupkg, ITracer tracer) { tracer.Trace("Performing incremental package update for {0}", packageId); using (var client = new HttpClient()) { var uri = new Uri(String.Format("https://www.nuget.org/api/v2/package/{0}/{1}", packageId, packageVersion)); var response = await client.GetAsync(uri); using (Stream newPackageStream = await response.Content.ReadAsStreamAsync()) { // update file var localPackage = (await FeedExtensionsV2.SearchLocalRepo(siteExntentionsRootPath, packageId)).FirstOrDefault(); if (localPackage == null) { throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "Package {0} not found from local repo.", packageId)); } string nupkgFile = Directory.GetFiles(Path.Combine(siteExntentionsRootPath, packageId), "*.nupkg", SearchOption.TopDirectoryOnly).FirstOrDefault(); using (ZipFile oldPackageZip = ZipFile.Read(nupkgFile)) 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 entryFileName = Uri.UnescapeDataString(entry.FileName); string fullPath = Path.Combine(destinationFolder, entryFileName.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 entryFileName = Uri.UnescapeDataString(entry.FileName); string fullPath = Path.Combine(destinationFolder, entryFileName.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 folder is meant to be deleted, double check there is no files under it var entryNameInLower = entryFileName.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 (!packageVersion.Equals(localPackage.Version)) { using (tracer.Step("New package has difference version {0} from old package {1}. Remove old nupkg file.", packageVersion, localPackage.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", packageId, packageVersion), string.Format(CultureInfo.InvariantCulture, "{0}.{1}.nupkg", localPackage.Id, localPackage.Version)); FileSystemHelpers.DeleteFileSafe(oldNupkg); } } } } } }