Skip to content

Commit

Permalink
Update "triggers" information for the function apps (#112)
Browse files Browse the repository at this point in the history
* Update "triggers" information for the function apps

* updating function trigger using new buildctl updatejson command

* Adding buildNumber in the functionApp trigger patch json payload

* Adding buildNumber in the functionApp trigger patch json payload

* Updating the method name to include triggers name to it
  • Loading branch information
SatishRanjan authored Apr 3, 2020
1 parent 06fc670 commit 36cd4c4
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 49 deletions.
80 changes: 40 additions & 40 deletions Kudu.Core/Deployment/DeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Kudu.Contracts.Infrastructure;
using Kudu.Contracts.Settings;
using Kudu.Contracts.Tracing;
using Kudu.Core.Functions;
using Kudu.Core.Helpers;
using Kudu.Core.Hooks;
using Kudu.Core.Infrastructure;
Expand Down Expand Up @@ -111,7 +112,7 @@ public IEnumerable<DeployResult> GetResults()

public DeployResult GetResult(string id)
{
return GetResult(id, _status.ActiveDeploymentId, IsDeploying);
return GetResult(id, _status.ActiveDeploymentId, IsDeploying);
}

public IEnumerable<LogEntry> GetLogEntries(string id)
Expand Down Expand Up @@ -194,30 +195,30 @@ public async Task DeployAsync(
Console.WriteLine("Deploy Async");
using (var deploymentAnalytics = new DeploymentAnalytics(_analytics, _settings))
{
Exception exception = null;
ITracer tracer = _traceFactory.GetTracer();
IDisposable deployStep = null;
ILogger innerLogger = null;
string targetBranch = null;
Exception exception = null;
ITracer tracer = _traceFactory.GetTracer();
IDisposable deployStep = null;
ILogger innerLogger = null;
string targetBranch = null;

// If we don't get a changeset, find out what branch we should be deploying and get the commit ID from it
if (changeSet == null)
{
targetBranch = _settings.GetBranch();

changeSet = repository.GetChangeSet(targetBranch);

// If we don't get a changeset, find out what branch we should be deploying and get the commit ID from it
if (changeSet == null)
{
targetBranch = _settings.GetBranch();

changeSet = repository.GetChangeSet(targetBranch);

if (changeSet == null)
{
throw new InvalidOperationException(String.Format(
"The current deployment branch is '{0}', but nothing has been pushed to it",
targetBranch));
}
throw new InvalidOperationException(String.Format(
"The current deployment branch is '{0}', but nothing has been pushed to it",
targetBranch));
}
}

string id = changeSet.Id;
_environment.CurrId = id;
IDeploymentStatusFile statusFile = null;
string id = changeSet.Id;
_environment.CurrId = id;
IDeploymentStatusFile statusFile = null;
try
{
deployStep = tracer.Step($"DeploymentManager.Deploy(id:{id})");
Expand Down Expand Up @@ -284,9 +285,8 @@ public async Task DeployAsync(
logger.Log(Resources.Log_TriggeringContainerRestart);
}

string appName = _environment.SiteRootPath.Replace("/home/apps/", "").Split("/")[0]; ;

DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason);
string appName = _environment.SiteRootPath.Replace("/home/apps/", "").Split("/")[0];
DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason, deploymentInfo.RepositoryUrl);
logger.Log($"Deployment Pod Rollout Started! Use kubectl watch deplotment {appName} to monitor the rollout status");
}
}
Expand Down Expand Up @@ -314,21 +314,21 @@ public async Task DeployAsync(
}
}

// Reload status file with latest updates
statusFile = _status.Open(id, _environment);
using (tracer.Step("Reloading status file with latest updates"))
// Reload status file with latest updates
statusFile = _status.Open(id, _environment);
using (tracer.Step("Reloading status file with latest updates"))
{
if (statusFile != null)
{
if (statusFile != null)
{
await _hooksManager.PublishEventAsync(HookEventTypes.PostDeployment, statusFile);
}
await _hooksManager.PublishEventAsync(HookEventTypes.PostDeployment, statusFile);
}
}

if (exception != null)
{
tracer.TraceError(exception);
throw new DeploymentFailedException(exception);
}
if (exception != null)
{
tracer.TraceError(exception);
throw new DeploymentFailedException(exception);
}
}
}

