diff --git a/Common/Constants.cs b/Common/Constants.cs index d39f363a2..bb6ae5e4e 100644 --- a/Common/Constants.cs +++ b/Common/Constants.cs @@ -6,5 +6,14 @@ public static class Constants public const string MappedLiveSite = "/_app"; public const string MappedDevSite = "/_devapp"; public const string RepositoryPath = "repository"; + + public const string LockPath = "locks"; + public const string DeploymentLockFile = "deployments.lock"; + public const string InitLockFile = "init.lock"; + + public const string DeploymentCachePath = "deployments"; + public const string TracePath = @"LogFiles\Git\trace"; + public const string DeploySettingsPath = "settings.xml"; + public const string TraceFile = "trace.xml"; } } \ No newline at end of file diff --git a/Kudu.Console/Kudu.Console.csproj b/Kudu.Console/Kudu.Console.csproj new file mode 100644 index 000000000..794666deb --- /dev/null +++ b/Kudu.Console/Kudu.Console.csproj @@ -0,0 +1,84 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9} + Exe + Properties + Kudu.Console + kudu + v4.0 + + + 512 + ..\ + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\packages\System.IO.Abstractions.1.4.0.12\lib\net35\System.IO.Abstractions.dll + + + + + + + + + + Constants.cs + + + + + + + + + + + {EC0ED988-2C60-4F31-A434-645E048BFD95} + Kudu.Contracts + + + {5320177C-725A-44BD-8FA6-F88D9725B46C} + Kudu.Core + + + + + + + + + \ No newline at end of file diff --git a/Kudu.Console/Program.cs b/Kudu.Console/Program.cs new file mode 100644 index 000000000..5bda90729 --- /dev/null +++ b/Kudu.Console/Program.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using Kudu.Core; +using Kudu.Core.Deployment; +using Kudu.Core.Infrastructure; +using Kudu.Core.SourceControl.Git; +using Kudu.Core.Tracing; + +namespace Kudu.Console +{ + class Program + { + static int Main(string[] args) + { + if (args.Length < 2) + { + System.Console.WriteLine("Usage: kudu.exe {appRoot} {wapTargets}"); + return 1; + } + + while (!System.Diagnostics.Debugger.IsAttached) + { + + } + + System.Environment.SetEnvironmentVariable("GIT_DIR", null, System.EnvironmentVariableTarget.Process); + + var appRoot = args[0]; + var wapTargets = args[1]; + string nugetCachePath = null; + + IEnvironment env = GetEnvironment(appRoot, nugetCachePath); + + // Setup the trace + string tracePath = Path.Combine(env.ApplicationRootPath, Constants.TracePath, Constants.TraceFile); + var traceFactory = new TracerFactory(() => new Tracer(tracePath)); + + // Calculate the lock path + string lockPath = Path.Combine(env.ApplicationRootPath, Constants.LockPath); + string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); + var deploymentLock = new LockFile(traceFactory, deploymentLockPath); + + var fs = new FileSystem(); + var buildPropertyProvider = new BuildPropertyProvider(wapTargets); + var builderFactory = new SiteBuilderFactory(buildPropertyProvider, env); + var serverRepository = new GitDeploymentRepository(env.DeploymentRepositoryPath, traceFactory); + + var logger = new ConsoleLogger(); + var deploymentManager = new DeploymentManager(serverRepository, + builderFactory, + env, + fs, + traceFactory, + deploymentLock, + logger); + + try + { + deploymentManager.Deploy(); + } + catch(System.Exception ex) + { + System.Console.Error.WriteLine(ex.Message); + + throw; + } + + if (logger.HasErrors) + { + return 1; + } + + return 0; + } + + private static IEnvironment GetEnvironment(string root, string nugetCachePath) + { + string deployPath = Path.Combine(root, Constants.WebRoot); + string deployCachePath = Path.Combine(root, Constants.DeploymentCachePath); + string deploymentRepositoryPath = Path.Combine(root, Constants.RepositoryPath); + string tempPath = Path.GetTempPath(); + string deploymentTempPath = Path.Combine(tempPath, Constants.RepositoryPath); + + return new Environment(root, + tempPath, + () => deploymentRepositoryPath, + () => null, + deployPath, + deployCachePath, + nugetCachePath); + } + + private class BuildPropertyProvider : IBuildPropertyProvider + { + private readonly string _extensionsPath; + + public BuildPropertyProvider(string extensionsPath) + { + _extensionsPath = extensionsPath; + } + + public IDictionary GetProperties() + { + return new Dictionary { + { "MSBuildExtensionsPath32", _extensionsPath } + }; + } + } + + private class ConsoleLogger : ILogger + { + public ILogger Log(string value, LogEntryType type) + { + if (type == LogEntryType.Error) + { + HasErrors = true; + + System.Console.Error.WriteLine(value); + } + else + { + System.Console.WriteLine(value); + } + + return NullLogger.Instance; + } + + public bool HasErrors { get; set; } + } + } +} diff --git a/Kudu.Console/Properties/AssemblyInfo.cs b/Kudu.Console/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..85a2d3f44 --- /dev/null +++ b/Kudu.Console/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Kudu.Console")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Kudu.Console")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1d669d37-1397-4533-8bb7-561c46217f5d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Kudu.Console/app.config b/Kudu.Console/app.config new file mode 100644 index 000000000..e36560333 --- /dev/null +++ b/Kudu.Console/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Kudu.Console/packages.config b/Kudu.Console/packages.config new file mode 100644 index 000000000..66ba7d7fa --- /dev/null +++ b/Kudu.Console/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Kudu.Contracts/Deployment/IDeploymentCommandGenerator.cs b/Kudu.Contracts/Deployment/IDeploymentCommandGenerator.cs new file mode 100644 index 000000000..6eb9a6a3a --- /dev/null +++ b/Kudu.Contracts/Deployment/IDeploymentCommandGenerator.cs @@ -0,0 +1,7 @@ +namespace Kudu.Core.Deployment +{ + public interface IDeploymentCommandGenerator + { + string GetDeploymentCommand(); + } +} diff --git a/Kudu.Contracts/IEnvironment.cs b/Kudu.Contracts/IEnvironment.cs index 7312d1984..0906bce58 100644 --- a/Kudu.Contracts/IEnvironment.cs +++ b/Kudu.Contracts/IEnvironment.cs @@ -10,6 +10,5 @@ public interface IEnvironment string ApplicationRootPath { get; } string NuGetCachePath { get; } string TempPath { get; } - string AppName { get; } } } diff --git a/Kudu.Contracts/Infrastructure/TaskExtensions.cs b/Kudu.Contracts/Infrastructure/TaskExtensions.cs index 094220485..44691df09 100644 --- a/Kudu.Contracts/Infrastructure/TaskExtensions.cs +++ b/Kudu.Contracts/Infrastructure/TaskExtensions.cs @@ -60,7 +60,7 @@ public static void Catch(this Task task, Action handler) Trace.TraceError(t.Exception.Message); handler(t.Exception); } - }, TaskContinuationOptions.OnlyOnFaulted); + }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); } public static Task Then(this Task task, Action continuation, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/Kudu.Contracts/Kudu.Contracts.csproj b/Kudu.Contracts/Kudu.Contracts.csproj index f92695311..07fa8c566 100644 --- a/Kudu.Contracts/Kudu.Contracts.csproj +++ b/Kudu.Contracts/Kudu.Contracts.csproj @@ -51,6 +51,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/Kudu.Contracts/SourceControl/IDeploymentRepository.cs b/Kudu.Contracts/SourceControl/IDeploymentRepository.cs new file mode 100644 index 000000000..d947a035a --- /dev/null +++ b/Kudu.Contracts/SourceControl/IDeploymentRepository.cs @@ -0,0 +1,13 @@ +using Kudu.Core.SourceControl; + +namespace Kudu.Core.SourceControl +{ + public interface IDeploymentRepository + { + void Clean(); + ChangeSet GetChangeSet(string id); + PushInfo GetPushInfo(); + void Update(); + void Update(string id); + } +} diff --git a/Kudu.Contracts/SourceControl/IServerRepository.cs b/Kudu.Contracts/SourceControl/IServerRepository.cs index fdb571ffd..0aa7878c2 100644 --- a/Kudu.Contracts/SourceControl/IServerRepository.cs +++ b/Kudu.Contracts/SourceControl/IServerRepository.cs @@ -4,15 +4,10 @@ namespace Kudu.Core.SourceControl { public interface IServerRepository { - string CurrentId { get; } bool Exists { get; } - PushInfo GetPushInfo(); bool Initialize(RepositoryConfiguration configuration); ChangeSet Initialize(RepositoryConfiguration configuration, string path); - ChangeSet GetChangeSet(string id); - void Update(string id); - void Update(); - void Clean(); RepositoryType GetRepositoryType(); + void Clean(); } } diff --git a/Kudu.Core/Deployment/CascadeLogger.cs b/Kudu.Core/Deployment/CascadeLogger.cs new file mode 100644 index 000000000..34b6248fe --- /dev/null +++ b/Kudu.Core/Deployment/CascadeLogger.cs @@ -0,0 +1,20 @@ +namespace Kudu.Core.Deployment +{ + internal class CascadeLogger : ILogger + { + private readonly ILogger _primary; + private readonly ILogger _secondary; + + public CascadeLogger(ILogger primary, ILogger secondary) + { + _primary = primary; + _secondary = secondary; + } + + public ILogger Log(string value, LogEntryType type) + { + _secondary.Log(value, type); + return _primary.Log(value, type); + } + } +} diff --git a/Kudu.Core/Deployment/DeploymentManager.cs b/Kudu.Core/Deployment/DeploymentManager.cs index 48db97aba..3d3f669bb 100644 --- a/Kudu.Core/Deployment/DeploymentManager.cs +++ b/Kudu.Core/Deployment/DeploymentManager.cs @@ -4,23 +4,23 @@ using System.IO; using System.IO.Abstractions; using System.Linq; -using Kudu.Contracts; using Kudu.Contracts.Infrastructure; +using Kudu.Contracts.Tracing; using Kudu.Core.Infrastructure; -using Kudu.Core.Tracing; using Kudu.Core.SourceControl; -using Kudu.Contracts.Tracing; +using Kudu.Core.Tracing; namespace Kudu.Core.Deployment { public class DeploymentManager : IDeploymentManager { - private readonly IServerRepository _serverRepository; + private readonly IDeploymentRepository _serverRepository; private readonly ISiteBuilderFactory _builderFactory; private readonly IEnvironment _environment; private readonly IFileSystem _fileSystem; private readonly ITraceFactory _traceFactory; private readonly IOperationLock _deploymentLock; + private readonly ILogger _globalLogger; private const string StatusFile = "status.xml"; private const string LogFile = "log.xml"; @@ -29,12 +29,13 @@ public class DeploymentManager : IDeploymentManager public event Action StatusChanged; - public DeploymentManager(IServerRepository serverRepository, + public DeploymentManager(IDeploymentRepository serverRepository, ISiteBuilderFactory builderFactory, IEnvironment environment, IFileSystem fileSystem, ITraceFactory traceFactory, - IOperationLock deploymentLock) + IOperationLock deploymentLock, + ILogger globalLogger) { _serverRepository = serverRepository; _builderFactory = builderFactory; @@ -42,6 +43,7 @@ public DeploymentManager(IServerRepository serverRepository, _fileSystem = fileSystem; _traceFactory = traceFactory; _deploymentLock = deploymentLock; + _globalLogger = globalLogger ?? NullLogger.Instance; } private string ActiveDeploymentId @@ -222,6 +224,8 @@ public void Deploy() else { tracer.Trace("Non-master branch deployed {0}", pushInfo.Branch.Name); + + _globalLogger.Log("Non-master branch deployed {0}", pushInfo.Branch.Name); } ReportCompleted(); @@ -236,6 +240,8 @@ public void Deploy() { tracer.Trace("Deployment '{0}' already active", id); + _globalLogger.Log("Deployment '{0}' already active", id); + ReportCompleted(); deployStep.Dispose(); return; @@ -255,6 +261,8 @@ public void Deploy() } catch (Exception ex) { + _globalLogger.Log(ex); + tracer.TraceError(ex); if (deployStep != null) @@ -377,6 +385,8 @@ private void Build(string id, ITracer tracer, IDisposable deployStep) } catch (Exception ex) { + _globalLogger.Log(ex); + tracer.TraceError(ex); innerLogger.Log(ex); @@ -412,6 +422,8 @@ private void Build(string id, ITracer tracer, IDisposable deployStep) }) .Catch(ex => { + _globalLogger.Log(ex.GetBaseException()); + // End the build step buildStep.Dispose(); @@ -610,7 +622,9 @@ private DeploymentStatusFile CreateStatusFile(string id) private ILogger GetLogger(string id) { - return new XmlLogger(_fileSystem, GetLogPath(id)); + var path = GetLogPath(id); + var xmlLogger = new XmlLogger(_fileSystem, path); + return new CascadeLogger(xmlLogger, _globalLogger); } private string GetStatusFilePath(string id, bool ensureDirectory = true) diff --git a/Kudu.Core/Deployment/NullLogger.cs b/Kudu.Core/Deployment/NullLogger.cs new file mode 100644 index 000000000..cb9531886 --- /dev/null +++ b/Kudu.Core/Deployment/NullLogger.cs @@ -0,0 +1,12 @@ +namespace Kudu.Core.Deployment +{ + public class NullLogger : ILogger + { + public static NullLogger Instance = new NullLogger(); + + public ILogger Log(string value, LogEntryType type) + { + return Instance; + } + } +} diff --git a/Kudu.Core/Environment.cs b/Kudu.Core/Environment.cs index 1dfa94cd6..4a1727ae1 100644 --- a/Kudu.Core/Environment.cs +++ b/Kudu.Core/Environment.cs @@ -1,6 +1,7 @@ using System; using Kudu.Core.Infrastructure; using Kudu.Core.SourceControl; +using System.IO; namespace Kudu.Core { @@ -12,16 +13,14 @@ public class Environment : IEnvironment private readonly Func _deploymentRepositoryPathResolver; private readonly Func _repositoryPathResolver; - public Environment(string appName, - string applicationRootPath, - string tempPath, + public Environment(string applicationRootPath, + string tempPath, Func deploymentRepositoryPathResolver, Func repositoryPathResolver, string deployPath, - string deployCachePath, + string deployCachePath, string nugetCachePath) { - AppName = appName; ApplicationRootPath = applicationRootPath; _tempPath = tempPath; _deploymentRepositoryPathResolver = deploymentRepositoryPathResolver; @@ -93,11 +92,5 @@ public string NuGetCachePath get; private set; } - - public string AppName - { - get; - private set; - } } } diff --git a/Kudu.Core/Kudu.Core.csproj b/Kudu.Core/Kudu.Core.csproj index e1dabe07f..c2436fa18 100644 --- a/Kudu.Core/Kudu.Core.csproj +++ b/Kudu.Core/Kudu.Core.csproj @@ -69,6 +69,7 @@ Properties\CommonAssemblyInfo.cs + @@ -78,12 +79,14 @@ + + diff --git a/Kudu.Core/SourceControl/Git/GitDeploymentRepository.cs b/Kudu.Core/SourceControl/Git/GitDeploymentRepository.cs new file mode 100644 index 000000000..baf3a4f88 --- /dev/null +++ b/Kudu.Core/SourceControl/Git/GitDeploymentRepository.cs @@ -0,0 +1,88 @@ +using System.IO; +using System.Linq; +using Kudu.Core.Tracing; + +namespace Kudu.Core.SourceControl.Git +{ + public class GitDeploymentRepository : IDeploymentRepository + { + private readonly GitExecutable _gitExe; + private readonly ITraceFactory _traceFactory; + private readonly GitExeRepository _repository; + + public GitDeploymentRepository(string path, ITraceFactory traceFactory) + { + _gitExe = new GitExecutable(path); + _gitExe.SetTraceLevel(2); + _traceFactory = traceFactory; + _repository = new GitExeRepository(path, traceFactory); + _repository.SetTraceLevel(2); + } + + private string PostReceiveHookPath + { + get + { + return Path.Combine(_gitExe.WorkingDirectory, ".git", "hooks", "post-receive"); + } + } + + private string PushInfoPath + { + get + { + return Path.Combine(_gitExe.WorkingDirectory, ".git", "pushinfo"); + } + } + + public void Clean() + { + _repository.Clean(); + } + + public ChangeSet GetChangeSet(string id) + { + return _repository.GetChangeSet(id); + } + + public PushInfo GetPushInfo() + { + string path = PushInfoPath; + + if (!File.Exists(path)) + { + return null; + } + + string[] pushDetails = File.ReadAllText(path).Split(' '); + + if (pushDetails.Length == 3) + { + string oldId = pushDetails[0]; + string newId = pushDetails[1]; + string reference = pushDetails[2]; + string branch = reference.Split('/').Last().Trim(); + string fullNewId = _repository.Resolve(branch); + + return new PushInfo + { + OldId = oldId, + NewId = newId, + Branch = new GitBranch(fullNewId, branch, false) + }; + } + + return null; + } + + public void Update() + { + _repository.Update(); + } + + public void Update(string id) + { + _repository.Update(id); + } + } +} diff --git a/Kudu.Core/SourceControl/Git/GitExeServer.cs b/Kudu.Core/SourceControl/Git/GitExeServer.cs index a61d90d08..94c307731 100644 --- a/Kudu.Core/SourceControl/Git/GitExeServer.cs +++ b/Kudu.Core/SourceControl/Git/GitExeServer.cs @@ -5,6 +5,7 @@ using Kudu.Contracts.Infrastructure; using Kudu.Contracts.SourceControl; using Kudu.Contracts.Tracing; +using Kudu.Core.Deployment; using Kudu.Core.Infrastructure; using Kudu.Core.Tracing; @@ -16,10 +17,11 @@ public class GitExeServer : IGitServer, IServerRepository private readonly ITraceFactory _traceFactory; private readonly GitExeRepository _repository; private readonly IOperationLock _initLock; + private readonly IDeploymentCommandGenerator _deploymentCommandGenerator; private static readonly TimeSpan _initTimeout = TimeSpan.FromMinutes(8); - public GitExeServer(string path, IOperationLock initLock, ITraceFactory traceFactory) + public GitExeServer(string path, IOperationLock initLock, IDeploymentCommandGenerator deploymentCommandGenerator, ITraceFactory traceFactory) { _gitExe = new GitExecutable(path); _gitExe.SetTraceLevel(2); @@ -27,6 +29,7 @@ public GitExeServer(string path, IOperationLock initLock, ITraceFactory traceFac _repository = new GitExeRepository(path, traceFactory); _repository.SetTraceLevel(2); _initLock = initLock; + _deploymentCommandGenerator = deploymentCommandGenerator; } private string PostReceiveHookPath @@ -45,12 +48,9 @@ private string PushInfoPath } } - public string CurrentId + public void Clean() { - get - { - return _repository.CurrentId; - } + _repository.Clean(); } public bool Exists @@ -80,11 +80,6 @@ public void AdvertiseUploadPack(Stream output) } } - public void Clean() - { - _repository.Clean(); - } - public bool Receive(Stream inputStream, Stream outputStream) { ITracer tracer = _traceFactory.GetTracer(); @@ -119,36 +114,6 @@ private void ServiceRpc(ITracer tracer, string serviceName, Stream input, Stream _gitExe.Execute(tracer, input, output, @"{0} --stateless-rpc ""{1}""", serviceName, _gitExe.WorkingDirectory); } - public PushInfo GetPushInfo() - { - string path = PushInfoPath; - - if (!File.Exists(path)) - { - return null; - } - - string[] pushDetails = File.ReadAllText(path).Split(' '); - - if (pushDetails.Length == 3) - { - string oldId = pushDetails[0]; - string newId = pushDetails[1]; - string reference = pushDetails[2]; - string branch = reference.Split('/').Last().Trim(); - string fullNewId = _repository.Resolve(branch); - - return new PushInfo - { - OldId = oldId, - NewId = newId, - Branch = new GitBranch(fullNewId, branch, false) - }; - } - - return null; - } - public ChangeSet Initialize(RepositoryConfiguration configuration, string path) { if (Exists && !_initLock.IsHeld) @@ -220,32 +185,20 @@ private void InitializeRepository(RepositoryConfiguration configuration) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(PostReceiveHookPath)); - File.WriteAllText(PostReceiveHookPath, @"#!/bin/sh + string content = @"#!/bin/sh read i echo $i > pushinfo -"); +"; + string command = "\n" + _deploymentCommandGenerator.GetDeploymentCommand(); + + File.WriteAllText(PostReceiveHookPath, content + command); } } } - public ChangeSet GetChangeSet(string id) - { - return _repository.GetChangeSet(id); - } - public RepositoryType GetRepositoryType() { return RepositoryType.Git; } - - public void Update(string id) - { - _repository.Update(id); - } - - public void Update() - { - _repository.Update(); - } } } diff --git a/Kudu.Core/SourceControl/Git/GitExecutable.cs b/Kudu.Core/SourceControl/Git/GitExecutable.cs index 68b9349b5..1c0ca4025 100644 --- a/Kudu.Core/SourceControl/Git/GitExecutable.cs +++ b/Kudu.Core/SourceControl/Git/GitExecutable.cs @@ -7,6 +7,7 @@ internal class GitExecutable : Executable public GitExecutable(string workingDirectory) : base(PathUtility.ResolveGitPath(), workingDirectory) { + //EnvironmentVariables[KnownVariables.GIT_DIR] = workingDirectory; } public void SetTraceLevel(int level) @@ -37,6 +38,7 @@ private class KnownVariables public const string GIT_CURL_VERBOSE = "GIT_CURL_VERBOSE"; public const string GIT_TRACE = "GIT_TRACE"; public const string GIT_SSL_NO_VERIFY = "GIT_SSL_NO_VERIFY"; + public const string GIT_DIR = "GIT_DIR"; } } } diff --git a/Kudu.Services.Web/App_Start/NinjectServices.cs b/Kudu.Services.Web/App_Start/NinjectServices.cs index d821f1400..4426f1b1a 100644 --- a/Kudu.Services.Web/App_Start/NinjectServices.cs +++ b/Kudu.Services.Web/App_Start/NinjectServices.cs @@ -32,15 +32,6 @@ namespace Kudu.Services.Web.App_Start { public static class NinjectServices { - private const string LockPath = "locks"; - private const string DeploymentLockFile = "deployments.lock"; - private const string InitLockFile = "init.lock"; - - private const string DeploymentCachePath = "deployments"; - private const string TracePath = @"LogFiles\Git\trace"; - private const string DeploySettingsPath = "settings.xml"; - private const string TraceFile = "trace.xml"; - /// /// Starts the application /// @@ -98,7 +89,7 @@ private static void RegisterServices(IKernel kernel) if (AppSettings.TraceEnabled) { - string tracePath = Path.Combine(environment.ApplicationRootPath, TracePath, TraceFile); + string tracePath = Path.Combine(environment.ApplicationRootPath, Constants.TracePath, Constants.TraceFile); System.Func createTracerThunk = () => new Tracer(tracePath); // First try to use the current request profiler if any, otherwise create a new one @@ -117,9 +108,9 @@ private static void RegisterServices(IKernel kernel) // Setup the deployment lock - string lockPath = Path.Combine(environment.ApplicationRootPath, LockPath); - string deploymentLockPath = Path.Combine(lockPath, DeploymentLockFile); - string initLockPath = Path.Combine(lockPath, InitLockFile); + string lockPath = Path.Combine(environment.ApplicationRootPath, Constants.LockPath); + string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); + string initLockPath = Path.Combine(lockPath, Constants.InitLockFile); var deploymentLock = new LockFile(kernel.Get(), deploymentLockPath); var initLock = new LockFile(kernel.Get(), initLockPath); @@ -132,7 +123,7 @@ private static void RegisterServices(IKernel kernel) // 3. The profile dump var paths = new[] { environment.DeploymentCachePath, - Path.Combine(environment.ApplicationRootPath, TracePath), + Path.Combine(environment.ApplicationRootPath, Constants.TracePath), }; kernel.Bind().ToMethod(context => new DiagnosticsService(paths)); @@ -146,7 +137,10 @@ private static void RegisterServices(IKernel kernel) kernel.Bind().To() .InRequestScope(); - kernel.Bind().ToMethod(context => new GitExeServer(environment.DeploymentRepositoryPath, initLock, context.Kernel.Get())) + kernel.Bind().ToMethod(context => new GitExeServer(environment.DeploymentRepositoryPath, + initLock, + context.Kernel.Get(), + context.Kernel.Get())) .InRequestScope(); kernel.Bind().To() @@ -154,41 +148,19 @@ private static void RegisterServices(IKernel kernel) .OnActivation(SubscribeForDeploymentEvents); // Git server - kernel.Bind().ToMethod(context => GetDeploymentManagerFactory(environment, initLock, deploymentLock, propertyProvider, context.Kernel.Get())); + kernel.Bind().To(); - kernel.Bind().ToMethod(context => new GitExeServer(environment.DeploymentRepositoryPath, initLock, context.Kernel.Get())) + kernel.Bind().ToMethod(context => new GitExeServer(environment.DeploymentRepositoryPath, + initLock, + context.Kernel.Get(), + context.Kernel.Get())) .InRequestScope(); - + // Editor kernel.Bind().ToMethod(context => GetEditorProjectSystem(environment, context)) .InRequestScope(); } - private static IDeploymentManagerFactory GetDeploymentManagerFactory(IEnvironment environment, - IOperationLock initLock, - IOperationLock deploymentLock, - IBuildPropertyProvider propertyProvider, - ITraceFactory traceFactory) - { - return new DeploymentManagerFactory(() => - { - var serverRepository = new GitExeServer(environment.DeploymentRepositoryPath, initLock, traceFactory); - var fileSystem = new FileSystem(); - var siteBuilderFactory = new SiteBuilderFactory(propertyProvider, environment); - - var deploymentManager = new DeploymentManager(serverRepository, - siteBuilderFactory, - environment, - fileSystem, - traceFactory, - deploymentLock); - - SubscribeForDeploymentEvents(deploymentManager); - - return deploymentManager; - }); - } - private static IProjectSystem GetEditorProjectSystem(IEnvironment environment, IContext context) { return new ProjectSystem(environment.DeploymentTargetPath); @@ -196,32 +168,28 @@ private static IProjectSystem GetEditorProjectSystem(IEnvironment environment, I private static string GetSettingsPath(IEnvironment environment) { - return Path.Combine(environment.DeploymentCachePath, DeploySettingsPath); + return Path.Combine(environment.DeploymentCachePath, Constants.DeploySettingsPath); } private static IEnvironment GetEnvironment() { - string targetRoot = PathResolver.ResolveRootPath(); - - string site = HttpRuntime.AppDomainAppVirtualPath.Trim('/'); - string root = Path.Combine(targetRoot, site); + string root = PathResolver.ResolveRootPath(); string deployPath = Path.Combine(root, Constants.WebRoot); - string deployCachePath = Path.Combine(root, DeploymentCachePath); + string deployCachePath = Path.Combine(root, Constants.DeploymentCachePath); string deploymentRepositoryPath = Path.Combine(root, Constants.RepositoryPath); string tempPath = Path.GetTempPath(); string deploymentTempPath = Path.Combine(tempPath, Constants.RepositoryPath); - return new Environment(site, - root, + return new Environment(root, tempPath, () => deploymentRepositoryPath, - () => ResolveRepositoryPath(site), + () => ResolveRepositoryPath(), deployPath, deployCachePath, AppSettings.NuGetCachePath); } - private static string ResolveRepositoryPath(string site) + private static string ResolveRepositoryPath() { string path = PathResolver.ResolveDevelopmentPath(); @@ -230,7 +198,7 @@ private static string ResolveRepositoryPath(string site) return null; } - return Path.Combine(path, site, Constants.WebRoot); + return Path.Combine(path, Constants.WebRoot); } private static void SubscribeForDeploymentEvents(IDeploymentManager deploymentManager) diff --git a/Kudu.Services.Web/Kudu.Services.Web.csproj b/Kudu.Services.Web/Kudu.Services.Web.csproj index 8e57cd2f7..aecaeab12 100644 --- a/Kudu.Services.Web/Kudu.Services.Web.csproj +++ b/Kudu.Services.Web/Kudu.Services.Web.csproj @@ -145,6 +145,7 @@ + diff --git a/Kudu.Services.Web/Services/DeploymentCommandGenerator.cs b/Kudu.Services.Web/Services/DeploymentCommandGenerator.cs new file mode 100644 index 000000000..99140b29b --- /dev/null +++ b/Kudu.Services.Web/Services/DeploymentCommandGenerator.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Web; +using Kudu.Core; +using Kudu.Core.Deployment; + +namespace Kudu.Services.Web.Services +{ + public class DeploymentCommandGenerator : IDeploymentCommandGenerator + { + private readonly IEnvironment _environment; + + public DeploymentCommandGenerator(IEnvironment environment) + { + _environment = environment; + } + + public string GetDeploymentCommand() + { + return String.Format(@"""C:/dev/github/kudu/Kudu.Console/bin/Debug/kudu.exe"" ""{0}"" ""{1}""", + _environment.ApplicationRootPath, + Path.Combine(HttpRuntime.AppDomainAppPath, "msbuild")); + } + } +} \ No newline at end of file diff --git a/Kudu.Services/GitServer/RpcService.cs b/Kudu.Services/GitServer/RpcService.cs index 33f922969..0301b6800 100644 --- a/Kudu.Services/GitServer/RpcService.cs +++ b/Kudu.Services/GitServer/RpcService.cs @@ -45,18 +45,15 @@ namespace Kudu.Services.GitServer [ServiceContract] public class RpcService { - private readonly IDeploymentManagerFactory _deploymentManagerFactory; private readonly IGitServer _gitServer; private readonly ITracer _tracer; private readonly IOperationLock _deploymentLock; public RpcService(ITracer tracer, IGitServer gitServer, - IDeploymentManagerFactory deploymentManagerFactory, IOperationLock deploymentLock) { _gitServer = gitServer; - _deploymentManagerFactory = deploymentManagerFactory; _tracer = tracer; _deploymentLock = deploymentLock; } @@ -90,34 +87,14 @@ public HttpResponseMessage ReceivePack(HttpRequestMessage request) var memoryStream = new MemoryStream(); - // Only if we've completed the receive pack should we start a deployment - if (_gitServer.Receive(GetInputStream(request), memoryStream)) - { - Deploy(); - } - else - { - _deploymentLock.Release(); - } + _gitServer.Receive(GetInputStream(request), memoryStream); + + _deploymentLock.Release(); return CreateResponse(memoryStream, "application/x-git-{0}-result".With("receive-pack")); } } - private void Deploy() - { - ThreadPool.QueueUserWorkItem(_ => - { - try { } - finally - { - // Avoid thread aborts by putting this logic in the finally - var deployer = new Deployer(_tracer, _deploymentManagerFactory, _deploymentLock); - deployer.Deploy(); - } - }); - } - private Stream GetInputStream(HttpRequestMessage request) { using (_tracer.Step("RpcService.GetInputStream")) @@ -145,67 +122,5 @@ private HttpResponseMessage CreateResponse(MemoryStream stream, string mediaType response.WriteNoCache(); return response; } - - /// - /// Let ASP.NET know about our background deployment thread. - /// - private class Deployer : IRegisteredObject - { - private readonly ITracer _tracer; - private readonly IDeploymentManagerFactory _deploymentManagerFactory; - private readonly IOperationLock _deploymentLock; - - public Deployer(ITracer tracer, - IDeploymentManagerFactory deploymentManagerFactory, - IOperationLock deploymentLock) - { - _tracer = tracer; - _deploymentManagerFactory = deploymentManagerFactory; - _deploymentLock = deploymentLock; - - // Let the hosting environment know about this object. - HostingEnvironment.RegisterObject(this); - } - - public void Stop(bool immediate) - { - if (!_deploymentLock.IsHeld) - { - return; - } - - _tracer.TraceWarning("Initiating ASP.NET shutdown. Waiting on deployment to complete."); - - // Wait until ASP.NET or IIS kills us - bool timeout = _deploymentLock.Wait(TimeSpan.MaxValue); - - if (timeout) - { - _tracer.TraceWarning("Deployment timed out."); - } - else - { - _tracer.Trace("Deployment completed."); - } - } - - public void Deploy() - { - try - { - IDeploymentManager deploymentManager = _deploymentManagerFactory.CreateDeploymentManager(); - deploymentManager.Deploy(); - } - catch (Exception ex) - { - _tracer.TraceError(ex); - } - finally - { - _deploymentLock.Release(); - HostingEnvironment.UnregisterObject(this); - } - } - } } } diff --git a/Kudu.sln b/Kudu.sln index 91e690395..6113da6f7 100644 --- a/Kudu.sln +++ b/Kudu.sln @@ -28,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kudu.Performance", "Kudu.Pe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kudu.TestHarness", "Kudu.TestHarness\Kudu.TestHarness.csproj", "{ACF3450A-8062-48D5-9C9D-8486261F290F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kudu.Console", "Kudu.Console\Kudu.Console.csproj", "{E2B0EE28-8C96-497A-AB08-605B9FD841E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -148,6 +150,16 @@ Global {ACF3450A-8062-48D5-9C9D-8486261F290F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {ACF3450A-8062-48D5-9C9D-8486261F290F}.Release|Mixed Platforms.Build.0 = Release|Any CPU {ACF3450A-8062-48D5-9C9D-8486261F290F}.Release|x86.ActiveCfg = Release|Any CPU + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Debug|Any CPU.ActiveCfg = Debug|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Debug|x86.ActiveCfg = Debug|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Debug|x86.Build.0 = Debug|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Release|Any CPU.ActiveCfg = Release|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Release|Mixed Platforms.Build.0 = Release|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Release|x86.ActiveCfg = Release|x86 + {E2B0EE28-8C96-497A-AB08-605B9FD841E9}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE