Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] Publish API Endpoint #104

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Kudu.Contracts/Deployment/DeploymentInfoBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,14 @@ public bool IsValid()
// If doWarmUp is set to true, the after Linux Consumption function app deployment,
// will initiate a GET request to http://appname.azurewebsites.net
public bool DoWarmUp { get; set; }

// Used to set Publish Endpoint context
public bool IsPublishRequest { get; set; }

// Used to set Publish Endpoint context
public bool ShouldRunArtifactFromPackage { get; set; }

// Used to set Publish Endpoint context
public bool ShouldBuildArtifact { get; set; }
}
}
15 changes: 13 additions & 2 deletions Kudu.Contracts/Settings/DeploymentSettingsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,18 @@ public static bool DoBuildDuringDeployment(this IDeploymentSettingsManager setti
// returning true by default here as an indicator of generally expected behavior
return value == null || StringUtils.IsTrueLike(value);
}


// Used by SiteBuilder to determine if it needs to follow the
// publish artifact format to maintain versioned artifacts for redeploying
public static bool ShouldPublishArtifacts(this IDeploymentSettingsManager settings)
{
string value = settings.GetValue(SettingsKeys.ShouldPublishArtifacts);

// A default value should be set on a per-deployment basis depending on the query param
// to the publish api, but returning false by default here as an indicator of generally expected behavior
return value != null || StringUtils.IsTrueLike(value);
}

