Skip to content

Commit

Permalink
OneDeploy API - /publish and /api/publish (Azure-App-Service#134)
Browse files Browse the repository at this point in the history
* OneDeploy API - /publish and /api/publish

- Adds a new /publish and /api/publish APIs for Java deployment scenarios
- This new API primarily supports Java scenarios currently, but can be easily extended for additional scenarios
- Overview of new changes
  - OneDeploy reuses bulk of the code from PushDeploymentController and DeploymentManager
  - OneDeployBuilder is a new OneDeploy specific site builder, OneDeployFetch is a OneDeploy specific Fetch handler
  - WatchedFileEnabled/RestartAllowed/ArtifactType/TargetRootPath are additional knobs introduced in DeploymentInfoBase.cs for OneDeploy specific functionality
  - The implementation inspects the WEBSITE_STACK environment variable for certain scenarios. This variable is injected by the App Service runtime
  - OneDeploy uses the new restart API (in other words, it does not rely on touching any file for triggering a recycle)
- Summary of changes to existing code:
  - A bunch of variables were renamed for readability purposes
  - ZipDeploymentInfo is now ArtifactDeploymentInfo (generalized) and is now used by OneDeploy for non-zip artifacts as well
- Note: This is primarily a port of projectkudu/kudu#3203 with some additional enhancements to OneDeploy. These enhancements will be back ported to https://github.com/projectkudu/kudu

* Minor refactoring
  • Loading branch information
shrishrirang authored Sep 11, 2020
1 parent c1cbd84 commit 972f930
Show file tree
Hide file tree
Showing 14 changed files with 688 additions and 93 deletions.
8 changes: 5 additions & 3 deletions Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static class Constants
public const string WebRoot = "wwwroot";
public const string MappedSite = "/_app";
public const string RepositoryPath = "repository";
public const string ZipTempPath = "zipdeploy";
public const string ZipExtractPath = "extracted";
public const string ZipTempDirectoryName = "zipdeploy";
public const string ArtifactStagingDirectoryName = "extracted";

public const string LockPath = "locks";
public const string DeploymentLockFile = "deployments.lock";
Expand Down Expand Up @@ -115,7 +115,7 @@ public static TimeSpan MaxAllowedExecutionTime
public const string FreeSKU = "Free";
public const string BasicSKU = "Basic";

//Setting for VC++ for node builds
// Setting for VC++ for node builds
public const string VCVersion = "2015";

public const string WebsiteSiteName = "WEBSITE_SITE_NAME";
Expand Down Expand Up @@ -165,5 +165,7 @@ public static TimeSpan MaxAllowedExecutionTime
public const string KuduFileShareMountPath = "/kudu-mnt";
public const string KuduFileSharePrefix = "kudu-mnt";
public const string EnablePersistentStorage = "ENABLE_KUDU_PERSISTENT_STORAGE";

public const string OneDeploy = "OneDeploy";
}
}
21 changes: 21 additions & 0 deletions Kudu.Contracts/Deployment/ArtifactType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Kudu.Contracts.Deployment
{
public enum ArtifactType
{
Unknown,
War,
Jar,
Ear,
Lib,
Static,
Startup,
Script,
Zip,
}
}
35 changes: 31 additions & 4 deletions Kudu.Contracts/Deployment/DeploymentInfoBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;

using Kudu.Contracts.Deployment;

namespace Kudu.Core.Deployment
{
public abstract class DeploymentInfoBase
Expand All @@ -16,6 +17,9 @@ protected DeploymentInfoBase()
IsReusable = true;
AllowDeferredDeployment = true;
DoFullBuildByDefault = true;
WatchedFileEnabled = true;
RestartAllowed = true;
ArtifactType = ArtifactType.Unknown;
}

public RepositoryType RepositoryType { get; set; }
Expand All @@ -26,16 +30,32 @@ protected DeploymentInfoBase()
// Allow deferred deployment via marker file mechanism.
public bool AllowDeferredDeployment { get; set; }
// indicating that this is a CI triggered by SCM provider
public bool IsContinuous { get; set; }
public bool IsContinuous { get; set; }

