private void ExtractTriggers(IRepository repository, ArtifactDeploymentInfo deploymentInfo) { FileSystemHelpers.EnsureDirectory(deploymentInfo.SyncFunctionsTriggersPath); // Loading the zip file depends on how fast the file system is. // Tested Azure Files share with a zip containing 120k files (160 MBs) // takes 20 seconds to load. On my machine it takes 900 msec. using (var zip = ZipFile.OpenRead(Path.Combine(_environment.SitePackagesPath, deploymentInfo.ArtifactFileName))) { var entries = zip.Entries // Only select host.json, proxies.json, or function.json that are from top level directories only // Tested with a zip containing 120k files, and this took 90 msec // on my machine. .Where(e => e.FullName.Equals(Constants.FunctionsHostConfigFile, StringComparison.OrdinalIgnoreCase) || e.FullName.Equals(Constants.ProxyConfigFile, StringComparison.OrdinalIgnoreCase) || isFunctionJson(e.FullName)); foreach (var entry in entries) { var path = Path.Combine(deploymentInfo.SyncFunctionsTriggersPath, entry.FullName); FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(path)); entry.ExtractToFile(path, overwrite: true); } } CommitRepo(repository, deploymentInfo); bool isFunctionJson(string fullName) { return(fullName.EndsWith(Constants.FunctionsConfigFile) && fullName.Count(c => c == '/' || c == '\\') == 1); } }
private async Task WriteSitePackageZip(ArtifactDeploymentInfo zipDeploymentInfo, ITracer tracer) { var filePath = Path.Combine(_environment.SitePackagesPath, zipDeploymentInfo.ArtifactFileName); // Make sure D:\home\data\SitePackages exists FileSystemHelpers.EnsureDirectory(_environment.SitePackagesPath); using (_tracer.Step("Writing zip file to {0}", filePath)) { if (HttpContext.Request.ContentType.Contains("multipart/form-data", StringComparison.OrdinalIgnoreCase)) { FormValueProvider formModel; using (_tracer.Step("Writing zip file to {0}", filePath)) { using (var file = System.IO.File.Create(filePath)) { formModel = await Request.StreamFile(file); } } } else { using (var file = System.IO.File.Create(filePath)) { await Request.Body.CopyToAsync(file); } } } DeploymentHelper.PurgeBuildArtifactsIfNecessary(_environment.SitePackagesPath, BuildArtifactType.Zip, tracer, _settings.GetMaxZipPackageCount()); }
public static async Task UpdatePackageName(ArtifactDeploymentInfo deploymentInfo, IEnvironment environment, ILogger logger) { var packageNamePath = Path.Combine(environment.SitePackagesPath, Constants.PackageNameTxt); logger.Log($"Updating {packageNamePath} with deployment {deploymentInfo.ArtifactFileName}"); await FileSystemHelpers.WriteAllTextToFileAsync(packageNamePath, deploymentInfo.ArtifactFileName); }
private static void CommitRepo(IRepository repository, ArtifactDeploymentInfo deploymentInfo) { // Needed in order for repository.GetChangeSet() to work. // Similar to what OneDriveHelper and DropBoxHelper do. // We need to make to call repository.Commit() since deployment flow expects at // least 1 commit in the IRepository. Even though there is no repo per se in this // scenario, deployment pipeline still generates a NullRepository repository.Commit(deploymentInfo.Message, deploymentInfo.Author, deploymentInfo.AuthorEmail); }
public static async Task UpdateSiteVersion(ArtifactDeploymentInfo deploymentInfo, IEnvironment environment, ITracer tracer) { var siteVersionPath = Path.Combine(environment.SitePackagesPath, Constants.PackageNameTxt); using (tracer.Step($"Updating {siteVersionPath} with deployment {deploymentInfo.ArtifactFileName}")) { await OperationManager.AttemptAsync(() => FileSystemHelpers.WriteAllTextToFileAsync(siteVersionPath, deploymentInfo.ArtifactFileName)); } }
public async Task <IActionResult> ZipPushDeploy( [FromQuery] bool isAsync = false, [FromQuery] bool syncTriggers = false, [FromQuery] bool overwriteWebsiteRunFromPackage = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("ZipPushDeploy")) { string deploymentId = GetExternalDeploymentId(Request); var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = deployer, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed zip file"), CommitId = null, ExternalDeploymentId = deploymentId, RepositoryType = RepositoryType.None, Fetch = LocalZipHandler, DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, Message = message, RemoteURL = null, DoSyncTriggers = syncTriggers, OverwriteWebsiteRunFromPackage = overwriteWebsiteRunFromPackage && _environment.IsOnLinuxConsumption }; if (_settings.RunFromLocalZip()) { // This is used if the deployment is Run-From-Zip // the name of the deployed file in D:\home\data\SitePackages\{name}.zip is the // timestamp in the format yyyMMddHHmmss. deploymentInfo.ArtifactFileName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; // This is also for Run-From-Zip where we need to extract the triggers // for post deployment sync triggers. deploymentInfo.SyncFunctionsTriggersPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); } ; return(await PushDeployAsync(deploymentInfo, isAsync, HttpContext)); } }
private async Task WriteSitePackageZip(ArtifactDeploymentInfo zipDeploymentInfo, ITracer tracer, HttpContent content) { var filePath = Path.Combine(_environment.SitePackagesPath, zipDeploymentInfo.ArtifactFileName); // Make sure D:\home\data\SitePackages exists FileSystemHelpers.EnsureDirectory(_environment.SitePackagesPath); using (tracer.Step("Saving request content to {0}", filePath)) { await content.CopyToAsync(filePath, tracer); } DeploymentHelper.PurgeZipsIfNecessary(_environment.SitePackagesPath, tracer, _settings.GetMaxZipPackageCount()); }
private async Task <string> DeployZipLocally(ArtifactDeploymentInfo zipDeploymentInfo, ITracer tracer) { var content = await DeploymentHelper.GetArtifactContentFromURL(zipDeploymentInfo, tracer); var zipFileName = Path.ChangeExtension(Path.GetRandomFileName(), "zip"); var zipFilePath = Path.Combine(_environment.ZipTempPath, zipFileName); using (_tracer.Step("Downloading content from {0} to {1}", zipDeploymentInfo.RemoteURL.Split('?')[0], zipFilePath)) { await content.CopyToAsync(zipFilePath, _tracer); } zipDeploymentInfo.RepositoryUrl = zipFilePath; return(zipFilePath); }
private async Task LocalZipHandler(IRepository repository, DeploymentInfoBase deploymentInfo, string targetBranch, ILogger logger, ITracer tracer) { if (_settings.RunFromLocalZip() && deploymentInfo is ArtifactDeploymentInfo) { ArtifactDeploymentInfo zipDeploymentInfo = (ArtifactDeploymentInfo)deploymentInfo; // If this was a request with a Zip URL in the JSON, we first need to get the zip content and write it to the site. if (!string.IsNullOrEmpty(zipDeploymentInfo.RemoteURL)) { await WriteSitePackageZip(zipDeploymentInfo, tracer, await DeploymentHelper.GetArtifactContentFromURL(zipDeploymentInfo, tracer)); } // If this is a Run-From-Zip deployment, then we need to extract function.json // from the zip file into path zipDeploymentInfo.SyncFunctionsTriggersPath ExtractTriggers(repository, zipDeploymentInfo); } else { await LocalZipFetch(repository, deploymentInfo, targetBranch, logger, tracer); } }
public async Task <IActionResult> WarPushDeploy( [FromQuery] bool isAsync = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("WarPushDeploy")) { string deploymentId = GetExternalDeploymentId(Request); var appName = HttpContext.Request.Query["name"].ToString(); if (string.IsNullOrWhiteSpace(appName)) { appName = "ROOT"; } var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = deployer, TargetSubDirectoryRelativePath = Path.Combine("webapps", appName), WatchedFilePath = Path.Combine("WEB-INF", "web.xml"), IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, CleanupTargetDirectory = true, // For now, always cleanup the target directory. If needed, make it configurable TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed war file"), CommitId = null, ExternalDeploymentId = deploymentId, RepositoryType = RepositoryType.None, Fetch = LocalZipFetch, DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, Message = message, RemoteURL = null }; return(await PushDeployAsync(deploymentInfo, isAsync, HttpContext)); } }
public async Task <HttpResponseMessage> ZipPushDeploy( [FromUri] bool isAsync = false, [FromUri] string author = null, [FromUri] string authorEmail = null, [FromUri] string deployer = Constants.ZipDeploy, [FromUri] string message = DefaultMessage, [FromUri] bool trackDeploymentProgress = false) { using (_tracer.Step("ZipPushDeploy")) { var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = deployer, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed zip file"), CommitId = null, DeploymentTrackingId = trackDeploymentProgress ? Guid.NewGuid().ToString() : null, RepositoryType = RepositoryType.None, Fetch = LocalZipHandler, DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, Message = message }; if (_settings.RunFromLocalZip()) { // This is used if the deployment is Run-From-Zip // the name of the deployed file in D:\home\data\SitePackages\{name}.zip is the // timestamp in the format yyyMMddHHmmss. deploymentInfo.ArtifactFileName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; // This is also for Run-From-Zip where we need to extract the triggers // for post deployment sync triggers. deploymentInfo.SyncFunctionsTriggersPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); } return(await PushDeployAsync(deploymentInfo, isAsync)); } }
public async Task <HttpResponseMessage> WarPushDeploy( [FromUri] bool isAsync = false, [FromUri] string author = null, [FromUri] string authorEmail = null, [FromUri] string deployer = Constants.WarDeploy, [FromUri] string message = DefaultMessage, [FromUri] bool trackDeploymentProgress = false) { using (_tracer.Step("WarPushDeploy")) { var appName = Request.RequestUri.ParseQueryString()["name"]; if (string.IsNullOrWhiteSpace(appName)) { appName = "ROOT"; } var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = deployer, TargetSubDirectoryRelativePath = Path.Combine("webapps", appName), WatchedFilePath = Path.Combine("WEB-INF", "web.xml"), IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, CleanupTargetDirectory = true, // For now, always cleanup the target directory. If needed, make it configurable TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed war file"), CommitId = null, DeploymentTrackingId = trackDeploymentProgress ? Guid.NewGuid().ToString() : null, RepositoryType = RepositoryType.None, Fetch = LocalZipFetch, DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, Message = message }; return(await PushDeployAsync(deploymentInfo, isAsync)); } }
public async Task <IActionResult> ZipPushDeployViaUrl( [FromBody] JObject requestJson, [FromQuery] bool isAsync = false, [FromQuery] bool syncTriggers = false, [FromQuery] bool overwriteWebsiteRunFromPackage = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("ZipPushDeployViaUrl")) { string deploymentId = GetExternalDeploymentId(Request); string zipUrl = GetArtifactURLFromJSON(requestJson); var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = deployer, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed zip file"), CommitId = null, ExternalDeploymentId = deploymentId, RepositoryType = RepositoryType.None, Fetch = LocalZipHandler, DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, Message = message, RemoteURL = zipUrl, DoSyncTriggers = syncTriggers, OverwriteWebsiteRunFromPackage = overwriteWebsiteRunFromPackage && _environment.IsOnLinuxConsumption }; return(await PushDeployAsync(deploymentInfo, isAsync, HttpContext)); } }
private async Task <HttpResponseMessage> PushDeployAsync(ArtifactDeploymentInfo deploymentInfo, bool isAsync, JObject requestObject = null, ArtifactType artifactType = ArtifactType.Zip) { var content = Request.Content; var isRequestJSON = content.Headers?.ContentType?.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase); if (isRequestJSON == true) { try { // Read the request body if it hasn't been read already if (requestObject == null) { requestObject = await Request.Content.ReadAsAsync <JObject>(); } deploymentInfo.RemoteURL = ArmUtils.IsArmRequest(Request) ? GetArticfactURLFromARMJSON(requestObject) : GetArtifactURLFromJSON(requestObject); } catch (Exception ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } } // For zip artifacts (zipdeploy, wardeploy, onedeploy with type=zip), copy the request body in a temp zip file. // It will be extracted to the appropriate directory by the Fetch handler else if (artifactType == ArtifactType.Zip) { if (_settings.RunFromLocalZip()) { await WriteSitePackageZip(deploymentInfo, _tracer, Request.Content); } else { var zipFileName = Path.ChangeExtension(Path.GetRandomFileName(), "zip"); var zipFilePath = Path.Combine(_environment.ZipTempPath, zipFileName); using (_tracer.Step("Saving request content to {0}", zipFilePath)) { await content.CopyToAsync(zipFilePath, _tracer); } deploymentInfo.RepositoryUrl = zipFilePath; } } // Copy the request body to a temp file. // It will be moved to the appropriate directory by the Fetch handler else if (deploymentInfo.Deployer == Constants.OneDeploy) { var artifactTempPath = Path.Combine(_environment.ZipTempPath, deploymentInfo.TargetFileName); using (_tracer.Step("Saving request content to {0}", artifactTempPath)) { await content.CopyToAsync(artifactTempPath, _tracer); } deploymentInfo.RepositoryUrl = artifactTempPath; } isAsync = ArmUtils.IsArmRequest(Request) ? true : isAsync; var result = await _deploymentManager.FetchDeploy(deploymentInfo, isAsync, Request.GetRequestUri(), "HEAD"); var response = Request.CreateResponse(); switch (result) { case FetchDeploymentRequestResult.RunningAynschronously: if (ArmUtils.IsArmRequest(Request)) { DeployResult deployResult = new DeployResult(); response = Request.CreateResponse(HttpStatusCode.Accepted, ArmUtils.AddEnvelopeOnArmRequest(deployResult, Request)); string statusURL = GetStatusUrl(Request.Headers.Referrer ?? Request.RequestUri); // Should not happen: If we couldn't make the URL, there must have been an error in the request if (string.IsNullOrEmpty(statusURL)) { var badResponse = Request.CreateResponse(); badResponse.StatusCode = HttpStatusCode.BadRequest; return(badResponse); } // latest deployment keyword reserved to poll till deployment done response.Headers.Location = new Uri(statusURL + String.Format("/deployments/{0}?api-version=2018-02-01&deployer={1}&time={2}", Constants.LatestDeployment, deploymentInfo.Deployer, DateTime.UtcNow.ToString("yyy-MM-dd_HH-mm-ssZ"))); } else if (isAsync) { // latest deployment keyword reserved to poll till deployment done response.Headers.Location = new Uri(Request.GetRequestUri(), String.Format("/api/deployments/{0}?deployer={1}&time={2}", Constants.LatestDeployment, deploymentInfo.Deployer, DateTime.UtcNow.ToString("yyy-MM-dd_HH-mm-ssZ"))); } response.StatusCode = HttpStatusCode.Accepted; break; case FetchDeploymentRequestResult.ForbiddenScmDisabled: // Should never hit this for zip push deploy response.StatusCode = HttpStatusCode.Forbidden; _tracer.Trace("Scm is not enabled, reject all requests."); break; case FetchDeploymentRequestResult.ConflictAutoSwapOngoing: response.StatusCode = HttpStatusCode.Conflict; response.Content = new StringContent(Resources.Error_AutoSwapDeploymentOngoing); break; case FetchDeploymentRequestResult.Pending: // Shouldn't happen here, as we disallow deferral for this use case response.StatusCode = HttpStatusCode.Accepted; break; case FetchDeploymentRequestResult.RanSynchronously: response.StatusCode = HttpStatusCode.OK; break; case FetchDeploymentRequestResult.ConflictDeploymentInProgress: response.StatusCode = HttpStatusCode.Conflict; response.Content = new StringContent(Resources.Error_DeploymentInProgress); break; case FetchDeploymentRequestResult.ConflictRunFromRemoteZipConfigured: response.StatusCode = HttpStatusCode.Conflict; response.Content = new StringContent(Resources.Error_RunFromRemoteZipConfigured); break; default: response.StatusCode = HttpStatusCode.BadRequest; break; } return(response); }
public async Task <HttpResponseMessage> OneDeploy( [FromUri] string type = null, [FromUri] bool async = false, [FromUri] string path = null, [FromUri] bool?restart = true, [FromUri] bool?clean = null, [FromUri] bool ignoreStack = false ) { using (_tracer.Step(Constants.OneDeploy)) { JObject requestObject = null; try { if (ArmUtils.IsArmRequest(Request)) { requestObject = await Request.Content.ReadAsAsync <JObject>(); var armProperties = requestObject.Value <JObject>("properties"); type = armProperties.Value <string>("type"); async = armProperties.Value <bool>("async"); path = armProperties.Value <string>("path"); restart = armProperties.Value <bool?>("restart"); clean = armProperties.Value <bool?>("clean"); ignoreStack = armProperties.Value <bool>("ignorestack"); } } catch (Exception ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } // // 'async' is not a CSharp-ish variable name. And although it is a valid variable name, some // IDEs confuse it to be the 'async' keyword in C#. // On the other hand, isAsync is not a good name for the query-parameter. // So we use 'async' as the query parameter, and then assign it to the C# variable 'isAsync' // at the earliest. Hereon, we use just 'isAsync'. // bool isAsync = async; ArtifactType artifactType = ArtifactType.Unknown; try { artifactType = (ArtifactType)Enum.Parse(typeof(ArtifactType), type, ignoreCase: true); } catch { return(StatusCode400($"type='{type}' not recognized")); } var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = Constants.OneDeploy, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetRootPath = _environment.WebRootPath, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: Constants.OneDeploy), CommitId = null, RepositoryType = RepositoryType.None, Fetch = OneDeployFetch, DoFullBuildByDefault = false, Message = Constants.OneDeploy, WatchedFileEnabled = false, CleanupTargetDirectory = clean.GetValueOrDefault(false), RestartAllowed = restart.GetValueOrDefault(true), }; string error; switch (artifactType) { case ArtifactType.War: if (!OneDeployHelper.EnsureValidStack(OneDeployHelper.Tomcat, ignoreStack, out error)) { return(StatusCode400(error)); } // If path is non-null, we assume this is a legacy war deployment, i.e. equivalent of wardeploy if (!string.IsNullOrWhiteSpace(path)) { // // For legacy war deployments, the only path allowed is webapps/<directory-name> // if (!OneDeployHelper.IsLegacyWarPathValid(path)) { return(StatusCode400($"path='{path}'. Only allowed path when type={artifactType} is webapps/<directory-name>. Example: path=webapps/ROOT")); } deploymentInfo.TargetRootPath = Path.Combine(_environment.WebRootPath, path); deploymentInfo.Fetch = LocalZipHandler; // Legacy war deployment is equivalent to wardeploy // So always do clean deploy. deploymentInfo.CleanupTargetDirectory = true; artifactType = ArtifactType.Zip; } else { // For type=war, if no path is specified, the target file is app.war deploymentInfo.TargetFileName = "app.war"; } break; case ArtifactType.Jar: if (!OneDeployHelper.EnsureValidStack(OneDeployHelper.JavaSE, ignoreStack, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetFileName = "app.jar"; break; case ArtifactType.Ear: if (!OneDeployHelper.EnsureValidStack(OneDeployHelper.JBossEap, ignoreStack, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetFileName = "app.ear"; break; case ArtifactType.Lib: if (!OneDeployHelper.EnsureValidPath(artifactType, path, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetRootPath = OneDeployHelper.GetLibsDirectoryAbsolutePath(_environment); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromPath(deploymentInfo, path); break; case ArtifactType.Startup: deploymentInfo.TargetRootPath = OneDeployHelper.GetScriptsDirectoryAbsolutePath(_environment); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromPath(deploymentInfo, OneDeployHelper.GetStartupFileName()); break; case ArtifactType.Script: if (!OneDeployHelper.EnsureValidPath(artifactType, path, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetRootPath = OneDeployHelper.GetScriptsDirectoryAbsolutePath(_environment); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromPath(deploymentInfo, path); break; case ArtifactType.Static: if (!OneDeployHelper.EnsureValidPath(artifactType, path, out error)) { return(StatusCode400(error)); } OneDeployHelper.SetTargetSubDirectoyAndFileNameFromPath(deploymentInfo, path); break; case ArtifactType.Zip: deploymentInfo.Fetch = LocalZipHandler; deploymentInfo.TargetSubDirectoryRelativePath = path; // Deployments for type=zip default to clean=true deploymentInfo.CleanupTargetDirectory = clean.GetValueOrDefault(true); break; default: return(StatusCode400($"Artifact type '{artifactType}' not supported")); } return(await PushDeployAsync(deploymentInfo, isAsync, requestObject, artifactType)); } }
private async Task <IActionResult> PushDeployAsync(ArtifactDeploymentInfo deploymentInfo, bool isAsync, HttpContext context) { string artifactTempPath; if (string.IsNullOrWhiteSpace(deploymentInfo.TargetFileName)) { artifactTempPath = Path.Combine(_environment.ZipTempPath, Guid.NewGuid() + ".zip"); } else { artifactTempPath = Path.Combine(_environment.ZipTempPath, deploymentInfo.TargetFileName); } if (_settings.RunFromLocalZip()) { await WriteSitePackageZip(deploymentInfo, _tracer); } else { var oryxManifestFile = Path.Combine(_environment.WebRootPath, "oryx-manifest.toml"); if (FileSystemHelpers.FileExists(oryxManifestFile)) { _tracer.Step("Removing previous build artifact's manifest file"); FileSystemHelpers.DeleteFileSafe(oryxManifestFile); } try { var nodeModulesSymlinkFile = Path.Combine(_environment.WebRootPath, "node_modules"); Mono.Unix.UnixSymbolicLinkInfo i = new Mono.Unix.UnixSymbolicLinkInfo(nodeModulesSymlinkFile); if (i.FileType == Mono.Unix.FileTypes.SymbolicLink) { _tracer.Step("Removing node_modules symlink"); // TODO: Add support to remove Unix Symlink File in DeleteFileSafe // FileSystemHelpers.DeleteFileSafe(nodeModulesSymlinkFile); FileSystemHelpers.RemoveUnixSymlink(nodeModulesSymlinkFile, TimeSpan.FromSeconds(5)); } } catch (Exception) { // best effort } using (_tracer.Step("Writing artifact to {0}", artifactTempPath)) { if (!string.IsNullOrEmpty(context.Request.ContentType) && context.Request.ContentType.Contains("multipart/form-data", StringComparison.OrdinalIgnoreCase)) { FormValueProvider formModel; using (_tracer.Step("Writing zip file to {0}", artifactTempPath)) { using (var file = System.IO.File.Create(artifactTempPath)) { formModel = await Request.StreamFile(file); } } } else if (deploymentInfo.RemoteURL != null) { using (_tracer.Step("Writing zip file from packageUri to {0}", artifactTempPath)) { using (var httpClient = new HttpClient()) using (var fileStream = new FileStream(artifactTempPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) { var zipUrlRequest = new HttpRequestMessage(HttpMethod.Get, deploymentInfo.RemoteURL); var zipUrlResponse = await httpClient.SendAsync(zipUrlRequest); try { zipUrlResponse.EnsureSuccessStatusCode(); } catch (HttpRequestException hre) { _tracer.TraceError(hre, "Failed to get file from packageUri {0}", deploymentInfo.RemoteURL); throw; } using (var content = await zipUrlResponse.Content.ReadAsStreamAsync()) { await content.CopyToAsync(fileStream); } } } } else { using (var file = System.IO.File.Create(artifactTempPath)) { await Request.Body.CopyToAsync(file); } } deploymentInfo.RepositoryUrl = artifactTempPath; } } var result = await _deploymentManager.FetchDeploy(deploymentInfo, isAsync, UriHelper.GetRequestUri(Request), "HEAD"); switch (result) { case FetchDeploymentRequestResult.RunningAynschronously: if (isAsync) { // latest deployment keyword reserved to poll till deployment done Response.GetTypedHeaders().Location = new Uri(UriHelper.GetRequestUri(Request), String.Format("/api/deployments/{0}?deployer={1}&time={2}", Constants.LatestDeployment, deploymentInfo.Deployer, DateTime.UtcNow.ToString("yyy-MM-dd_HH-mm-ssZ"))); } return(Accepted()); case FetchDeploymentRequestResult.ForbiddenScmDisabled: // Should never hit this for zip push deploy _tracer.Trace("Scm is not enabled, reject all requests."); return(Forbid()); case FetchDeploymentRequestResult.ConflictAutoSwapOngoing: return(StatusCode(StatusCodes.Status409Conflict, Resources.Error_AutoSwapDeploymentOngoing)); case FetchDeploymentRequestResult.Pending: // Shouldn't happen here, as we disallow deferral for this use case return(Accepted()); case FetchDeploymentRequestResult.RanSynchronously: return(Ok()); case FetchDeploymentRequestResult.ConflictDeploymentInProgress: return(StatusCode(StatusCodes.Status409Conflict, Resources.Error_DeploymentInProgress)); case FetchDeploymentRequestResult.ConflictRunFromRemoteZipConfigured: return(StatusCode(StatusCodes.Status409Conflict, Resources.Error_RunFromRemoteZipConfigured)); default: return(BadRequest()); } }
public async Task <HttpResponseMessage> OneDeploy( [FromUri] string type = null, [FromUri] bool async = false, [FromUri] string path = null, [FromUri] bool restart = true, [FromUri] string stack = null ) { using (_tracer.Step("OnePushDeploy")) { JObject requestObject = null; try { if (ArmUtils.IsArmRequest(Request)) { requestObject = await Request.Content.ReadAsAsync <JObject>(); var armProperties = requestObject.Value <JObject>("properties"); type = armProperties.Value <string>("type"); async = armProperties.Value <bool>("async"); path = armProperties.Value <string>("path"); restart = armProperties.Value <bool>("restart"); stack = armProperties.Value <string>("stack"); } } catch (Exception ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } // // 'async' is not a CSharp-ish variable name. And although it is a valid variable name, some // IDEs confuse it to be the 'async' keyword in C#. // On the other hand, isAsync is not a good name for the query-parameter. // So we use 'async' as the query parameter, and then assign it to the C# variable 'isAsync' // at the earliest. Hereon, we use just 'isAsync'. // bool isAsync = async; var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, Deployer = Constants.OneDeploy, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: "OneDeploy"), CommitId = null, RepositoryType = RepositoryType.None, Fetch = OneDeployFetch, DoFullBuildByDefault = false, Message = "OneDeploy", WatchedFileEnabled = false, RestartAllowed = restart, }; string websiteStack = !string.IsNullOrWhiteSpace(stack) ? stack : _settings.GetValue(Constants.StackEnvVarName); ArtifactType artifactType = ArtifactType.Invalid; try { artifactType = (ArtifactType)Enum.Parse(typeof(ArtifactType), type, ignoreCase: true); } catch { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"type='{type}' not recognized")); } switch (artifactType) { case ArtifactType.War: if (!string.Equals(websiteStack, Constants.Tomcat, StringComparison.OrdinalIgnoreCase)) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"WAR files cannot be deployed to stack='{websiteStack}'. Expected stack='TOMCAT'")); } // Support for legacy war deployments // Sets TargetDirectoryPath then deploys the War file as a Zip so it can be extracted // then deployed if (!string.IsNullOrWhiteSpace(path)) { // // For legacy war deployments, the only path allowed is site/wwwroot/webapps/<directory-name> // var segments = path.Split('/'); if (segments.Length != 4 || !path.StartsWith("site/wwwroot/webapps/") || string.IsNullOrWhiteSpace(segments[3])) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"path='{path}'. Only allowed path when type={artifactType} is site/wwwroot/webapps/<directory-name>. Example: path=site/wwwroot/webapps/ROOT")); } deploymentInfo.TargetDirectoryPath = Path.Combine(_environment.RootPath, path); deploymentInfo.Fetch = LocalZipHandler; deploymentInfo.CleanupTargetDirectory = true; artifactType = ArtifactType.Zip; } else { // For type=war, the target file is app.war // As we want app.war to be deployed to wwwroot, no need to configure TargetDirectoryPath deploymentInfo.TargetFileName = "app.war"; } break; case ArtifactType.Jar: if (!string.Equals(websiteStack, Constants.JavaSE, StringComparison.OrdinalIgnoreCase)) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"JAR files cannot be deployed to stack='{websiteStack}'. Expected stack='JAVASE'")); } deploymentInfo.TargetFileName = "app.jar"; break; case ArtifactType.Ear: // Currently not supported on Windows but here for future use if (!string.Equals(websiteStack, Constants.JBossEap, StringComparison.OrdinalIgnoreCase)) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"EAR files cannot be deployed to stack='{websiteStack}'. Expected stack='JBOSSEAP'")); } deploymentInfo.TargetFileName = "app.ear"; break; case ArtifactType.Lib: if (string.IsNullOrWhiteSpace(path)) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"Path must be defined for library deployments")); } SetTargetFromPath(deploymentInfo, path); break; case ArtifactType.Startup: SetTargetFromPath(deploymentInfo, GetStartupFileName()); break; case ArtifactType.Static: if (string.IsNullOrWhiteSpace(path)) { return(Request.CreateResponse(HttpStatusCode.BadRequest, $"Path must be defined for static file deployments")); } SetTargetFromPath(deploymentInfo, path); break; case ArtifactType.Zip: deploymentInfo.Fetch = LocalZipHandler; break; default: return(Request.CreateResponse(HttpStatusCode.BadRequest, $"Artifact type '{artifactType}' not supported")); } return(await PushDeployAsync(deploymentInfo, isAsync, requestObject, artifactType)); } }
public async Task <IActionResult> OneDeploy( [FromQuery] string type = null, [FromQuery] bool async = false, [FromQuery] string path = null, [FromQuery] bool?restart = true, [FromQuery] bool?clean = null, [FromQuery] bool ignoreStack = false ) { string remoteArtifactUrl = null; using (_tracer.Step(Constants.OneDeploy)) { string deploymentId = GetExternalDeploymentId(Request); try { if (Request.MediaTypeContains("application/json")) { string jsonString; using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { jsonString = await reader.ReadToEndAsync(); } var requestJson = JObject.Parse(jsonString); if (ArmUtils.IsArmRequest(Request)) { requestJson = requestJson.Value <JObject>("properties"); type = requestJson.Value <string>("type"); async = requestJson.Value <bool>("async"); path = requestJson.Value <string>("path"); restart = requestJson.Value <bool?>("restart"); clean = requestJson.Value <bool?>("clean"); ignoreStack = requestJson.Value <bool>("ignorestack"); } remoteArtifactUrl = GetArtifactURLFromJSON(requestJson); } } catch (Exception ex) { return(StatusCode400(ex.ToString())); } // // 'async' is not a CSharp-ish variable name. And although it is a valid variable name, some // IDEs confuse it to be the 'async' keyword in C#. // On the other hand, isAsync is not a good name for the query-parameter. // So we use 'async' as the query parameter, and then assign it to the C# variable 'isAsync' // at the earliest. Hereon, we use just 'isAsync'. // bool isAsync = async; ArtifactType artifactType = ArtifactType.Unknown; try { artifactType = (ArtifactType)Enum.Parse(typeof(ArtifactType), type, ignoreCase: true); } catch { return(StatusCode400($"type='{type}' not recognized")); } var deploymentInfo = new ArtifactDeploymentInfo(_environment, _traceFactory) { ArtifactType = artifactType, AllowDeploymentWhileScmDisabled = true, Deployer = Constants.OneDeploy, IsContinuous = false, AllowDeferredDeployment = false, IsReusable = false, TargetRootPath = _environment.WebRootPath, TargetChangeset = DeploymentManager.CreateTemporaryChangeSet(message: Constants.OneDeploy), CommitId = null, ExternalDeploymentId = deploymentId, RepositoryType = RepositoryType.None, RemoteURL = remoteArtifactUrl, Fetch = OneDeployFetch, DoFullBuildByDefault = false, Message = Constants.OneDeploy, WatchedFileEnabled = false, CleanupTargetDirectory = clean.GetValueOrDefault(false), RestartAllowed = restart.GetValueOrDefault(true), }; string error; switch (artifactType) { case ArtifactType.War: if (!OneDeployHelper.EnsureValidStack(artifactType, new List <string> { OneDeployHelper.Tomcat, OneDeployHelper.JBossEap }, ignoreStack, out error)) { return(StatusCode400(error)); } // If path is non-null, we assume this is a legacy war deployment, i.e. equivalent of wardeploy if (!string.IsNullOrWhiteSpace(path)) { // // For legacy war deployments, the only path allowed is webapps/<directory-name> // if (!OneDeployHelper.EnsureValidPath(artifactType, OneDeployHelper.WwwrootDirectoryRelativePath, ref path, out error)) { return(StatusCode400(error)); } if (!OneDeployHelper.IsLegacyWarPathValid(path)) { return(StatusCode400($"path='{path}' is invalid. When type={artifactType}, the only allowed paths are webapps/<directory-name> or /home/site/wwwroot/webapps/<directory-name>. " + $"Example: path=webapps/ROOT or path=/home/site/wwwroot/webapps/ROOT")); } deploymentInfo.TargetRootPath = Path.Combine(_environment.WebRootPath, path); deploymentInfo.Fetch = LocalZipHandler; // Legacy war deployment is equivalent to wardeploy // So always do clean deploy. deploymentInfo.CleanupTargetDirectory = true; artifactType = ArtifactType.Zip; } else { // For type=war, if no path is specified, the target file is app.war deploymentInfo.TargetFileName = "app.war"; } break; case ArtifactType.Jar: if (!OneDeployHelper.EnsureValidStack(artifactType, new List <string> { OneDeployHelper.JavaSE }, ignoreStack, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetFileName = "app.jar"; break; case ArtifactType.Ear: if (!OneDeployHelper.EnsureValidStack(artifactType, new List <string> { OneDeployHelper.JBossEap }, ignoreStack, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetFileName = "app.ear"; break; case ArtifactType.Lib: if (!OneDeployHelper.EnsureValidPath(artifactType, OneDeployHelper.LibsDirectoryRelativePath, ref path, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetRootPath = OneDeployHelper.GetAbsolutePath(_environment, OneDeployHelper.LibsDirectoryRelativePath); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromRelativePath(deploymentInfo, path); break; case ArtifactType.Startup: deploymentInfo.TargetRootPath = OneDeployHelper.GetAbsolutePath(_environment, OneDeployHelper.ScriptsDirectoryRelativePath); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromRelativePath(deploymentInfo, OneDeployHelper.GetStartupFileName()); break; case ArtifactType.Script: if (!OneDeployHelper.EnsureValidPath(artifactType, OneDeployHelper.ScriptsDirectoryRelativePath, ref path, out error)) { return(StatusCode400(error)); } deploymentInfo.TargetRootPath = OneDeployHelper.GetAbsolutePath(_environment, OneDeployHelper.ScriptsDirectoryRelativePath); OneDeployHelper.SetTargetSubDirectoyAndFileNameFromRelativePath(deploymentInfo, path); break; case ArtifactType.Static: if (!OneDeployHelper.EnsureValidPath(artifactType, OneDeployHelper.WwwrootDirectoryRelativePath, ref path, out error)) { return(StatusCode400(error)); } OneDeployHelper.SetTargetSubDirectoyAndFileNameFromRelativePath(deploymentInfo, path); break; case ArtifactType.Zip: deploymentInfo.Fetch = LocalZipHandler; deploymentInfo.TargetSubDirectoryRelativePath = path; // Deployments for type=zip default to clean=true deploymentInfo.CleanupTargetDirectory = clean.GetValueOrDefault(true); break; default: return(StatusCode400($"Artifact type '{artifactType}' not supported")); } return(await PushDeployAsync(deploymentInfo, isAsync, HttpContext)); } }