Expand Down Expand Up @@ -681,11 +681,11 @@ private async Task Build(
NextManifestFilePath = GetDeploymentManifestPath(id),
PreviousManifestFilePath = GetActiveDeploymentManifestPath(),
IgnoreManifest = deploymentInfo != null && deploymentInfo.CleanupTargetDirectory,
// Ignoring the manifest will cause kudusync to delete sub-directories / files
// in the destination directory that are not present in the source directory,
// without checking the manifest to see if the file was copied over to the destination
// during a previous kudusync operation. This effectively performs a clean deployment
// from the source to the destination directory
// Ignoring the manifest will cause kudusync to delete sub-directories / files
// in the destination directory that are not present in the source directory,
// without checking the manifest to see if the file was copied over to the destination
// during a previous kudusync operation. This effectively performs a clean deployment
// from the source to the destination directory
Tracer = tracer,
Logger = logger,
GlobalLogger = _globalLogger,
Expand Down
10 changes: 10 additions & 0 deletions Kudu.Core/Functions/CodeSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Kudu.Core.Functions
{
using Newtonsoft.Json;

public class CodeSpec
{
[JsonProperty(PropertyName = "packageRef")]
public PackageReference PackageRef { get; set; }
}
}
30 changes: 30 additions & 0 deletions Kudu.Core/Functions/FunctionTriggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Kudu.Core.Functions
{
public class FunctionTriggerProvider
{
/// <summary>
/// Returns the function triggers from function.json files
/// </summary>
/// <param name="providerName">The the returns json string differs as per the format expected based upon providers.
/// e.g. for "KEDA" the json string is the serialzed string of IEnumerable<ScaleTrigger>object</param>
/// <param name="functionzipFilePath">The functions file path</param>
/// <returns>The josn string of triggers in function.json</returns>
public static T GetFunctionTriggers<T>(string providerName, string functionzipFilePath)
{
if (string.IsNullOrWhiteSpace(providerName))
{
return default;
}

switch (providerName.ToLower())
{
case "keda":
var functionTriggerProvider = new KedaFunctionTriggerProvider();
return (T)functionTriggerProvider.GetFunctionTriggers(functionzipFilePath);
default:
functionTriggerProvider = new KedaFunctionTriggerProvider();
return (T)functionTriggerProvider.GetFunctionTriggers(functionzipFilePath);
}
}
}
}
117 changes: 117 additions & 0 deletions Kudu.Core/Functions/KedaFunctionTriggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;

namespace Kudu.Core.Functions
{
/// <summary>
/// Returns "<see cref="IEnumerable<ScaleTrigger>"/> for KEDA scalers"
/// </summary>
public class KedaFunctionTriggerProvider
{
public IEnumerable<ScaleTrigger> GetFunctionTriggers(string zipFilePath)
{
if (!File.Exists(zipFilePath))
{
return null;
}

List<ScaleTrigger> kedaScaleTriggers = new List<ScaleTrigger>();
using (var zip = ZipFile.OpenRead(zipFilePath))
{
var entries = zip.Entries
.Where(e => IsFunctionJson(e.FullName));

foreach (var entry in entries)
{
using (var stream = entry.Open())
{
using (var reader = new StreamReader(stream))
{
var functionTriggers = ParseFunctionJson(GetFunctionName(entry), reader.ReadToEnd());
if (functionTriggers?.Any() == true)
{
kedaScaleTriggers.AddRange(functionTriggers);
}
}
}
}
}

bool IsFunctionJson(string fullName)
{
return fullName.EndsWith(Constants.FunctionsConfigFile) &&
fullName.Count(c => c == '/' || c == '\\') == 1;
}

return kedaScaleTriggers;
}

public IEnumerable<ScaleTrigger> ParseFunctionJson(string functionName, string functionJson)
{
var json = JObject.Parse(functionJson);
if (json.TryGetValue("disabled", out JToken value))
{
string stringValue = value.ToString();
if (!bool.TryParse(stringValue, out bool disabled))
{
string expandValue = System.Environment.GetEnvironmentVariable(stringValue);
disabled = string.Equals(expandValue, "1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(expandValue, "true", StringComparison.OrdinalIgnoreCase);
}

if (disabled)
{
return null;
}
}

var excluded = json.TryGetValue("excluded", out value) && (bool)value;
if (excluded)
{
return null;
}

var triggers = new List<ScaleTrigger>();
foreach (JObject binding in (JArray)json["bindings"])
{
var type = (string)binding["type"];
if (type.EndsWith("Trigger", StringComparison.OrdinalIgnoreCase))
{
var scaleTrigger = new ScaleTrigger
{
Type = type,
Metadata = new Dictionary<string, string>()
};
foreach (var property in binding)
{
if (property.Value.Type == JTokenType.String)
{
scaleTrigger.Metadata.Add(property.Key, property.Value.ToString());
}
}

scaleTrigger.Metadata.Add("functionName", functionName);
triggers.Add(scaleTrigger);
}
}

return triggers;
}

private static string GetFunctionName(ZipArchiveEntry zipEnetry)
{
if (string.IsNullOrWhiteSpace(zipEnetry?.FullName))
{
return string.Empty;
}

return zipEnetry.FullName.Split('/').Length == 2 ? zipEnetry.FullName.Split('/')[0] : zipEnetry.FullName.Split('\\')[0];
}
}
}
10 changes: 10 additions & 0 deletions Kudu.Core/Functions/PackageReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Kudu.Core.Functions
{
using Newtonsoft.Json;

public class PackageReference
{
[JsonProperty(PropertyName = "buildVersion")]
public string BuildVersion { get; set; }
}
}
13 changes: 13 additions & 0 deletions Kudu.Core/Functions/PatchAppJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;

namespace Kudu.Core.Functions
{
public class PatchAppJson
{
[JsonProperty(PropertyName = "spec")]
public PatchSpec PatchSpec { get; set; }
}
}
16 changes: 16 additions & 0 deletions Kudu.Core/Functions/PatchSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;

namespace Kudu.Core.Functions
{
public class PatchSpec
{
[JsonProperty(PropertyName = "triggerOptions")]
public TriggerOptions TriggerOptions { get; set; }

[JsonProperty(PropertyName = "code")]
public CodeSpec Code { get; set; }
}
}
14 changes: 14 additions & 0 deletions Kudu.Core/Functions/ScaleTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Kudu.Core.Functions
{
public class ScaleTrigger
{
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }

[JsonProperty(PropertyName = "metadata")]
public IDictionary<string, string> Metadata { get; set; }
}
}
17 changes: 17 additions & 0 deletions Kudu.Core/Functions/TriggerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Kudu.Core.Functions
{
public class TriggerOptions
{
[JsonProperty(PropertyName = "triggers")]
public IEnumerable<ScaleTrigger> Triggers { get; set; }

[JsonProperty(PropertyName = "pollingInterval")]
public int? PollingInterval { get; set; }

[JsonProperty(PropertyName = "cooldownPeriod")]
public int? cooldownPeriod { get; set; }
}
}
Loading

0 comments on commit 36cd4c4

Please sign in to comment.