public static bool RunFromLocalZip(this IDeploymentSettingsManager settings)
{
return settings.GetFromFromZipAppSettingValue() == "1";
Expand Down Expand Up @@ -286,7 +297,7 @@ public static bool RunFromZip(this IDeploymentSettingsManager settings)

public static int GetMaxZipPackageCount(this IDeploymentSettingsManager settings)
{
int DEFAULT_ALLOWED_ZIPS = 5;
int DEFAULT_ALLOWED_ZIPS = 10;
int MIN_ALLOWED_ZIPS = 1;
string maxZipPackageCount = settings.GetValue(SettingsKeys.MaxZipPackageCount);
if(Int32.TryParse(maxZipPackageCount, out int totalAllowedZips))
Expand Down
1 change: 1 addition & 0 deletions Kudu.Contracts/Settings/SettingsKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public static class SettingsKeys
// To make it work for Windows apps, the app-setting WEBSITE_RECYCLE_PREVIEW_ENABLED=1 needs to be defined.
public const string RestartAppAfterDeployment = "SCM_RESTART_APP_CONTAINER_AFTER_DEPLOYMENT";

public const string ShouldPublishArtifacts = "SCM_ENABLE_PUBLISH_ARTIFACTS";
public const string DoBuildDuringDeployment = "SCM_DO_BUILD_DURING_DEPLOYMENT";
public const string RunFromZipOld = "WEBSITE_RUN_FROM_ZIP"; // Old name, will eventually go away
public const string RunFromZip = "WEBSITE_RUN_FROM_PACKAGE";
Expand Down
23 changes: 23 additions & 0 deletions Kudu.Core/Deployment/DeploymentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ public static void PurgeOldDeploymentsIfNecessary(string deploymentsPath, ITrace
}
}

/// <summary>
/// Updates the RFP Manager to account for the latest zip artifact
/// </summary>
/// <param name="packageName">name of the zip file published via zip or as a result of the oryx build</param>
/// <param name="artifactPath">path where package name is stored eg: /home/site/deployments/<guid>/artifact</guid></param>
public static void UpdateLatestAndPurgeOldArtifacts(IEnvironment environment,
string packageName,
string artifactPath,
ITracer tracer,
int maxAllowedZips = 2)
{
string sitePackages = environment.SitePackagesPath;
FileSystemHelpers.EnsureDirectory(sitePackages);
string packageNameFile = Path.Combine(sitePackages, "packagename.txt");
string packagePathFile = Path.Combine(sitePackages, "packagepath.txt");

// Purge Old Artifacts, TODO change the logic to work with deployments/artifact dir
DeploymentHelper.PurgeBuildArtifactsIfNecessary(sitePackages, BuildArtifactType.Zip, tracer, totalAllowedFiles: maxAllowedZips);

File.WriteAllText(packageNameFile, packageName);
File.WriteAllText(packagePathFile, artifactPath);
}

public static void PurgeBuildArtifactsIfNecessary(string sitePackagesPath, BuildArtifactType fileExtension, ITracer tracer, int totalAllowedFiles)
{
string extension = fileExtension.ToString().ToLowerInvariant();
Expand Down
9 changes: 8 additions & 1 deletion Kudu.Core/Deployment/DeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ public async Task DeployAsync(

ILogger logger = GetLogger(changeSet.Id);

// TODO: Save Artifacts Zip at the correct path

if (needFileUpdate)
{
using (tracer.Step("Updating to specific changeset"))
Expand Down Expand Up @@ -628,11 +630,16 @@ private async Task Build(
}
}

if(deploymentInfo.IsPublishRequest)
{
perDeploymentSettings.SetValue(SettingsKeys.ShouldPublishArtifacts, "true");
}

try
{
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
4 changes: 3 additions & 1 deletion Kudu.Core/Deployment/FetchDeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ public async Task PerformDeployment(DeploymentInfoBase deploymentInfo,
// Perform the actual deployment
var changeSet = repository.GetChangeSet(deployBranch);

if (changeSet == null && !String.IsNullOrEmpty(deploymentInfo.CommitId))
if (changeSet == null
&& !String.IsNullOrEmpty(deploymentInfo.CommitId)
&& !(deploymentInfo.IsPublishRequest && deploymentInfo.ShouldRunArtifactFromPackage && !deploymentInfo.ShouldBuildArtifact))
{
throw new InvalidOperationException(String.Format("Invalid revision '{0}'!", deploymentInfo.CommitId));
}
Expand Down
38 changes: 17 additions & 21 deletions Kudu.Core/Deployment/Generator/OryxBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ public class OryxBuilder : ExternalCommandBuilder
public override string ProjectType => "Oryx-Build";

IEnvironment environment;
IDeploymentSettingsManager settings;
IDeploymentSettingsManager settingsManager;
IBuildPropertyProvider propertyProvider;
DeploymentInfoBase deploymentInfo;
string sourcePath;

public OryxBuilder(IEnvironment environment, IDeploymentSettingsManager settings, IBuildPropertyProvider propertyProvider, string sourcePath)
: base(environment, settings, propertyProvider, sourcePath)
public OryxBuilder(IEnvironment environment, IDeploymentSettingsManager settingsManager, IBuildPropertyProvider propertyProvider, DeploymentInfoBase deploymentInfo, string sourcePath)
: base(environment, settingsManager, propertyProvider, sourcePath)
{
this.environment = environment;
this.settings = settings;
this.settingsManager = settingsManager;
this.propertyProvider = propertyProvider;
this.deploymentInfo = deploymentInfo;
this.sourcePath = sourcePath;
}

Expand All @@ -36,7 +38,7 @@ public override Task Build(DeploymentContext context)
context.Logger.Log("Repository path is "+context.RepositoryPath);

// Initialize Oryx Args.
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(environment);
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(environment, settingsManager);

if (!args.SkipKuduSync)
{
Expand All @@ -60,7 +62,6 @@ public override Task Build(DeploymentContext context)
string buildCommand = args.GenerateOryxBuildCommand(context);
RunCommand(context, buildCommand, false, "Running oryx build...");

//
// Run express build setups if needed
if (args.Flags == BuildOptimizationsFlags.UseExpressBuild)
{
Expand All @@ -70,10 +71,12 @@ public override Task Build(DeploymentContext context)
}
else
{
ExpressBuilder appServiceExpressBuilder = new ExpressBuilder(environment, settings, propertyProvider, sourcePath);
ExpressBuilder appServiceExpressBuilder = new ExpressBuilder(environment, settingsManager, propertyProvider, sourcePath);
appServiceExpressBuilder.SetupExpressBuilderArtifacts(context.OutputPath, context, args);
}
}else if(args.Flags == BuildOptimizationsFlags.DeploymentV2)
}
// Publish API with RFP follows DeploymentV2 Format
else if(args.Flags == BuildOptimizationsFlags.DeploymentV2 || (deploymentInfo.IsPublishRequest && deploymentInfo.ShouldRunArtifactFromPackage))
{
SetupAppServiceArtifacts(context);
}
Expand All @@ -97,13 +100,9 @@ private static void PreOryxBuild(DeploymentContext context)

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");
string deploymentsPath = environment.DeploymentsPath;
string artifactPath = Path.Combine(deploymentsPath,context.CommitId,"artifact");

FileSystemHelpers.EnsureDirectory(sitePackages);
FileSystemHelpers.EnsureDirectory(artifactPath);

string zipAppName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip";
Expand All @@ -123,17 +122,14 @@ private void SetupAppServiceArtifacts(DeploymentContext context)
throw;
}

// Gotta remove the old zips
DeploymentHelper.PurgeOldDeploymentsIfNecessary(deploymentsPath, context.Tracer, totalAllowedDeployments: 10);

File.WriteAllText(packageNameFile, zipAppName);
File.WriteAllText(packagePathFile, artifactPath);
// Update the packagename file to ensure latest app restart loads the new zip file
DeploymentHelper.UpdateLatestAndPurgeOldArtifacts(environment, zipAppName, artifactPath, context.Tracer);
}


private void SetupFunctionAppExpressArtifacts(DeploymentContext context)
{
string sitePackages = "/home/data/SitePackages";
string sitePackages = environment.SitePackagesPath;
string packageNameFile = Path.Combine(sitePackages, "packagename.txt");
string packagePathFile = Path.Combine(sitePackages, "packagepath.txt");

Expand All @@ -155,7 +151,7 @@ private void SetupFunctionAppExpressArtifacts(DeploymentContext context)
throw;
}

// Gotta remove the old zips
// Purge old zips
DeploymentHelper.PurgeBuildArtifactsIfNecessary(sitePackages, BuildArtifactType.Zip, context.Tracer, totalAllowedFiles: 2);

File.WriteAllText(packageNameFile, zipAppName);
Expand Down
19 changes: 16 additions & 3 deletions 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,19 @@ public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSet
targetProjectPath = Path.GetFullPath(Path.Combine(repositoryRoot, targetProjectPath.TrimStart('/', '\\')));
}

// Gets precidence over RFP App Setting,
// follows the publish artifact format
// and puts artifact in /home/site/deployments/<guid>/artifact,
// sets up RFP manager
if (!settings.DoBuildDuringDeployment()
&& deploymentInfo.IsPublishRequest
&& deploymentInfo.ShouldRunArtifactFromPackage)
{
var projectPath = !String.IsNullOrEmpty(targetProjectPath) ? targetProjectPath : repositoryRoot;
// Also ensures setting up RFP if needed
return new DeploymentV2BasicBuilder(_environment, settings, _propertyProvider, deploymentInfo, repository, logger, tracer, repositoryRoot, projectPath);
}

if (settings.RunFromLocalZip())
{
return new RunFromZipSiteBuilder();
Expand All @@ -70,12 +83,12 @@ public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSet
{
if (StringUtils.IsTrueLike(enableOryxBuild))
{
return new OryxBuilder(_environment, settings, _propertyProvider, repositoryRoot);
return new OryxBuilder(_environment, settings, _propertyProvider, deploymentInfo, repositoryRoot);
}
}
else if (FunctionAppHelper.LooksLikeFunctionApp())
{
return new OryxBuilder(_environment, settings, _propertyProvider, repositoryRoot);
return new OryxBuilder(_environment, settings, _propertyProvider, deploymentInfo, repositoryRoot);
}

if (!String.IsNullOrEmpty(targetProjectPath))
Expand Down
2 changes: 1 addition & 1 deletion Kudu.Core/Deployment/ISiteBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace Kudu.Core.Deployment
{
public interface ISiteBuilderFactory
{
ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository fileFinder);
ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository fileFinder, DeploymentInfoBase deploymentInfo);
}
}
15 changes: 12 additions & 3 deletions Kudu.Core/Deployment/Oryx/AppServiceOryxArguments.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Kudu.Contracts.Settings;
using Kudu.Core.Deployment.Oryx;
using LibGit2Sharp;

