2014-02-26 04:15:14 +11:00
|
|
|
|
using System;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using System.Collections.Generic;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using System.Linq;
|
2014-03-04 16:21:45 +11:00
|
|
|
|
using System.Reflection;
|
2019-01-16 02:35:21 +11:00
|
|
|
|
using System.Threading.Tasks;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
using System.Web.Http;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using Newtonsoft.Json.Linq;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using Umbraco.Core;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using Umbraco.Core.Logging;
|
2017-12-18 18:26:32 +01:00
|
|
|
|
using Umbraco.Core.Migrations.Install;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using Umbraco.Web.Install.Models;
|
|
|
|
|
|
using Umbraco.Web.WebApi;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Install.Controllers
|
|
|
|
|
|
{
|
2014-02-26 18:25:59 +11:00
|
|
|
|
[AngularJsonOnlyConfiguration]
|
2014-02-26 04:15:14 +11:00
|
|
|
|
[HttpInstallAuthorize]
|
|
|
|
|
|
public class InstallApiController : ApiController
|
|
|
|
|
|
{
|
2016-11-29 10:31:25 +01:00
|
|
|
|
private readonly DatabaseBuilder _databaseBuilder;
|
2018-11-27 13:46:43 +01:00
|
|
|
|
private readonly IProfilingLogger _proflog;
|
2018-04-06 13:51:54 +10:00
|
|
|
|
private readonly InstallStepCollection _installSteps;
|
2016-09-01 19:06:08 +02:00
|
|
|
|
private readonly ILogger _logger;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
|
2019-02-14 12:40:45 +01:00
|
|
|
|
public InstallApiController(DatabaseBuilder databaseBuilder, IProfilingLogger proflog, InstallHelper installHelper, InstallStepCollection installSteps)
|
2014-02-26 04:15:14 +11:00
|
|
|
|
{
|
2018-04-06 13:51:54 +10:00
|
|
|
|
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
|
|
|
|
|
|
_proflog = proflog ?? throw new ArgumentNullException(nameof(proflog));
|
|
|
|
|
|
_installSteps = installSteps;
|
|
|
|
|
|
InstallHelper = installHelper;
|
2018-11-27 13:46:43 +01:00
|
|
|
|
_logger = _proflog;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-06 13:51:54 +10:00
|
|
|
|
internal InstallHelper InstallHelper { get; }
|
2014-03-04 16:21:45 +11:00
|
|
|
|
|
2014-03-05 20:03:45 +01:00
|
|
|
|
public bool PostValidateDatabaseConnection(DatabaseModel model)
|
|
|
|
|
|
{
|
2018-12-18 10:39:39 +01:00
|
|
|
|
var canConnect = _databaseBuilder.CanConnect(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth);
|
2014-04-17 19:55:17 +10:00
|
|
|
|
return canConnect;
|
2014-03-05 20:03:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// <summary>
|
2016-09-01 19:06:08 +02:00
|
|
|
|
/// Gets the install setup.
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public InstallSetup GetSetup()
|
|
|
|
|
|
{
|
2014-03-04 19:20:36 +11:00
|
|
|
|
var setup = new InstallSetup();
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: Check for user/site token
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-02-26 16:01:31 +01:00
|
|
|
|
var steps = new List<InstallSetupStep>();
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2018-04-06 13:51:54 +10:00
|
|
|
|
var installSteps = _installSteps.GetStepsForCurrentInstallType().ToArray();
|
2014-03-04 19:20:36 +11:00
|
|
|
|
|
2019-01-26 10:52:19 -05:00
|
|
|
|
//only get the steps that are targeting the current install type
|
2014-03-04 19:20:36 +11:00
|
|
|
|
steps.AddRange(installSteps);
|
2014-03-04 11:16:42 +11:00
|
|
|
|
setup.Steps = steps;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-03-04 19:20:36 +11:00
|
|
|
|
InstallStatusTracker.Initialize(setup.InstallId, installSteps);
|
|
|
|
|
|
|
2014-03-04 11:16:42 +11:00
|
|
|
|
return setup;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
}
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
2014-03-03 08:57:00 +01:00
|
|
|
|
public IEnumerable<Package> GetPackages()
|
|
|
|
|
|
{
|
2018-04-06 13:51:54 +10:00
|
|
|
|
var starterKits = InstallHelper.GetStarterKits();
|
2014-11-30 15:50:55 +01:00
|
|
|
|
return starterKits;
|
2014-03-03 08:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// <summary>
|
2016-09-01 19:06:08 +02:00
|
|
|
|
/// Installs.
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// </summary>
|
2019-01-16 02:35:21 +11:00
|
|
|
|
public async Task<InstallProgressResultModel> PostPerformInstall(InstallInstructions installModel)
|
2014-02-26 18:25:59 +11:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
if (installModel == null) throw new ArgumentNullException(nameof(installModel));
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-03-04 16:21:45 +11:00
|
|
|
|
var status = InstallStatusTracker.GetStatus().ToArray();
|
2014-03-04 19:20:36 +11:00
|
|
|
|
//there won't be any statuses returned if the app pool has restarted so we need to re-read from file.
|
2014-03-04 16:21:45 +11:00
|
|
|
|
if (status.Any() == false)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-04 19:20:36 +11:00
|
|
|
|
status = InstallStatusTracker.InitializeFromFile(installModel.InstallId).ToArray();
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//create a new queue of the non-finished ones
|
|
|
|
|
|
var queue = new Queue<InstallTrackingItem>(status.Where(x => x.IsComplete == false));
|
|
|
|
|
|
while (queue.Count > 0)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
var item = queue.Dequeue();
|
2018-04-06 13:51:54 +10:00
|
|
|
|
var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name);
|
2014-03-04 11:16:42 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// if this step has any instructions then extract them
|
2019-01-16 02:35:21 +11:00
|
|
|
|
installModel.Instructions.TryGetValue(item.Name, out var instruction); // else null
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
|
|
|
|
|
// if this step doesn't require execution then continue to the next one, this is just a fail-safe check.
|
2014-03-05 14:30:17 +11:00
|
|
|
|
if (StepRequiresExecution(step, instruction) == false)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// set this as complete and continue
|
|
|
|
|
|
InstallStatusTracker.SetComplete(installModel.InstallId, item.Name);
|
2014-03-05 11:34:42 +11:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2019-01-16 02:35:21 +11:00
|
|
|
|
var setupData = await ExecuteStepAsync(step, instruction);
|
2014-03-03 08:57:00 +01:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// update the status
|
|
|
|
|
|
InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData?.SavedStepData);
|
|
|
|
|
|
|
|
|
|
|
|
// determine's the next step in the queue and dequeue's any items that don't need to execute
|
|
|
|
|
|
var nextStep = IterateSteps(step, queue, installModel.InstallId, installModel);
|
2014-03-04 16:21:45 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// check if there's a custom view to return for this step
|
2014-03-05 11:34:42 +11:00
|
|
|
|
if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel);
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
|
|
|
|
|
return new InstallProgressResultModel(false, step.Name, nextStep);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2018-08-17 15:41:58 +01:00
|
|
|
|
_logger.Error<InstallApiController>(ex, "An error occurred during installation step {Step}", step.Name);
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
if (ex is TargetInvocationException && ex.InnerException != null)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-05 11:34:42 +11:00
|
|
|
|
ex = ex.InnerException;
|
|
|
|
|
|
}
|
2014-03-04 16:21:45 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
var installException = ex as InstallException;
|
|
|
|
|
|
if (installException != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
|
2014-03-04 19:11:27 +01:00
|
|
|
|
{
|
2014-03-05 11:34:42 +11:00
|
|
|
|
view = installException.View,
|
|
|
|
|
|
model = installException.ViewModel,
|
|
|
|
|
|
message = installException.Message
|
|
|
|
|
|
}));
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
|
|
|
|
|
|
{
|
|
|
|
|
|
step = step.Name,
|
|
|
|
|
|
view = "error",
|
|
|
|
|
|
message = ex.Message
|
|
|
|
|
|
}));
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InstallStatusTracker.Reset();
|
2014-03-05 10:32:59 +11:00
|
|
|
|
return new InstallProgressResultModel(true, "", "");
|
2014-02-26 18:25:59 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// We'll peek ahead and check if it's RequiresExecution is returning true. If it
|
|
|
|
|
|
/// is not, we'll dequeue that step and peek ahead again (recurse)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="current"></param>
|
|
|
|
|
|
/// <param name="queue"></param>
|
|
|
|
|
|
/// <param name="installId"></param>
|
2014-03-05 14:30:17 +11:00
|
|
|
|
/// <param name="installModel"></param>
|
2014-03-05 11:34:42 +11:00
|
|
|
|
/// <returns></returns>
|
2016-09-01 19:06:08 +02:00
|
|
|
|
private string IterateSteps(InstallSetupStep current, Queue<InstallTrackingItem> queue, Guid installId, InstallInstructions installModel)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
while (queue.Count > 0)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
var item = queue.Peek();
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// if the current step restarts the app pool then we must simply return the next one in the queue,
|
|
|
|
|
|
// we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution
|
|
|
|
|
|
// will rely on that too.
|
2014-03-05 14:30:17 +11:00
|
|
|
|
if (current.PerformsAppRestart)
|
2016-09-01 19:06:08 +02:00
|
|
|
|
return item.Name;
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2018-04-06 13:51:54 +10:00
|
|
|
|
var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name);
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
|
|
|
|
|
// if this step has any instructions then extract them
|
|
|
|
|
|
JToken instruction;
|
|
|
|
|
|
installModel.Instructions.TryGetValue(item.Name, out instruction); // else null
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// if the step requires execution then return its name
|
2014-03-05 14:30:17 +11:00
|
|
|
|
if (StepRequiresExecution(step, instruction))
|
|
|
|
|
|
return step.Name;
|
|
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// no longer requires execution, could be due to a new config change during installation
|
|
|
|
|
|
// dequeue
|
2014-03-05 11:34:42 +11:00
|
|
|
|
queue.Dequeue();
|
|
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// complete
|
|
|
|
|
|
InstallStatusTracker.SetComplete(installId, step.Name);
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// and continue
|
|
|
|
|
|
current = step;
|
2014-03-05 11:34:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// determines whether the step requires execution
|
2014-03-05 14:30:17 +11:00
|
|
|
|
internal bool StepRequiresExecution(InstallSetupStep step, JToken instruction)
|
|
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
var model = instruction?.ToObject(step.StepType);
|
2014-03-05 14:30:17 +11:00
|
|
|
|
var genericStepType = typeof(InstallSetupStep<>);
|
|
|
|
|
|
Type[] typeArgs = { step.StepType };
|
|
|
|
|
|
var typedStepType = genericStepType.MakeGenericType(typeArgs);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution");
|
2016-09-01 19:06:08 +02:00
|
|
|
|
return (bool) method.Invoke(step, new[] { model });
|
2014-03-05 14:30:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2018-08-17 15:41:58 +01:00
|
|
|
|
_logger.Error<InstallApiController>(ex, "Checking if step requires execution ({Step}) failed.", step.Name);
|
2014-03-05 14:30:17 +11:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// executes the step
|
2019-01-16 02:35:21 +11:00
|
|
|
|
internal async Task<InstallSetupResult> ExecuteStepAsync(InstallSetupStep step, JToken instruction)
|
2014-02-26 18:25:59 +11:00
|
|
|
|
{
|
2018-08-29 11:41:04 +02:00
|
|
|
|
using (_proflog.TraceDuration<InstallApiController>($"Executing installation step: '{step.Name}'.", "Step completed"))
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2016-09-01 19:06:08 +02:00
|
|
|
|
var model = instruction?.ToObject(step.StepType);
|
2014-03-04 16:21:45 +11:00
|
|
|
|
var genericStepType = typeof(InstallSetupStep<>);
|
|
|
|
|
|
Type[] typeArgs = { step.StepType };
|
|
|
|
|
|
var typedStepType = genericStepType.MakeGenericType(typeArgs);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2019-01-16 02:35:21 +11:00
|
|
|
|
var method = typedStepType.GetMethods().Single(x => x.Name == "ExecuteAsync");
|
|
|
|
|
|
var task = (Task<InstallSetupResult>) method.Invoke(step, new[] { model });
|
|
|
|
|
|
return await task;
|
2014-03-04 16:21:45 +11:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2018-08-17 15:41:58 +01:00
|
|
|
|
_logger.Error<InstallApiController>(ex, "Installation step {Step} failed.", step.Name);
|
2014-03-04 16:21:45 +11:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2014-02-26 04:15:14 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|