// NOTE: Do not access the request stream in the Fetch handler as it may have been closed during asynchronous scenarios
public FetchDelegate Fetch { get; set; }
public bool AllowDeploymentWhileScmDisabled { get; set; }

public IDictionary<string, string> repositorySymlinks { get; set; }

// Optional.
// By default, TargetSubDirectoryRelativePath specifies the directory to deploy to relative to /home/site/wwwroot.
// This property can be used to change the root from wwwroot to something else.
public string TargetRootPath { get; set; }

// Optional.
// Path of the directory to be deployed to. The path should be relative to the wwwroot directory.
// Example: "webapps/ROOT"
public string TargetPath { get; set; }
public string TargetSubDirectoryRelativePath { get; set; }

// Optional.
// Specifies the name of the deployed artifact.
// Example: When deploying startup files, OneDeploy will set this to startup.cmd (or startup.sh)
public string TargetFileName { get; set; }

// Optional.
// Type of artifact being deployed.
public ArtifactType ArtifactType { get; set; }

// Optional.
// Path of the file that is watched for changes by the web server.
Expand Down Expand Up @@ -71,8 +91,15 @@ public bool IsValid()
// This is used in Run-From-Zip deployments where the content of wwwroot
// won't update until after a process restart. Therefore, we copy the needed
// files into a separate folders and run sync triggers from there.
public string SyncFunctionsTriggersPath { get; set; } = null;
public string SyncFunctionsTriggersPath { get; set; } = null;

// Specifies whether to touch the watched file (example web.config, web.xml, etc) after the deployment
public bool WatchedFileEnabled { get; set; }

// Used to allow / disallow 'restart' on a per deployment basis, if needed.
// For example: OneDeploy allows clients to enable / disable 'restart'.
public bool RestartAllowed { get; set; }

// If DoSyncTriggers is set to true, the after Linux Consumption function app deployment,
// will initiate a POST request to http://appname.azurewebsites.net/admin/host/synctriggers
public bool DoSyncTriggers { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,35 @@

namespace Kudu.Core.Deployment
{
public class ZipDeploymentInfo : DeploymentInfoBase
public class ArtifactDeploymentInfo : DeploymentInfoBase
{
private readonly IEnvironment _environment;
private readonly ITraceFactory _traceFactory;

public ZipDeploymentInfo(IEnvironment environment, ITraceFactory traceFactory)
public ArtifactDeploymentInfo(IEnvironment environment, ITraceFactory traceFactory)
{
_environment = environment;
_traceFactory = traceFactory;
}

public override IRepository GetRepository()
{
// Zip "repository" does not conflict with other types, including NoRepository,
// Artifact "repository" does not conflict with other types, including NoRepository,
// so there's no call to EnsureRepository
var path = Path.Combine(_environment.ZipTempPath, Constants.ZipExtractPath);
var path = Path.Combine(_environment.ZipTempPath, Constants.ArtifactStagingDirectoryName);
return new NullRepository(path, _traceFactory);
}

public string Author { get; set; }

public string AuthorEmail { get; set; }

public string Message { get; set; }
// This is used if the deployment is Run-From-Zip
public string ZipName { get; set; }
public string Message { get; set; }

// Optional file name. Used by certain features like run-from-zip.
public string ArtifactFileName { get; set; }

// This is used when getting the zipfile from the zipURL
public string ZipURL { get; set; }
// This is used when getting the artifact file from the remote URL
public string RemoteURL { get; set; }
}
}
67 changes: 47 additions & 20 deletions Kudu.Core/Deployment/DeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Kudu.Contracts.Deployment;
using Kudu.Contracts.Infrastructure;
using Kudu.Contracts.Settings;
using Kudu.Contracts.Tracing;
Expand Down Expand Up @@ -293,12 +294,24 @@ public async Task DeployAsync(
}
}