namespace Kudu.Core.Deployment
{
Expand All @@ -20,7 +19,7 @@ public class AppServiceOryxArguments : IOryxArguments

public string VirtualEnv { get; set; }

public AppServiceOryxArguments()
public AppServiceOryxArguments(IDeploymentSettingsManager settingsManager)
{
RunOryxBuild = false;
SkipKuduSync = false;
Expand All @@ -29,6 +28,12 @@ public AppServiceOryxArguments()
string version = System.Environment.GetEnvironmentVariable(OryxBuildConstants.OryxEnvVars.FrameworkVersionSetting);
string buildFlags = System.Environment.GetEnvironmentVariable(OryxBuildConstants.OryxEnvVars.BuildFlagsSetting);

// Override build flags if publish artifacts enabled
if(settingsManager.ShouldPublishArtifacts())
{
buildFlags = "DeploymentV2";
}

if (string.IsNullOrEmpty(framework) ||
string.IsNullOrEmpty(version))
{
Expand Down Expand Up @@ -87,6 +92,10 @@ private void SetLanguageOptions()
return;

case Framework.PHP:
if (Flags == BuildOptimizationsFlags.None)
{
Flags = BuildOptimizationsFlags.UseTempDirectory;
}
return;
}
}
Expand Down
7 changes: 4 additions & 3 deletions Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Kudu.Core.Infrastructure;
using Kudu.Contracts.Settings;
using Kudu.Core.Infrastructure;

namespace Kudu.Core.Deployment.Oryx
{
public class OryxArgumentsFactory
{
public static IOryxArguments CreateOryxArguments(IEnvironment env)
public static IOryxArguments CreateOryxArguments(IEnvironment env, IDeploymentSettingsManager settingsManager)
{
if (FunctionAppHelper.LooksLikeFunctionApp())
{
Expand All @@ -15,7 +16,7 @@ public static IOryxArguments CreateOryxArguments(IEnvironment env)
return new FunctionAppOryxArguments();
}
}
return new AppServiceOryxArguments();
return new AppServiceOryxArguments(settingsManager);
}
}
}
2 changes: 1 addition & 1 deletion Kudu.Core/Deployment/ZipDeploymentInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public override IRepository GetRepository()
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; }

Expand Down
5 changes: 5 additions & 0 deletions Kudu.Services.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ public void Configure(IApplicationBuilder app,
new {controller = "PushDeployment", action = "WarPushDeploy"},
new {verb = new HttpMethodRouteConstraint("POST")});

// Publish Deploy
routes.MapRoute("publish-deploy", "api/publish",
new { controller = "PushDeployment", action = "PublishDeploy" },
new { verb = new HttpMethodRouteConstraint("POST") });

// Support Linux Consumption Function app on Service Fabric Mesh
routes.MapRoute("admin-instance-info", "admin/instance/info",
new {controller = "LinuxConsumptionInstanceAdmin", action = "Info"},
Expand Down
Loading