private async Task <HttpResponseMessage> CreateOrUpdateHelper(string name, Task <FunctionEnvelope> functionEnvelopeBuilder) { if (!FunctionNameValidationRegex.IsMatch(name)) { // it returns the same error object if the PUT request does not come from Arm return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, new ArgumentException($"{name} is not a valid function name"))); } var tracer = _traceFactory.GetTracer(); using (tracer.Step($"FunctionsController.CreateOrUpdate({name})")) { var functionEnvelope = await functionEnvelopeBuilder; bool configChanged = false; functionEnvelope = await _manager.CreateOrUpdateAsync(name, functionEnvelope, () => { configChanged = true; }); AddFunctionAppIdToEnvelope(functionEnvelope); if (configChanged) { // Fire and forget SyncTrigger request. FireSyncTriggers(tracer); } return(Request.CreateResponse(HttpStatusCode.Created, ArmUtils.AddEnvelopeOnArmRequest(functionEnvelope, Request))); } }
public async Task <HttpResponseMessage> GetSecrets(string name) { // "name".json will be created as function keys, (runtime will always have lowercase "name") // kudu REST api does not care, "name" can be camelcase (ex: function portal) // windows file system is case insensitive, but this might not work in linux var tracer = _traceFactory.GetTracer(); using (tracer.Step($"FunctionsController.GetSecrets({name})")) { try { return(Request.CreateResponse(HttpStatusCode.OK, await _manager.GetFunctionSecretsAsync(name))); } catch (InvalidOperationException ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.Conflict, ex)); } } }
public override void OnException(HttpActionExecutedContext context) { var statusCode = HttpStatusCode.InternalServerError; if (context.Exception is FileNotFoundException) { statusCode = HttpStatusCode.NotFound; } else if (context.Exception is InvalidOperationException) { statusCode = HttpStatusCode.Conflict; } else if (context.Exception is ArgumentException) { statusCode = HttpStatusCode.BadRequest; } context.Response = ArmUtils.CreateErrorResponse(context.Request, statusCode, context.Exception); }
public async Task <HttpResponseMessage> SyncTriggers() { var tracer = _traceFactory.GetTracer(); using (tracer.Step("FunctionController.SyncTriggers")) { try { await _manager.SyncTriggers(); return(Request.CreateResponse(HttpStatusCode.OK)); } catch (Exception ex) { tracer.TraceError(ex); return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } } }
private async Task <HttpResponseMessage> PushDeployAsync(ZipDeploymentInfo deploymentInfo, bool isAsync) { var content = Request.Content; var isRequestJSON = content.Headers?.ContentType?.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase); if (isRequestJSON == true) { try { var requestObject = await Request.Content.ReadAsAsync <JObject>(); deploymentInfo.ZipURL = ArmUtils.IsArmRequest(Request) ? GetZipURLFromARMJSON(requestObject) : GetZipURLFromJSON(requestObject); } catch (Exception ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } } else { 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; } } isAsync = ArmUtils.IsArmRequest(Request) ? true : isAsync; var result = await _deploymentManager.FetchDeploy(deploymentInfo, isAsync, UriHelper.GetRequestUri(Request), "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(); // 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(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"))); } 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)); } }
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)); } }
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) { await _deploymentLock.LockHttpOperationAsync(async() => { 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; } }, "Preparing zip package"); } // 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) { await _deploymentLock.LockHttpOperationAsync(async() => { 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; }, "Preparing zip package"); } 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"))); response.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(ScmHostingConfigurations.ArmRetryAfterSeconds)); } 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.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(ScmHostingConfigurations.ArmRetryAfterSeconds)); } 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); }
protected override async Task <HttpResponseMessage> CreateDirectoryPutResponse(DirectoryInfoBase info, string localFilePath) { try { var isRequestJSON = Request.Content.Headers?.ContentType?.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase); var targetPath = localFilePath; var isArmTemplate = false; JObject requestContent = null; Uri packageUri = null; if (isRequestJSON == true) { requestContent = await Request.Content.ReadAsAsync <JObject>(); var payload = requestContent; if (ArmUtils.IsArmRequest(Request)) { payload = payload.Value <JObject>("properties"); isArmTemplate = ArmUtils.IsAzureResourceManagerUserAgent(Request); } var uri = payload?.Value <string>("packageUri"); if (!Uri.TryCreate(uri, UriKind.Absolute, out packageUri)) { throw new InvalidOperationException($"Payload contains invalid '{uri}' packageUri property"); } var path = payload?.Value <string>("path"); if (!string.IsNullOrEmpty(path)) { targetPath = Path.Combine(targetPath, path); FileSystemHelpers.CreateDirectory(targetPath); } } using (packageUri == null ? Tracer.Step($"Extracting content to {targetPath}") : Tracer.Step("Extracting content from {0} to {1}", StringUtils.ObfuscatePath(packageUri.AbsoluteUri), targetPath)) { var content = packageUri == null ? Request.Content : await DeploymentHelper.GetArtifactContentFromURL(new ArtifactDeploymentInfo(null, null) { RemoteURL = packageUri.AbsoluteUri }, Tracer); using (var stream = await content.ReadAsStreamAsync()) { // The unzipping is done over the existing folder, without first removing existing files. // Hence it's more of a PATCH than a PUT. We should consider supporting both with the right semantic. // Though a true PUT at the root would be scary as it would wipe all existing files! var zipArchive = new ZipArchive(stream, ZipArchiveMode.Read); zipArchive.Extract(targetPath, Tracer); } } if (isArmTemplate && requestContent != null) { requestContent.Value <JObject>("properties").Add("provisioningState", "Succeeded"); return(Request.CreateResponse(HttpStatusCode.OK, requestContent)); } return(Request.CreateResponse(HttpStatusCode.OK)); } catch (Exception ex) { return(ArmUtils.CreateErrorResponse(Request, HttpStatusCode.BadRequest, ex)); } }