예제 #1
0
        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)));
        }
예제 #2
0
        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));
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        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));
        }
예제 #5
0
        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);
        }
예제 #6
0
        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));
        }
예제 #8
0
        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);
        }