1+ using Microsoft . Extensions . DependencyInjection ;
2+ using Microsoft . Extensions . Options ;
13using Umbraco . Cms . Core ;
24using Umbraco . Cms . Core . Configuration ;
5+ using Umbraco . Cms . Core . Configuration . Models ;
6+ using Umbraco . Cms . Core . DependencyInjection ;
37using Umbraco . Cms . Core . Events ;
48using Umbraco . Cms . Core . Exceptions ;
59using Umbraco . Cms . Core . Logging ;
@@ -23,19 +27,39 @@ public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpg
2327 private readonly IProfilingLogger _profilingLogger ;
2428 private readonly IRuntimeState _runtimeState ;
2529 private readonly IUmbracoVersion _umbracoVersion ;
30+ private readonly UnattendedSettings _unattendedSettings ;
2631
2732 public UnattendedUpgrader (
2833 IProfilingLogger profilingLogger ,
2934 IUmbracoVersion umbracoVersion ,
3035 DatabaseBuilder databaseBuilder ,
3136 IRuntimeState runtimeState ,
32- PackageMigrationRunner packageMigrationRunner )
37+ PackageMigrationRunner packageMigrationRunner ,
38+ IOptions < UnattendedSettings > unattendedSettings )
3339 {
3440 _profilingLogger = profilingLogger ?? throw new ArgumentNullException ( nameof ( profilingLogger ) ) ;
3541 _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException ( nameof ( umbracoVersion ) ) ;
3642 _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException ( nameof ( databaseBuilder ) ) ;
3743 _runtimeState = runtimeState ?? throw new ArgumentNullException ( nameof ( runtimeState ) ) ;
3844 _packageMigrationRunner = packageMigrationRunner ;
45+ _unattendedSettings = unattendedSettings . Value ;
46+ }
47+
48+ [ Obsolete ( "Use constructor that takes IOptions<UnattendedSettings>, this will be removed in V16" ) ]
49+ public UnattendedUpgrader (
50+ IProfilingLogger profilingLogger ,
51+ IUmbracoVersion umbracoVersion ,
52+ DatabaseBuilder databaseBuilder ,
53+ IRuntimeState runtimeState ,
54+ PackageMigrationRunner packageMigrationRunner )
55+ : this (
56+ profilingLogger ,
57+ umbracoVersion ,
58+ databaseBuilder ,
59+ runtimeState ,
60+ packageMigrationRunner ,
61+ StaticServiceProvider . Instance . GetRequiredService < IOptions < UnattendedSettings > > ( ) )
62+ {
3963 }
4064
4165 public Task HandleAsync ( RuntimeUnattendedUpgradeNotification notification , CancellationToken cancellationToken )
@@ -46,55 +70,26 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
4670 {
4771 case RuntimeLevelReason . UpgradeMigrations :
4872 {
49- var plan = new UmbracoPlan ( _umbracoVersion ) ;
50- using ( ! _profilingLogger . IsEnabled ( Core . Logging . LogLevel . Verbose ) ? null : _profilingLogger . TraceDuration < UnattendedUpgrader > (
51- "Starting unattended upgrade." ,
52- "Unattended upgrade completed." ) )
53- {
54- DatabaseBuilder . Result ? result = _databaseBuilder . UpgradeSchemaAndData ( plan ) ;
55- if ( result ? . Success == false )
56- {
57- var innerException = new UnattendedInstallException (
58- "An error occurred while running the unattended upgrade.\n " + result . Message ) ;
59- _runtimeState . Configure ( RuntimeLevel . BootFailed , RuntimeLevelReason . BootFailedOnException , innerException ) ;
60- }
61-
62- notification . UnattendedUpgradeResult =
63- RuntimeUnattendedUpgradeNotification . UpgradeResult . CoreUpgradeComplete ;
64- }
65- }
73+ RunUpgrade ( notification ) ;
6674
67- break ;
68- case RuntimeLevelReason . UpgradePackageMigrations :
69- {
70- if ( ! _runtimeState . StartupState . TryGetValue (
71- RuntimeState . PendingPackageMigrationsStateKey ,
72- out var pm )
73- || pm is not IReadOnlyList < string > pendingMigrations )
75+ // If we errored out when upgrading don't do anything.
76+ if ( notification . UnattendedUpgradeResult is RuntimeUnattendedUpgradeNotification . UpgradeResult . HasErrors )
7477 {
75- throw new InvalidOperationException (
76- $ "The required key { RuntimeState . PendingPackageMigrationsStateKey } does not exist in startup state") ;
78+ return Task . CompletedTask ;
7779 }
7880
79- if ( pendingMigrations . Count == 0 )
81+ // It's entirely possible that there's both a core upgrade and package migrations to run, so try and run package migrations too.
82+ // but only if upgrade unattended is enabled.
83+ if ( _unattendedSettings . PackageMigrationsUnattended )
8084 {
81- throw new InvalidOperationException (
82- "No pending migrations found but the runtime level reason is " +
83- RuntimeLevelReason . UpgradePackageMigrations ) ;
85+ RunPackageMigrations ( notification ) ;
8486 }
87+ }
8588
86- try
87- {
88- _packageMigrationRunner . RunPackagePlans ( pendingMigrations ) ;
89- notification . UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification . UpgradeResult
90- . PackageMigrationComplete ;
91- }
92- catch ( Exception ex )
93- {
94- SetRuntimeError ( ex ) ;
95- notification . UnattendedUpgradeResult =
96- RuntimeUnattendedUpgradeNotification . UpgradeResult . HasErrors ;
97- }
89+ break ;
90+ case RuntimeLevelReason . UpgradePackageMigrations :
91+ {
92+ RunPackageMigrations ( notification ) ;
9893 }
9994
10095 break ;
@@ -106,6 +101,64 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
106101 return Task . CompletedTask ;
107102 }
108103
104+ private void RunPackageMigrations ( RuntimeUnattendedUpgradeNotification notification )
105+ {
106+ if ( _runtimeState . StartupState . TryGetValue (
107+ RuntimeState . PendingPackageMigrationsStateKey ,
108+ out var pm ) is false
109+ || pm is not IReadOnlyList < string > pendingMigrations )
110+ {
111+ throw new InvalidOperationException (
112+ $ "The required key { RuntimeState . PendingPackageMigrationsStateKey } does not exist in startup state") ;
113+ }
114+
115+ if ( pendingMigrations . Count == 0 )
116+ {
117+ // If we determined we needed to run package migrations but there are none, this is an error
118+ if ( _runtimeState . Reason is RuntimeLevelReason . UpgradePackageMigrations )
119+ {
120+ throw new InvalidOperationException (
121+ "No pending migrations found but the runtime level reason is " +
122+ RuntimeLevelReason . UpgradePackageMigrations ) ;
123+ }
124+
125+ return ;
126+ }
127+
128+ try
129+ {
130+ _packageMigrationRunner . RunPackagePlans ( pendingMigrations ) ;
131+ notification . UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification . UpgradeResult
132+ . PackageMigrationComplete ;
133+ }
134+ catch ( Exception ex )
135+ {
136+ SetRuntimeError ( ex ) ;
137+ notification . UnattendedUpgradeResult =
138+ RuntimeUnattendedUpgradeNotification . UpgradeResult . HasErrors ;
139+ }
140+ }
141+
142+ private void RunUpgrade ( RuntimeUnattendedUpgradeNotification notification )
143+ {
144+ var plan = new UmbracoPlan ( _umbracoVersion ) ;
145+ using ( ! _profilingLogger . IsEnabled ( Core . Logging . LogLevel . Verbose ) ? null : _profilingLogger . TraceDuration < UnattendedUpgrader > (
146+ "Starting unattended upgrade." ,
147+ "Unattended upgrade completed." ) )
148+ {
149+ DatabaseBuilder . Result ? result = _databaseBuilder . UpgradeSchemaAndData ( plan ) ;
150+ if ( result ? . Success == false )
151+ {
152+ var innerException = new UnattendedInstallException (
153+ "An error occurred while running the unattended upgrade.\n " + result . Message ) ;
154+ _runtimeState . Configure ( RuntimeLevel . BootFailed , RuntimeLevelReason . BootFailedOnException , innerException ) ;
155+ }
156+
157+ notification . UnattendedUpgradeResult =
158+ RuntimeUnattendedUpgradeNotification . UpgradeResult . CoreUpgradeComplete ;
159+ }
160+ }
161+
109162 private void SetRuntimeError ( Exception exception )
110163 => _runtimeState . Configure (
111164 RuntimeLevel . BootFailed ,
0 commit comments