Files
Umbraco-CMS/src/Umbraco.Web/Install/Controllers/InstallApiController.cs

247 lines
9.9 KiB
C#
Raw Normal View History

using System;
2014-02-26 18:25:59 +11:00
using System.Collections.Generic;
using System.Linq;
2014-03-04 16:21:45 +11:00
using System.Reflection;
using System.Web.Http;
using Newtonsoft.Json.Linq;
2014-02-26 18:25:59 +11:00
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
2014-02-26 18:25:59 +11:00
using Umbraco.Web.Install.Models;
using Umbraco.Web.WebApi;
namespace Umbraco.Web.Install.Controllers
{
2014-02-26 18:25:59 +11:00
[AngularJsonOnlyConfiguration]
[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;
private readonly InstallStepCollection _installSteps;
private readonly ILogger _logger;
2018-11-27 13:46:43 +01:00
public InstallApiController(UmbracoContext umbracoContext, DatabaseBuilder databaseBuilder, IProfilingLogger proflog, InstallHelper installHelper, InstallStepCollection installSteps)
{
UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
_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;
}
/// <summary>
/// Gets the Umbraco context.
/// </summary>
public UmbracoContext UmbracoContext { get; }
internal InstallHelper InstallHelper { get; }
2014-03-04 16:21:45 +11: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);
return canConnect;
}
2014-02-26 18:25:59 +11:00
/// <summary>
/// Gets the install setup.
2014-02-26 18:25:59 +11:00
/// </summary>
public InstallSetup GetSetup()
{
var setup = new InstallSetup();
2014-02-26 18:25:59 +11:00
//TODO: Check for user/site token
var steps = new List<InstallSetupStep>();
2014-02-26 18:25:59 +11:00
var installSteps = _installSteps.GetStepsForCurrentInstallType().ToArray();
//only get the steps that are targetting the current install type
steps.AddRange(installSteps);
setup.Steps = steps;
2014-02-26 18:25:59 +11:00
InstallStatusTracker.Initialize(setup.InstallId, installSteps);
return setup;
2014-02-26 18:25:59 +11:00
}
public IEnumerable<Package> GetPackages()
{
var starterKits = InstallHelper.GetStarterKits();
return starterKits;
}
2014-02-26 18:25:59 +11:00
/// <summary>
/// Installs.
2014-02-26 18:25:59 +11:00
/// </summary>
public InstallProgressResultModel PostPerformInstall(InstallInstructions installModel)
2014-02-26 18:25:59 +11: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();
//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)
{
status = InstallStatusTracker.InitializeFromFile(installModel.InstallId).ToArray();
}
//create a new queue of the non-finished ones
var queue = new Queue<InstallTrackingItem>(status.Where(x => x.IsComplete == false));
while (queue.Count > 0)
{
var item = queue.Dequeue();
var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name);
// if this step has any instructions then extract them
JToken instruction;
installModel.Instructions.TryGetValue(item.Name, out instruction); // else null
// if this step doesn't require execution then continue to the next one, this is just a fail-safe check.
if (StepRequiresExecution(step, instruction) == false)
{
// set this as complete and continue
InstallStatusTracker.SetComplete(installModel.InstallId, item.Name);
continue;
}
try
{
var setupData = ExecuteStep(step, instruction);
// 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
// check if there's a custom view to return for this step
if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false)
{
return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel);
}
return new InstallProgressResultModel(false, step.Name, nextStep);
}
catch (Exception ex)
{
_logger.Error<InstallApiController>(ex, "An error occurred during installation step {Step}", step.Name);
if (ex is TargetInvocationException && ex.InnerException != null)
{
ex = ex.InnerException;
}
2014-03-04 16:21:45 +11:00
var installException = ex as InstallException;
if (installException != null)
{
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
2014-03-04 19:11:27 +01:00
{
view = installException.View,
model = installException.ViewModel,
message = installException.Message
}));
}
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
{
step = step.Name,
view = "error",
message = ex.Message
}));
}
}
InstallStatusTracker.Reset();
return new InstallProgressResultModel(true, "", "");
2014-02-26 18:25:59 +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>
/// <param name="installModel"></param>
/// <returns></returns>
private string IterateSteps(InstallSetupStep current, Queue<InstallTrackingItem> queue, Guid installId, InstallInstructions installModel)
{
while (queue.Count > 0)
{
var item = queue.Peek();
// 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.
if (current.PerformsAppRestart)
return item.Name;
var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name);
// if this step has any instructions then extract them
JToken instruction;
installModel.Instructions.TryGetValue(item.Name, out instruction); // else null
// if the step requires execution then return its name
if (StepRequiresExecution(step, instruction))
return step.Name;
// no longer requires execution, could be due to a new config change during installation
// dequeue
queue.Dequeue();
// complete
InstallStatusTracker.SetComplete(installId, step.Name);
// and continue
current = step;
}
return string.Empty;
}
// determines whether the step requires execution
internal bool StepRequiresExecution(InstallSetupStep step, JToken instruction)
{
var model = instruction?.ToObject(step.StepType);
var genericStepType = typeof(InstallSetupStep<>);
Type[] typeArgs = { step.StepType };
var typedStepType = genericStepType.MakeGenericType(typeArgs);
try
{
var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution");
return (bool) method.Invoke(step, new[] { model });
}
catch (Exception ex)
{
_logger.Error<InstallApiController>(ex, "Checking if step requires execution ({Step}) failed.", step.Name);
throw;
}
}
// executes the step
internal InstallSetupResult ExecuteStep(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"))
{
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
{
var method = typedStepType.GetMethods().Single(x => x.Name == "Execute");
return (InstallSetupResult) method.Invoke(step, new[] { model });
2014-03-04 16:21:45 +11:00
}
catch (Exception ex)
{
_logger.Error<InstallApiController>(ex, "Installation step {Step} failed.", step.Name);
2014-03-04 16:21:45 +11:00
throw;
}
}
}
}
}