public async Task RestartMainSiteIfNeeded(ITracer tracer, ILogger logger)
public async Task RestartMainSiteIfNeeded(ITracer tracer, ILogger logger, DeploymentInfoBase deploymentInfo)
{
// If post-deployment restart is disabled, do nothing.
if (!_settings.RestartAppOnGitDeploy())
{
return;
}

// Proceed only if 'restart' is allowed for this deployment
if (deploymentInfo != null && !deploymentInfo.RestartAllowed)
{
return;
}

if (deploymentInfo != null && deploymentInfo.Deployer == Constants.OneDeploy)
{
await PostDeploymentHelper.RestartMainSiteAsync(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger));
return;
}

if (_settings.RecylePreviewEnabled())
Expand Down Expand Up @@ -632,7 +645,7 @@ private async Task Build(
{
using (tracer.Step("Determining deployment builder"))
{
builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository);
builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository, deploymentInfo);
deploymentAnalytics.ProjectType = builder.ProjectType;
tracer.Trace("Builder is {0}", builder.GetType().Name);
}
Expand Down Expand Up @@ -703,8 +716,7 @@ private async Task Build(
{
await builder.Build(context);
builder.PostBuild(context);

await RestartMainSiteIfNeeded(tracer, logger);
await RestartMainSiteIfNeeded(tracer, logger, deploymentInfo);

if (FunctionAppHelper.LooksLikeFunctionApp() && _environment.IsOnLinuxConsumption)
{
Expand All @@ -725,14 +737,11 @@ await PostDeploymentHelper.SyncFunctionsTriggers(
new PostDeploymentTraceListener(tracer, logger),
deploymentInfo?.SyncFunctionsTriggersPath);

if (_settings.TouchWatchedFileAfterDeployment())
{
TryTouchWatchedFile(context, deploymentInfo);
}

if (_settings.RunFromLocalZip() && deploymentInfo is ZipDeploymentInfo)
TouchWatchedFileIfNeeded(_settings, deploymentInfo, context);

if (_settings.RunFromLocalZip() && deploymentInfo is ArtifactDeploymentInfo)
{
await PostDeploymentHelper.UpdatePackageName(deploymentInfo as ZipDeploymentInfo, _environment, logger);
await PostDeploymentHelper.UpdatePackageName(deploymentInfo as ArtifactDeploymentInfo, _environment, logger);
}

FinishDeployment(id, deployStep);
Expand Down Expand Up @@ -761,6 +770,19 @@ await PostDeploymentHelper.SyncFunctionsTriggers(
}
}

private static void TouchWatchedFileIfNeeded(IDeploymentSettingsManager settings, DeploymentInfoBase deploymentInfo, DeploymentContext context)
{
if (deploymentInfo != null && !deploymentInfo.WatchedFileEnabled)
{
return;
}

if (settings.TouchWatchedFileAfterDeployment())
{
TryTouchWatchedFile(context, deploymentInfo);
}
}

private void PreDeployment(ITracer tracer)
{
if (Environment.IsAzureEnvironment()
Expand Down Expand Up @@ -813,18 +835,23 @@ private static void FailDeployment(ITracer tracer, IDisposable deployStep, Deplo

private static string GetOutputPath(DeploymentInfoBase deploymentInfo, IEnvironment environment, IDeploymentSettingsManager perDeploymentSettings)
{
string targetPath = perDeploymentSettings.GetTargetPath();
string targetSubDirectoryRelativePath = perDeploymentSettings.GetTargetPath();

if (string.IsNullOrWhiteSpace(targetPath))
if (string.IsNullOrWhiteSpace(targetSubDirectoryRelativePath))
{
targetPath = deploymentInfo?.TargetPath;
}

if (!string.IsNullOrWhiteSpace(targetPath))
targetSubDirectoryRelativePath = deploymentInfo?.TargetSubDirectoryRelativePath;
}

if (deploymentInfo?.Deployer == Constants.OneDeploy)
{
return string.IsNullOrWhiteSpace(deploymentInfo?.TargetRootPath) ? environment.WebRootPath : deploymentInfo.TargetRootPath;
}

if (!string.IsNullOrWhiteSpace(targetSubDirectoryRelativePath))
{
targetPath = targetPath.Trim('\\', '/');
return Path.GetFullPath(Path.Combine(environment.WebRootPath, targetPath));
}
targetSubDirectoryRelativePath = targetSubDirectoryRelativePath.Trim('\\', '/');
return Path.GetFullPath(Path.Combine(environment.WebRootPath, targetSubDirectoryRelativePath));
}

return environment.WebRootPath;
}
Expand Down
78 changes: 78 additions & 0 deletions Kudu.Core/Deployment/Generator/OneDeployBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Kudu.Contracts.Settings;
using Kudu.Core.Infrastructure;

namespace Kudu.Core.Deployment.Generator
{
// This is the site builder used for OneDeploy scenarios
public class OneDeployBuilder : BasicBuilder
{
private DeploymentInfoBase _deploymentInfo;
private string _repositoryPath;

public OneDeployBuilder(IEnvironment environment, IDeploymentSettingsManager settings, IBuildPropertyProvider propertyProvider, string repositoryPath, string projectPath, DeploymentInfoBase deploymentInfo)
: base(environment, settings, propertyProvider, repositoryPath, projectPath)
{
_deploymentInfo = deploymentInfo;
_repositoryPath = repositoryPath;
}

public override string ProjectType
{
get { return Constants.OneDeploy; }
}

public override Task Build(DeploymentContext context)
{
context.Logger.Log($"Running build. Project type: {ProjectType}");

// Start by copying the manifest as-is so that
// manifest based deployments (Example: ZipDeploy) are unaffected
context.Logger.Log($"Copying the manifest");
FileSystemHelpers.CopyFile(context.PreviousManifestFilePath, context.NextManifestFilePath);

// If we want to clean up the target directory before copying
// the new files, use kudusync so that only unnecessary files are
// deleted. This has two benefits:
// 1. This is faster than deleting the target directory before copying the source dir.
// 2. Minimizes chances of failure in deleting a directory due to open handles.
// This is especially useful when a target directory is present in the source and
// need not be deleted.
if (_deploymentInfo.CleanupTargetDirectory)
{
context.Logger.Log($"Clean deploying to {context.OutputPath}");

// We do not want to use the manifest for OneDeploy. Use an empty manifest file.
// This way we don't interfere with manifest based deployments.
string tempManifestPath = null;
try
{
tempManifestPath = Path.GetTempFileName();
context.PreviousManifestFilePath = context.NextManifestFilePath = tempManifestPath;
base.Build(context);
}
finally
{
if (!string.IsNullOrWhiteSpace(tempManifestPath))
{
FileSystemHelpers.DeleteFileSafe(tempManifestPath);
}
}
}
else
{
context.Logger.Log($"Incrementally deploying to {context.OutputPath}");
FileSystemHelpers.CopyDirectoryRecursive(_repositoryPath, context.OutputPath);
}

context.Logger.Log($"Build completed succesfully.");

return Task.CompletedTask;
}
}
}
8 changes: 7 additions & 1 deletion Kudu.Core/Deployment/Generator/SiteBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public SiteBuilderFactory(IBuildPropertyProvider propertyProvider, IEnvironment
_environment = environment;
}

public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository repository)
public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository repository, DeploymentInfoBase deploymentInfo)
{
string repositoryRoot = repository.RepositoryPath;

Expand Down Expand Up @@ -53,6 +53,12 @@ public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSet
targetProjectPath = Path.GetFullPath(Path.Combine(repositoryRoot, targetProjectPath.TrimStart('/', '\\')));
}

if (deploymentInfo != null && deploymentInfo.Deployer == Constants.OneDeploy)
{
var projectPath = !String.IsNullOrEmpty(targetProjectPath) ? targetProjectPath : repositoryRoot;
return new OneDeployBuilder(_environment, settings, _propertyProvider, repositoryRoot, projectPath, deploymentInfo);
}

if (settings.RunFromLocalZip())
{
return new RunFromZipSiteBuilder();
Expand Down
Loading

0 comments on commit 972f930

Please sign in to comment.