Skip to content
Browse files

support functions sync triggers

  • Loading branch information...
1 parent f42028c commit 1f299029ad98e94954efbeac2d69f6f42a8b971e @suwatch suwatch committed
View
3 Common/Constants.cs
@@ -92,5 +92,8 @@ public static TimeSpan MaxAllowedExecutionTime
//Setting for VC++ for node builds
public const string VCVersion = "2015";
+
+ public const string X_MS_SITE_RESTRICTED_JWT = "X-MS-SITE-RESTRICTED-JWT";
+ public const string HTTP_HOST = "HTTP_HOST";
}
}
View
12 Kudu.Contracts/Functions/IFunctionManager.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Kudu.Core.SourceControl;
+
+namespace Kudu.Core.Functions
+{
+ public interface IFunctionManager
+ {
+ Task SyncTriggers();
+ }
+}
View
1 Kudu.Contracts/Kudu.Contracts.csproj
@@ -116,6 +116,7 @@
<Compile Include="Diagnostics\ProcessThreadInfo.cs" />
<Compile Include="Diagnostics\RuntimeInfo.cs" />
<Compile Include="Editor\VfsStatEntry.cs" />
+ <Compile Include="Functions\IFunctionManager.cs" />
<Compile Include="HashHelpers.cs" />
<Compile Include="Hooks\ConflictException.cs" />
<Compile Include="Hooks\WebHook.cs" />
View
100 Kudu.Core/Functions/FunctionManager.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Kudu.Contracts.Tracing;
+using Kudu.Core.Infrastructure;
+using Kudu.Core.Tracing;
+using Newtonsoft.Json.Linq;
+
+namespace Kudu.Core.Functions
+{
+ public class FunctionManager : IFunctionManager
+ {
+ private const string HostJsonFile = "host.json";
+ private const string FunctionJsonFile = "function.json";
+
+ private readonly IEnvironment _environment;
+ private readonly ITraceFactory _traceFactory;
+
+ public FunctionManager(IEnvironment environment, ITraceFactory traceFactory)
+ {
+ _environment = environment;
+ _traceFactory = traceFactory;
+ }
+
+ public async Task SyncTriggers()
+ {
+ var tracer = _traceFactory.GetTracer();
+ using (tracer.Step("FunctionManager.SyncTriggers"))
+ {
+ if (!IsFunctionEnabled())
+ {
+ tracer.Trace("This is not a function-enabled site!");
+ return;
+ }
+
+ var inputs = GetTriggerInputs(tracer);
+ if (inputs.Count == 0)
+ {
+ tracer.Trace("No input triggers!");
+ return;
+ }
+
+ var client = new OperationClient(tracer);
+ await client.PostAsync("/operations/settriggers", inputs);
+ }
+ }
+
+ public bool IsFunctionEnabled()
+ {
+ // this should read appSettings instead
+ var hostJson = Path.Combine(_environment.WebRootPath, HostJsonFile);
+ return FileSystemHelpers.FileExists(hostJson);
+ }
+
+ public JArray GetTriggerInputs(ITracer tracer)
+ {
+ JArray inputs = new JArray();
+ foreach (var functionJson in EnumerateFunctionFiles())
+ {
+ try
+ {
+ var json = JObject.Parse(FileSystemHelpers.ReadAllText(functionJson));
+ var binding = json.Value<JObject>("bindings");
+ foreach (JObject input in binding.Value<JArray>("input"))
+ {
+ var type = input.Value<string>("type");
+ if (type.EndsWith("Trigger", StringComparison.OrdinalIgnoreCase))
+ {
+ tracer.Trace(String.Format("Sync {0} of {1}", type, functionJson));
+ inputs.Add(input);
+ }
+ else
+ {
+ tracer.Trace(String.Format("Skip {0} of {1}", type, functionJson));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ tracer.Trace(String.Format("{0} is invalid. {1}", functionJson, ex.Message));
+ }
+ }
+
+ return inputs;
+ }
+
+ public IEnumerable<string> EnumerateFunctionFiles()
+ {
+ foreach (var functionPath in FileSystemHelpers.GetDirectories(_environment.WebRootPath))
+ {
+ var functionJson = Path.Combine(functionPath, FunctionJsonFile);
+ if (FileSystemHelpers.FileExists(functionJson))
+ {
+ yield return functionJson;
+ }
+ }
+ }
+ }
+}
View
52 Kudu.Core/Infrastructure/OperationClient.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Reflection;
+using System.Threading.Tasks;
+using Kudu.Contracts.Tracing;
+using Kudu.Core.Tracing;
+
+namespace Kudu.Core.Infrastructure
+{
+ public class OperationClient
+ {
+ private static Lazy<ProductInfoHeaderValue> _userAgent = new Lazy<ProductInfoHeaderValue>(() =>
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
+ return new ProductInfoHeaderValue("kudu", fvi.FileVersion);
+ });
+
+ private readonly ITracer _tracer;
+
+ public OperationClient(ITracer tracer)
+ {
+ _tracer = tracer;
+ }
+
+ public async Task<HttpResponseMessage> PostAsync(string path)
+ {
+ return await PostAsync<string>(path);
+ }
+
+ public async Task<HttpResponseMessage> PostAsync<T>(string path, T content = default(T))
+ {
+ var jwt = System.Environment.GetEnvironmentVariable(Constants.X_MS_SITE_RESTRICTED_JWT);
+ var host = System.Environment.GetEnvironmentVariable(Constants.HTTP_HOST);
+ using (_tracer.Step("POST " + path))
+ {
+ using (var client = new HttpClient())
+ {
+ client.BaseAddress = new Uri("https://" + host);
+ client.DefaultRequestHeaders.UserAgent.Add(_userAgent.Value);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
+
+ HttpResponseMessage response = await client.PostAsJsonAsync(path, content);
+ response.EnsureSuccessStatusCode();
+ return response;
+ }
+ }
+ }
+ }
+}
View
5 Kudu.Core/Kudu.Core.csproj
@@ -110,6 +110,9 @@
<Reference Include="System.Net.Http.Primitives">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
</Reference>
+ <Reference Include="System.Net.Http.Formatting, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll</HintPath>
+ </Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Web.Http" />
<Reference Include="System.Xml.Linq" />
@@ -151,6 +154,7 @@
<Compile Include="Deployment\StructuredTextDocument.cs" />
<Compile Include="Deployment\StructuredTextDocumentEntry.cs" />
<Compile Include="Deployment\StructuredTextLogger.cs" />
+ <Compile Include="Functions\FunctionManager.cs" />
<Compile Include="Hooks\WebHooksManager.cs" />
<Compile Include="Infrastructure\AspNet5Helper.cs" />
<Compile Include="Infrastructure\InstanceIdUtility.cs" />
@@ -227,6 +231,7 @@
<Compile Include="Infrastructure\LockFile.cs" />
<Compile Include="Infrastructure\DeploymentLockFile.cs" />
<Compile Include="Infrastructure\OperationManager.cs" />
+ <Compile Include="Infrastructure\OperationClient.cs" />
<Compile Include="Infrastructure\VsHelper.cs" />
<Compile Include="Infrastructure\XmlUtility.cs" />
<Compile Include="Settings\EnvironmentSettingsProvider.cs" />
View
1 Kudu.Core/Tracing/TraceExtensions.cs
@@ -25,6 +25,7 @@ public static class TraceExtensions
"DISGUISED-HOST",
"X-Original-URL",
"X-Forwarded-For",
+ Constants.X_MS_SITE_RESTRICTED_JWT,
"X-ARR-SSL"
},
StringComparer.OrdinalIgnoreCase);
View
34 Kudu.Core/app.config
@@ -1,15 +1,19 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
- <runtime>
- <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
- <dependentAssembly>
- <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30AD4FE6B2A6AEED" culture="neutral"/>
- <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
- </dependentAssembly>
- <dependentAssembly>
- <assemblyIdentity name="Microsoft.Web.XmlTransform" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
- <bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
- </dependentAssembly>
- </assemblyBinding>
- </runtime>
-</configuration>
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30AD4FE6B2A6AEED" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Microsoft.Web.XmlTransform" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-5.2.2.0" newVersion="5.2.2.0" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration>
View
7 Kudu.Services.Web/App_Start/NinjectServices.cs
@@ -45,6 +45,7 @@
using Owin;
using XmlSettings;
using System.Configuration;
+using Kudu.Core.Functions;
[assembly: WebActivator.PreApplicationStartMethod(typeof(Kudu.Services.Web.App_Start.NinjectServices), "Start")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(Kudu.Services.Web.App_Start.NinjectServices), "Stop")]
@@ -302,6 +303,9 @@ private static void RegisterServices(IKernel kernel)
// SiteExtensions
kernel.Bind<ISiteExtensionManager>().To<SiteExtensionManager>().InRequestScope();
+ // Functions
+ kernel.Bind<IFunctionManager>().To<FunctionManager>().InRequestScope();
+
// Command executor
kernel.Bind<ICommandExecutor>().To<CommandExecutor>().InRequestScope();
@@ -506,6 +510,9 @@ public static void RegisterRoutes(IKernel kernel, RouteCollection routes)
routes.MapHttpRoute("api-uninstall-extension", "api/siteextensions/{id}", new { controller = "SiteExtension", action = "UninstallExtension" }, new { verb = new HttpMethodConstraint("DELETE") });
routes.MapHttpRoute("api-install-update-extension", "api/siteextensions/{id}", new { controller = "SiteExtension", action = "InstallExtension" }, new { verb = new HttpMethodConstraint("PUT") });
+ // Functions
+ routes.MapHttpRouteDual("api-sync-functions", "functions/synctriggers", new { controller = "Function", action = "SyncTriggers" }, new { verb = new HttpMethodConstraint("POST") });
+
// catch all unregistered url to properly handle not found
// this is to work arounf the issue in TraceModule where we see double OnBeginRequest call
// for the same request (404 and then 200 statusCode).
View
15 Kudu.Services.Web/Tracing/TraceModule.cs
@@ -23,7 +23,7 @@ public class TraceModule : IHttpModule
// (/|$) means either "/" or end-of-line
// {0,2} means repeat pattern 0 to 2 times
- private static Regex[] _rbacWhiteListPaths = new[]
+ private static Regex[] _rbacWhiteListPaths = new[]
{
new Regex(@"^/api/siteextensions(/|$)", RegexOptions.IgnoreCase),
new Regex(@"^/api/deployments((/|$)([^/]*|$)){0,2}(/|$)$", RegexOptions.IgnoreCase),
@@ -66,6 +66,8 @@ private static void OnBeginRequest(object sender, EventArgs e)
httpContext.Response.End();
}
+ TryConvertSpecialHeadersToEnvironmentVariable(httpRequest);
+
// HACK: If it's a Razor extension, add a dummy extension to prevent WebPages for blocking it,
// as we need to serve those files via /vfs
// Yes, this is an abuse of the trace module
@@ -241,6 +243,17 @@ private static ITracer TraceStartup(HttpContext httpContext)
return attribs;
}
+ private static void TryConvertSpecialHeadersToEnvironmentVariable(HttpRequestWrapper request)
+ {
+ string siteRestrictedJwt = request.Headers.Get(Constants.X_MS_SITE_RESTRICTED_JWT);
+ if (!string.IsNullOrWhiteSpace(siteRestrictedJwt))
+ {
+ System.Environment.SetEnvironmentVariable(Constants.X_MS_SITE_RESTRICTED_JWT, siteRestrictedJwt);
+ }
+
+ System.Environment.SetEnvironmentVariable(Constants.HTTP_HOST, request.Url.Host);
+ }
+
public void Dispose()
{
}
View
37 Kudu.Services/Arm/ArmUtils.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Net.Http;
using Kudu.Contracts.Infrastructure;
+using Newtonsoft.Json;
namespace Kudu.Services.Arm
{
@@ -104,5 +106,40 @@ public static bool IsArmRequest(HttpRequestMessage request)
return armEntry;
}
+
+ public static HttpResponseMessage CreateErrorResponse(HttpRequestMessage request, HttpStatusCode statusCode, Exception exception)
+ {
+ if (IsArmRequest(request))
+ {
+ return request.CreateResponse(statusCode, new ArmErrorInfo(statusCode, exception));
+ }
+
+ return request.CreateErrorResponse(statusCode, exception);
+ }
+
+ // this error will be deserialized conforming with ARM spec
+ public class ArmErrorInfo
+ {
+ public ArmErrorInfo(HttpStatusCode code, Exception exception)
+ {
+ Error = new ArmErrorDetails
+ {
+ Code = code.ToString(),
+ Message = exception.ToString()
+ };
+ }
+
+ [JsonProperty(PropertyName = "error")]
+ public ArmErrorDetails Error { get; private set; }
+
+ public class ArmErrorDetails
+ {
+ [JsonProperty(PropertyName = "code")]
+ public string Code { get; set; }
+
+ [JsonProperty(PropertyName = "message")]
+ public string Message { get; set; }
+ }
+ }
}
}
View
46 Kudu.Services/Functions/FunctionController.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Kudu.Core.Functions;
+using Kudu.Core.Tracing;
+using Kudu.Services.Arm;
+
+namespace Kudu.Services.Functions
+{
+ [ArmControllerConfiguration]
+ public class FunctionController : ApiController
+ {
+ private readonly IFunctionManager _manager;
+ private readonly ITraceFactory _traceFactory;
+
+ public FunctionController(IFunctionManager manager, ITraceFactory traceFactory)
+ {
+ _manager = manager;
+ _traceFactory = traceFactory;
+ }
+
+ [HttpPost]
+ 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);
+ }
+ }
+ }
+ }
+}
View
1 Kudu.Services/Kudu.Services.csproj
@@ -120,6 +120,7 @@
<Compile Include="FetchHelpers\DropboxDeployInfo.cs" />
<Compile Include="FetchHelpers\DropboxHelper.cs" />
<Compile Include="FetchHelpers\DropboxInfo.cs" />
+ <Compile Include="Functions\FunctionController.cs" />
<Compile Include="GitServer\CustomGitRepositoryHandler.cs" />
<Compile Include="GitServer\GitServerHttpHandler.cs" />
<Compile Include="Hooks\WebHooksController.cs" />

0 comments on commit 1f29902

Please sign in to comment.
Something went wrong with that request. Please try again.