public async Task <IActionResult> Index(BulkActionViewModel model) { var allSettings = _shellHost.GetAllSettings(); foreach (var tenantName in model.TenantNames ?? Enumerable.Empty <string>()) { var shellSettings = allSettings .Where(x => string.Equals(x.Name, tenantName, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (shellSettings == null) { break; } switch (model.BulkAction.ToString()) { case "Disable": if (string.Equals(shellSettings.Name, ShellHelper.DefaultShellName, StringComparison.OrdinalIgnoreCase)) { _notifier.Warning(H["You cannot disable the default tenant."]); } else if (shellSettings.State != TenantState.Running) { _notifier.Warning(H["The tenant '{0}' is already disabled.", shellSettings.Name]); } else { shellSettings.State = TenantState.Disabled; await _shellHost.UpdateShellSettingsAsync(shellSettings); } break; case "Enable": if (shellSettings.State != TenantState.Disabled) { _notifier.Warning(H["The tenant '{0}' is already enabled.", shellSettings.Name]); } else { shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); } break; default: break; } } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Create(CreateApiViewModel model) { if (!_currentShellSettings.IsDefaultShell()) { return(Forbid()); } if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants)) { return(this.ChallengeOrForbid("Api")); } // Creates a default shell settings based on the configuration. var shellSettings = _shellSettingsManager.CreateDefaultSettings(); shellSettings.Name = model.Name; shellSettings.RequestUrlHost = model.RequestUrlHost; shellSettings.RequestUrlPrefix = model.RequestUrlPrefix; shellSettings.State = TenantState.Uninitialized; shellSettings["ConnectionString"] = model.ConnectionString; shellSettings["TablePrefix"] = model.TablePrefix; shellSettings["DatabaseProvider"] = model.DatabaseProvider; shellSettings["Secret"] = Guid.NewGuid().ToString(); shellSettings["RecipeName"] = model.RecipeName; shellSettings["FeatureProfile"] = model.FeatureProfile; model.IsNewTenant = true; ModelState.AddModelErrors(await _tenantValidator.ValidateAsync(model)); if (ModelState.IsValid) { if (_shellHost.TryGetSettings(model.Name, out var settings)) { // Site already exists, return 201 for indempotency purpose var token = CreateSetupToken(settings); return(StatusCode(201, GetEncodedUrl(settings, token))); } else { await _shellHost.UpdateShellSettingsAsync(shellSettings); var token = CreateSetupToken(shellSettings); return(Ok(GetEncodedUrl(shellSettings, token))); } } return(BadRequest(ModelState)); }
/// <summary> /// Creates a tenant shell settings. /// </summary> /// <param name="setupOptions">The setup options.</param> /// <returns>The <see cref="ShellSettings"/>.</returns> public async Task <ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions) { var shellSettings = _shellSettingsManager.CreateDefaultSettings(); shellSettings.Name = setupOptions.ShellName; shellSettings.RequestUrlHost = setupOptions.RequestUrlHost; shellSettings.RequestUrlPrefix = setupOptions.RequestUrlPrefix; shellSettings.State = TenantState.Uninitialized; shellSettings["ConnectionString"] = setupOptions.DatabaseConnectionString; shellSettings["TablePrefix"] = setupOptions.DatabaseTablePrefix; shellSettings["DatabaseProvider"] = setupOptions.DatabaseProvider; shellSettings["Secret"] = Guid.NewGuid().ToString(); shellSettings["RecipeName"] = setupOptions.RecipeName; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(shellSettings); }
public async Task <IActionResult> Create(CreateApiViewModel model) { if (!IsDefaultShell()) { return(Forbid()); } if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants)) { return(this.ChallengeOrForbid("Api")); } if (!String.IsNullOrEmpty(model.Name) && !Regex.IsMatch(model.Name, @"^\w+$")) { ModelState.AddModelError(nameof(CreateApiViewModel.Name), S["Invalid tenant name. Must contain characters only and no spaces."]); } // Creates a default shell settings based on the configuration. var shellSettings = _shellSettingsManager.CreateDefaultSettings(); shellSettings.Name = model.Name; shellSettings.RequestUrlHost = model.RequestUrlHost; shellSettings.RequestUrlPrefix = model.RequestUrlPrefix; shellSettings.State = TenantState.Uninitialized; shellSettings["ConnectionString"] = model.ConnectionString; shellSettings["TablePrefix"] = model.TablePrefix; shellSettings["DatabaseProvider"] = model.DatabaseProvider; shellSettings["Secret"] = Guid.NewGuid().ToString(); shellSettings["RecipeName"] = model.RecipeName; shellSettings["FeatureProfile"] = model.FeatureProfile; if (!String.IsNullOrWhiteSpace(model.FeatureProfile)) { var featureProfiles = await _featureProfilesService.GetFeatureProfilesAsync(); if (!featureProfiles.ContainsKey(model.FeatureProfile)) { ModelState.AddModelError(nameof(CreateApiViewModel.FeatureProfile), S["The feature profile does not exist.", model.FeatureProfile]); } } if (String.IsNullOrWhiteSpace(shellSettings.RequestUrlHost) && String.IsNullOrWhiteSpace(shellSettings.RequestUrlPrefix)) { ModelState.AddModelError(nameof(CreateApiViewModel.RequestUrlPrefix), S["Host and url prefix can not be empty at the same time."]); } if (!String.IsNullOrWhiteSpace(shellSettings.RequestUrlPrefix)) { if (shellSettings.RequestUrlPrefix.Contains('/')) { ModelState.AddModelError(nameof(CreateApiViewModel.RequestUrlPrefix), S["The url prefix can not contain more than one segment."]); } } if (ModelState.IsValid) { if (_shellHost.TryGetSettings(model.Name, out var settings)) { // Site already exists, return 201 for indempotency purpose var token = CreateSetupToken(settings); return(StatusCode(201, GetEncodedUrl(settings, token))); } else { await _shellHost.UpdateShellSettingsAsync(shellSettings); var token = CreateSetupToken(shellSettings); return(Ok(GetEncodedUrl(shellSettings, token))); } } return(BadRequest(ModelState)); }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name); } // Features to enable for Setup string[] hardcoded = { _applicationName, "OrchardCore.Features", "OrchardCore.Scripting", "OrchardCore.Recipes" }; context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList(); // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up. context.ShellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(context.ShellSettings); if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"])) { shellSettings["DatabaseProvider"] = context.DatabaseProvider; shellSettings["ConnectionString"] = context.DatabaseConnectionString; shellSettings["TablePrefix"] = context.DatabaseTablePrefix; } if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"])) { throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required"); } // Creating a standalone environment based on a "minimum shell descriptor". // In theory this environment can be used to resolve any normal components by interface, and those // components will exist entirely in isolation - no crossover between the safemode container currently in effect // It is used to initialize the database before the recipe is run. var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { await shellContext.CreateScope().UsingAsync(async scope => { IStore store; try { store = scope.ServiceProvider.GetRequiredService <IStore>(); } catch (Exception e) { // Tables already exist or database was not found // The issue is that the user creation needs the tables to be present, // if the user information is not valid, the next POST will try to recreate the // tables. The tables should be rolled back if one of the steps is invalid, // unless the recipe is executing? _logger.LogError(e, "An error occurred while initializing the datastore."); context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]); return; } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); }); if (context.Errors.Any()) { return(null); } executionId = Guid.NewGuid().ToString("n"); var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>(); await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new { SiteName = context.SiteName, AdminUsername = context.AdminUsername, AdminEmail = context.AdminEmail, AdminPassword = context.AdminPassword, DatabaseProvider = context.DatabaseProvider, DatabaseConnectionString = context.DatabaseConnectionString, DatabaseTablePrefix = context.DatabaseTablePrefix }, _applicationLifetime.ApplicationStopping); } // Reloading the shell context as the recipe has probably updated its features using (var shellContext = await _shellHost.CreateShellContextAsync(shellSettings)) { await shellContext.CreateScope().UsingAsync(async scope => { void reportError(string key, string message) { context.Errors[key] = message; } // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); await setupEventHandlers.InvokeAsync(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, context.SiteTimeZone, reportError ), logger); }); if (context.Errors.Any()) { return(executionId); } } // Update the shell state shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(executionId); }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name); } string[] hardcoded = { _applicationName, "SeedModules.Features", "OrchardCore.Scripting", "SeedModules.Recipes", }; context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList(); context.ShellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(context.ShellSettings); if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"])) { shellSettings["DatabaseProvider"] = context.DatabaseProvider; shellSettings["ConnectionString"] = context.DatabaseConnectionString; shellSettings["TablePrefix"] = context.DatabaseTablePrefix; } if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"])) { throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required"); } var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { await shellContext.CreateScope().UsingAsync(async scope => { IStore store; try { store = scope.ServiceProvider.GetRequiredService <IStore>(); // await store.InitializeAsync(scope.ServiceProvider); } catch (Exception e) { // 表已存在或数据库问题 _logger.LogError(e, "An error occurred while initializing the datastore."); context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]); return; } await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); }); if (context.Errors.Any()) { return(null); } executionId = Guid.NewGuid().ToString("n"); var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>(); await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new { SiteName = context.SiteName, AdminUsername = context.AdminUsername, AdminEmail = context.AdminEmail, AdminPassword = context.AdminPassword, DatabaseProvider = context.DatabaseProvider, DatabaseConnectionString = context.DatabaseConnectionString, DatabaseTablePrefix = context.DatabaseTablePrefix }, _applicationLifetime.ApplicationStopping); } await(await _shellHost.GetScopeAsync(shellSettings)).UsingAsync(async scope => { void reportError(string key, string message) { context.Errors[key] = message; } // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, context.SiteTimeZone, reportError ), context, logger); }); if (context.Errors.Any()) { return(executionId); } shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(executionId); }
public async Task <IActionResult> Edit(EditTenantViewModel model) { if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants)) { return(Unauthorized()); } if (!IsDefaultShell()) { return(Unauthorized()); } if (ModelState.IsValid) { await ValidateViewModel(model, false); } var shellContext = (await GetShellsAsync()) .Where(x => string.Equals(x.Settings.Name, model.Name, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (shellContext == null) { return(NotFound()); } var shellSettings = shellContext.Settings; if (ModelState.IsValid) { shellSettings.RequestUrlPrefix = model.RequestUrlPrefix?.Trim(); shellSettings.RequestUrlHost = model.RequestUrlHost; // The user can change the 'preset' database information only if the // tenant has not been initialized yet if (shellSettings.State == TenantState.Uninitialized) { shellSettings.DatabaseProvider = model.DatabaseProvider; shellSettings.TablePrefix = model.TablePrefix; shellSettings.ConnectionString = model.ConnectionString; shellSettings.RecipeName = model.RecipeName; shellSettings.Secret = Guid.NewGuid().ToString(); } await _orchardHost.UpdateShellSettingsAsync(shellSettings); return(RedirectToAction(nameof(Index))); } // The user can change the 'preset' database information only if the // tenant has not been initialized yet if (shellSettings.State == TenantState.Uninitialized) { model.DatabaseProvider = shellSettings.DatabaseProvider; model.TablePrefix = shellSettings.TablePrefix; model.ConnectionString = shellSettings.ConnectionString; model.RecipeName = shellSettings.RecipeName; model.CanSetDatabasePresets = true; } var recipeCollections = await Task.WhenAll(_recipeHarvesters.Select(x => x.HarvestRecipesAsync())); var recipes = recipeCollections.SelectMany(x => x).Where(x => x.IsSetupRecipe).ToArray(); model.Recipes = recipes; // If we got this far, something failed, redisplay form return(View(model)); }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name); } // Features to enable for Setup string[] hardcoded = { _applicationName, //TODO }; context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToArray(); // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up. context.ShellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(context.ShellSettings); shellSettings["ConnectionString"] = context.DatabaseConnectionString; // Creating a standalone environment based on a "minimum shell descriptor". // In theory this environment can be used to resolve any normal components by interface, and those // components will exist entirely in isolation - no crossover between the safemode container currently in effect // It is used to initialize the database before the recipe is run. var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { using (var scope = shellContext.CreateScope()) { var hasErrors = false; void reportError(string key, string message) { hasErrors = true; context.SetError(key, message); } try { await this.InitializeDatabaseForTenantAsync(context, scope, reportError); if (hasErrors) { _logger.LogError("An error occurred while initializing the tenant {0}", context.DbName); return(null); } } catch (Exception e) { // Tables already exist or database was not found // The issue is that the user creation needs the tables to be present, // if the user information is not valid, the next POST will try to recreate the // tables. The tables should be rolled back if one of the steps is invalid, // unless the recipe is executing? _logger.LogError(e, "An error occurred while initializing the datastore."); context.SetError("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]); return(null); } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features); // TODO FIXME /* * var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); * * if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { * var taskContext = new DeferredTaskContext(scope.ServiceProvider); * await deferredTaskEngine.ExecuteTasksAsync(taskContext); * } */ } executionId = Guid.NewGuid().ToString("n"); /* * var recipeExecutor = shellContext.ServiceProvider.GetRequiredService<IRecipeExecutor>(); * * await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new * { * SiteName = context.SiteName, * AdminUsername = context.AdminUsername, * AdminEmail = context.AdminEmail, * AdminPassword = context.AdminPassword, * DatabaseProvider = context.DatabaseProvider, * DatabaseConnectionString = context.DatabaseConnectionString, * DatabaseTablePrefix = context.DatabaseTablePrefix * }, * * _applicationLifetime.ApplicationStopping); */ } // Reloading the shell context as the recipe has probably updated its features using (var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings)) { using (var scope = shellContext.CreateScope()) { var hasErrors = false; void reportError(string key, string message) { hasErrors = true; context.SetError(key, message); } // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); //Notify all registered event handlers await setupEventHandlers.InvokeAsync(x => x.Setup(context, reportError), logger); if (hasErrors) { return(executionId); } /* TODO FIXME * var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); * * if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { * var taskContext = new DeferredTaskContext(scope.ServiceProvider); * await deferredTaskEngine.ExecuteTasksAsync(taskContext); * } */ } } // Update the shell state shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(executionId); }