Add queue hardening by modifying DeploymentJobTargets, DeploymentJobSteps, and DeploymentJobs tables
- Drop existing indexes on DeploymentJobTargets and DeploymentJobSteps - Alter Status column to nvarchar(450) in DeploymentJobTargets, DeploymentJobSteps, and DeploymentJobs - Add new columns: Finished, OutputMetadataJson, Started to DeploymentJobTargets; ErrorMessage, Finished, OutputMetadataJson, Started to DeploymentJobSteps; CorrelationId, HeartbeatAt, Priority, RowVersion, ScheduledAt, WorkerName to DeploymentJobs - Create new indexes for improved query performance - Add constraints to ensure OutputMetadataJson is valid JSON - Record migration in __EFMigrationsHistory
This commit is contained in:
@@ -93,6 +93,21 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.ToTable("DeploymentJobs");
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.HasIndex(job => new { job.Status, job.ScheduledAt, job.LockedUntil, job.Priority, job.Created });
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.HasIndex(job => job.CorrelationId);
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.HasIndex(job => job.WorkerName);
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.Property(job => job.CorrelationId)
|
||||
.HasDefaultValueSql("NEWID()");
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.Property(job => job.Priority)
|
||||
.HasDefaultValue(100);
|
||||
modelBuilder.Entity<QueueJobModel>()
|
||||
.Property(job => job.RowVersion)
|
||||
.IsRowVersion();
|
||||
|
||||
modelBuilder.Entity<QueueJobTargetModel>()
|
||||
.ToTable("DeploymentJobTargets");
|
||||
@@ -102,6 +117,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
modelBuilder.Entity<QueueJobTargetModel>()
|
||||
.Property(target => target.DeploymentGroupId)
|
||||
.HasColumnName("DeploymentBatchId");
|
||||
modelBuilder.Entity<QueueJobTargetModel>()
|
||||
.HasIndex(target => new { target.QueueJobId, target.Status });
|
||||
modelBuilder.Entity<QueueJobTargetModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentJobTargets_OutputMetadataJson_IsJson", "[OutputMetadataJson] IS NULL OR ISJSON([OutputMetadataJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<QueueJobStepModel>()
|
||||
.ToTable("DeploymentJobSteps");
|
||||
@@ -111,6 +130,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
modelBuilder.Entity<QueueJobStepModel>()
|
||||
.Property(step => step.DependsOnQueueJobStepId)
|
||||
.HasColumnName("DependsOnDeploymentJobStepId");
|
||||
modelBuilder.Entity<QueueJobStepModel>()
|
||||
.HasIndex(step => new { step.QueueJobId, step.Status, step.SortOrder });
|
||||
modelBuilder.Entity<QueueJobStepModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentJobSteps_OutputMetadataJson_IsJson", "[OutputMetadataJson] IS NULL OR ISJSON([OutputMetadataJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentModel>()
|
||||
.HasKey(d => new { d.TargetId, d.DeploymentGroupId });
|
||||
|
||||
419
Context/DemoData.TemplateJson.cs
Normal file
419
Context/DemoData.TemplateJson.cs
Normal file
@@ -0,0 +1,419 @@
|
||||
namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
{
|
||||
internal static partial class DemoData
|
||||
{
|
||||
private const string BgwEnvironmentTestTemplateJson = """
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"templateType": "Environment",
|
||||
"metadata": {
|
||||
"TemplateType": "Environment",
|
||||
"Name": "Test"
|
||||
},
|
||||
"parameters": {
|
||||
"Landscape": {
|
||||
"DefaultValue": "Prod",
|
||||
"Value": "Test",
|
||||
"Type": "string",
|
||||
"AllowedValues": [
|
||||
"Prod",
|
||||
"QA",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"AdminDbName": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), \u0027Farm_AdminContent\u0027)]",
|
||||
"ContentDbPrefix": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), parameters(\u0027ContentDatabaseSegment\u0027))]",
|
||||
"ServiceDbPrefix": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), parameters(\u0027ServiceDatabaseSegment\u0027))]",
|
||||
"DatabasePrefix": "[joinNotEmpty(\u0027_\u0027, parameters(\u0027DatabasePrefix\u0027), parameters(\u0027DomainLabel\u0027), if(equals(parameters(\u0027Landscape\u0027), \u0027Test\u0027), \u0027Test\u0027, \u0027\u0027))]",
|
||||
"ConfigDbName": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), \u0027Farm_Config\u0027)]"
|
||||
},
|
||||
"resources": {
|
||||
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string BgwDomainContosoTemplateJson = """
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"templateType": "Domain",
|
||||
"metadata": {
|
||||
"TemplateType": "Domain",
|
||||
"Name": "Contoso"
|
||||
},
|
||||
"parameters": {
|
||||
"DomainFQDN": {
|
||||
"DefaultValue": "",
|
||||
"Pattern": "^[A-Za-z0-9.-]+$",
|
||||
"Type": "string",
|
||||
"Value": "contoso.local",
|
||||
"Required": true
|
||||
},
|
||||
"DomainLabel": {
|
||||
"Type": "string",
|
||||
"MaxLength": 32,
|
||||
"Value": "Contoso",
|
||||
"DefaultValue": "",
|
||||
"Pattern": "^[A-Za-z][A-Za-z0-9_-]*$",
|
||||
"MinLength": 2,
|
||||
"Required": false
|
||||
},
|
||||
"DomainNetBIOS": {
|
||||
"Type": "string",
|
||||
"MaxLength": 15,
|
||||
"Value": "CONTOSO",
|
||||
"DefaultValue": "",
|
||||
"Pattern": "^[A-Za-z0-9_-]+$",
|
||||
"MinLength": 1,
|
||||
"Required": true
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
|
||||
},
|
||||
"resources": {
|
||||
"NonNodeData": {
|
||||
"Services": {
|
||||
"ActiveDirectory": {
|
||||
"NetBIOSName": "[parameters(\u0027DomainNetBIOS\u0027)]",
|
||||
"DomainName": "[parameters(\u0027DomainFQDN\u0027)]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string BgwSharePointContosoTemplateJson = """
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"templateType": "Service",
|
||||
"metadata": {
|
||||
"TemplateType": "Service",
|
||||
"Name": "SharePoint.Contoso"
|
||||
},
|
||||
"parameters": {
|
||||
"FarmCredential": {
|
||||
"Sensitive": true,
|
||||
"Value": {
|
||||
"Provider": "SecretManagement",
|
||||
"Vault": "Test",
|
||||
"Name": "Windows/SharePoint/FarmAccount"
|
||||
},
|
||||
"Type": "credential",
|
||||
"Required": true
|
||||
},
|
||||
"ContentDatabaseSegment": {
|
||||
"DefaultValue": "Content",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Namenssegment fuer SharePoint-Content-Datenbanken innerhalb des Datenbanknamens.",
|
||||
"en-US": "Name segment used for SharePoint content databases within the database name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ServiceApplicationPoolDefault": {
|
||||
"DefaultValue": "SharePoint Service Applications",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Anzeigename des Standard-Application-Pools fuer SharePoint-Serviceanwendungen.",
|
||||
"en-US": "Display name of the default application pool for SharePoint service applications."
|
||||
}
|
||||
}
|
||||
},
|
||||
"WebApplicationPoolDefault": {
|
||||
"DefaultValue": "SharePoint Web Applications",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Anzeigename des Standard-Application-Pools fuer SharePoint-Webanwendungen.",
|
||||
"en-US": "Display name of the default application pool for SharePoint web applications."
|
||||
}
|
||||
}
|
||||
},
|
||||
"DatabaseServerName": {
|
||||
"DefaultValue": "SQL_Server",
|
||||
"Type": "string",
|
||||
"Value": "CL-SQL-01"
|
||||
},
|
||||
"WebApplicationPoolDefaultAccount": {
|
||||
"DefaultValue": "SVC_SHP_WAP",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Kontoname fuer den Standard-Application-Pool der SharePoint-Webanwendungen.",
|
||||
"en-US": "Account name used by the default application pool for SharePoint web applications."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ServiceDatabaseSegment": {
|
||||
"DefaultValue": "Services",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Namenssegment fuer SharePoint-Service-Datenbanken innerhalb des Datenbanknamens.",
|
||||
"en-US": "Name segment used for SharePoint service databases within the database name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"FarmPassphrase": {
|
||||
"Sensitive": true,
|
||||
"Value": {
|
||||
"Provider": "SecretManagement",
|
||||
"Vault": "Test",
|
||||
"Name": "Windows/SharePoint/FarmPassphrase"
|
||||
},
|
||||
"Type": "credential",
|
||||
"Required": true
|
||||
},
|
||||
"DatabaseInstanceName": {
|
||||
"DefaultValue": "SQL_Server",
|
||||
"Type": "string",
|
||||
"Value": "SQLServer"
|
||||
},
|
||||
"DatabaseTcpPort": {
|
||||
"DefaultValue": 1433,
|
||||
"Type": "int",
|
||||
"Value": 1433
|
||||
},
|
||||
"DefaultServiceApplicationPoolAccount": {
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Kontoname fuer den Standard-Application-Pool der SharePoint-Serviceanwendungen.",
|
||||
"en-US": "Account name used by the default application pool for SharePoint service applications."
|
||||
}
|
||||
},
|
||||
"Sensitive": true,
|
||||
"Value": {
|
||||
"Provider": "SecretManagement",
|
||||
"Vault": "Test",
|
||||
"Name": "Windows/SharePoint/DefaultServiceAccount"
|
||||
},
|
||||
"Type": "credential",
|
||||
"Required": true
|
||||
},
|
||||
"ProductKey": {
|
||||
"DefaultValue": "0000-0000-0000-0000-0000",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "SharePoint-Produktlizenzschluessel.",
|
||||
"en-US": "SharePoint product license key."
|
||||
}
|
||||
}
|
||||
},
|
||||
"SetupCredential": {
|
||||
"Sensitive": true,
|
||||
"Value": {
|
||||
"Provider": "SecretManagement",
|
||||
"Vault": "Test",
|
||||
"Name": "Windows/SharePoint/SetupAccount"
|
||||
},
|
||||
"Type": "credential",
|
||||
"Required": true
|
||||
},
|
||||
"DatabasePrefix": {
|
||||
"DefaultValue": "SharePoint",
|
||||
"Type": "string",
|
||||
"Value": "SharePoint"
|
||||
},
|
||||
"SearchServiceApplicationPoolAccount": {
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Kontoname fuer den Search-Application-Pool der SharePoint-Serviceanwendungen.",
|
||||
"en-US": "Account name used by the search application pool for SharePoint service applications."
|
||||
}
|
||||
},
|
||||
"Sensitive": true,
|
||||
"Value": {
|
||||
"Provider": "SecretManagement",
|
||||
"Vault": "Test",
|
||||
"Name": "Windows/SharePoint/SearchAccount"
|
||||
},
|
||||
"Type": "credential",
|
||||
"Required": true
|
||||
},
|
||||
"CentralAdminPort": {
|
||||
"DefaultValue": 443,
|
||||
"Type": "int",
|
||||
"Value": 4000
|
||||
},
|
||||
"ServiceApplicationPoolSearch": {
|
||||
"DefaultValue": "SharePoint Search Service Applications",
|
||||
"Type": "string",
|
||||
"Metadata": {
|
||||
"Description": {
|
||||
"de-DE": "Anzeigename des Application-Pools fuer SharePoint Search-Serviceanwendungen.",
|
||||
"en-US": "Display name of the application pool for SharePoint Search service applications."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"AdminDbName": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), \u0027Farm_AdminContent\u0027)]",
|
||||
"ContentDbPrefix": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), parameters(\u0027ContentDatabaseSegment\u0027))]",
|
||||
"ServiceDbPrefix": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), parameters(\u0027ServiceDatabaseSegment\u0027))]",
|
||||
"DatabasePrefix": "[joinNotEmpty(\u0027_\u0027, parameters(\u0027DatabasePrefix\u0027), parameters(\u0027DomainLabel\u0027), if(equals(parameters(\u0027Landscape\u0027), \u0027Test\u0027), \u0027Test\u0027, \u0027\u0027))]",
|
||||
"ConfigDbName": "[joinNotEmpty(\u0027_\u0027, variables(\u0027DatabasePrefix\u0027), \u0027Farm_Config\u0027)]"
|
||||
},
|
||||
"resources": {
|
||||
"AllNodes": [
|
||||
{
|
||||
"PSDscAllowDomainUser": true,
|
||||
"PSDSCAllowPlainTextPassword": true,
|
||||
"NodeName": "*",
|
||||
"RunCentralAdministration": false
|
||||
}
|
||||
],
|
||||
"NonNodeData": {
|
||||
"Services": {
|
||||
"SharePoint": {
|
||||
"Farm": {
|
||||
"ManagedAccounts": {
|
||||
"DefaultServiceApplicationPoolAccount": "[parameters(\u0027DefaultServiceApplicationPoolAccount\u0027)]",
|
||||
"FarmAccount": "[parameters(\u0027FarmCredential\u0027)]",
|
||||
"SearchServiceApplicationPoolAccount": "[parameters(\u0027SearchServiceApplicationPoolAccount\u0027)]"
|
||||
},
|
||||
"Passphrase": "[parameters(\u0027FarmPassphrase\u0027)]",
|
||||
"ServiceApplications": {
|
||||
"AppManagementService": {
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027AppManagement\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.DefaultServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"StateService": {
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027StateService\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"SubscriptionSettingsService": {
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027SubscriptionSettings\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.DefaultServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"ManagedMetadataService": {
|
||||
"Name": "Managed Metadata Service",
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027), \u0027ManagedMetadata\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.DefaultServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"SearchService": {
|
||||
"Name": "Search Service Application",
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027Search\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.SearchServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"UsageAndHealthService": {
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027UsageAndHealth\u0027)]",
|
||||
"Provision": true
|
||||
},
|
||||
"SecureStoreService": {
|
||||
"Name": "Secure Store Service",
|
||||
"DatabaseName": "[concat(variables(\u0027ServiceDbPrefix\u0027),\u0027SecureStore\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.DefaultServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true,
|
||||
"AuditingEnabled": true
|
||||
},
|
||||
"UserProfileService": {
|
||||
"SyncDBName": "[concat(variables(\u0027ServiceDbPrefix\u0027), \u0027UserProfile_Sync\u0027)]",
|
||||
"ApplicationPool": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ServiceApplicationPools.DefaultServiceApplicationPool\u0027, \u0027Name\u0027)]",
|
||||
"Provision": true,
|
||||
"Name": "User Profile Service",
|
||||
"SocialDBName": "[concat(variables(\u0027ServiceDbPrefix\u0027), \u0027UserProfile_Social\u0027)]",
|
||||
"ProfileDBName": "[concat(variables(\u0027ServiceDbPrefix\u0027), \u0027UserProfile_Profile\u0027)]"
|
||||
}
|
||||
},
|
||||
"CentralAdminAuth": "NTLM",
|
||||
"ConfigDatabaseName": "[variables(\u0027ConfigDbName\u0027)]",
|
||||
"Accounts": {
|
||||
"SetupAccount": "[parameters(\u0027SetupCredential\u0027)]"
|
||||
},
|
||||
"CentralAdminPort": "[parameters(\u0027CentralAdminPort\u0027)]",
|
||||
"AdminContentDatabase": "[variables(\u0027AdminDbName\u0027)]",
|
||||
"ServiceApplicationPools": {
|
||||
"SearchServiceApplicationPool": {
|
||||
"Account": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.SearchServiceApplicationPoolAccount\u0027, \u0027UserName\u0027)]",
|
||||
"Name": "[parameters(\u0027ServiceApplicationPoolSearch\u0027)]"
|
||||
},
|
||||
"DefaultServiceApplicationPool": {
|
||||
"Account": "[reference(\u0027Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.DefaultServiceApplicationPoolAccount\u0027, \u0027UserName\u0027)]",
|
||||
"Name": "[parameters(\u0027ServiceApplicationPoolDefault\u0027)]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Database": {
|
||||
"Targets": {
|
||||
"Farm": {
|
||||
"Server": "SQLServer",
|
||||
"DependsOnAlias": "SQLServer"
|
||||
},
|
||||
"Content": {
|
||||
"Server": "SQLServer",
|
||||
"DependsOnAlias": "SQLServer"
|
||||
},
|
||||
"Service": {
|
||||
"Server": "SQLServer",
|
||||
"DependsOnAlias": "SQLServer"
|
||||
}
|
||||
},
|
||||
"SQLAlias": {
|
||||
"SQLServer": {
|
||||
"InstanceName": "[parameters(\u0027DatabaseInstanceName\u0027)]",
|
||||
"ServerName": "[parameters(\u0027DatabaseServerName\u0027)]",
|
||||
"Protocol": "TCP",
|
||||
"TcpPort": "[parameters(\u0027DatabaseTcpPort\u0027)]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"General": {
|
||||
"ProductKey": "[parameters(\u0027ProductKey\u0027)]"
|
||||
},
|
||||
"Windows": {
|
||||
"Registry": {
|
||||
"DisableLoopbackCheck": {
|
||||
"Path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa",
|
||||
"Name": "DisableLoopbackCheck",
|
||||
"Value": 1,
|
||||
"Type": "DWord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string BgwStageInstallTemplateJson = """
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"templateType": "Stage",
|
||||
"metadata": {
|
||||
"TemplateType": "Stage",
|
||||
"Name": "Install"
|
||||
},
|
||||
"parameters": {
|
||||
|
||||
},
|
||||
"variables": {
|
||||
|
||||
},
|
||||
"resources": {
|
||||
"NonNodeData": {
|
||||
"LocalConfigurationManager": {
|
||||
"RefreshFrequencyMins": "30",
|
||||
"RefreshMode": "PUSH",
|
||||
"ConfigurationModeFrequencyMins": "120",
|
||||
"ConfigurationMode": "ApplyOnly"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Microsoft.SelfService.Portal.Core.API.Models;
|
||||
|
||||
namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
{
|
||||
internal static class DemoData
|
||||
internal static partial class DemoData
|
||||
{
|
||||
private static readonly DateTime Timestamp = new(2026, 7, 7, 0, 0, 0, DateTimeKind.Utc);
|
||||
private const string Owner = "DemoSeed";
|
||||
@@ -22,16 +22,21 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
internal static readonly Guid VmSp02Id = Guid.Parse("30000000-0000-0000-0000-000000000005");
|
||||
internal static readonly Guid TargetM365TenantId = Guid.Parse("30000000-0000-0000-0000-000000000006");
|
||||
internal static readonly Guid TargetTeamsPolicyScopeId = Guid.Parse("30000000-0000-0000-0000-000000000007");
|
||||
internal static readonly Guid TargetBgwSp01Id = Guid.Parse("30000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid TargetBgwSp02Id = Guid.Parse("30000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid TargetBgwSp03Id = Guid.Parse("30000000-0000-0000-0000-000000000103");
|
||||
|
||||
internal static readonly Guid ServiceActiveDirectoryId = Guid.Parse("40000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid ServiceSqlServerId = Guid.Parse("40000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid ServiceSharePointId = Guid.Parse("40000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid ServiceTeamsId = Guid.Parse("40000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid ServiceConfigurationDataId = Guid.Parse("40000000-0000-0000-0000-000000000101");
|
||||
|
||||
internal static readonly Guid CategoryActiveDirectoryId = Guid.Parse("50000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid CategorySqlServerId = Guid.Parse("50000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid CategorySharePointId = Guid.Parse("50000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid CategoryTeamsId = Guid.Parse("50000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid CategoryBgwMergeTemplatesId = Guid.Parse("50000000-0000-0000-0000-000000000101");
|
||||
|
||||
internal static readonly Guid RuleStandardId = Guid.Parse("60000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid RuleStepValidateId = Guid.Parse("60000000-0000-0000-0000-000000000101");
|
||||
@@ -42,20 +47,39 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
internal static readonly Guid TemplateSqlServerId = Guid.Parse("70000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid TemplateSharePointId = Guid.Parse("70000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid TemplateTeamsId = Guid.Parse("70000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid TemplateBgwEnvironmentTestId = Guid.Parse("70000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid TemplateBgwDomainContosoId = Guid.Parse("70000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid TemplateBgwSharePointContosoId = Guid.Parse("70000000-0000-0000-0000-000000000103");
|
||||
internal static readonly Guid TemplateBgwStageInstallId = Guid.Parse("70000000-0000-0000-0000-000000000104");
|
||||
|
||||
internal static readonly Guid TemplateActiveDirectoryVersionId = Guid.Parse("71000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid TemplateSqlServerVersionId = Guid.Parse("71000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid TemplateSharePointVersionId = Guid.Parse("71000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid TemplateTeamsVersionId = Guid.Parse("71000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid TemplateBgwEnvironmentTestVersionId = Guid.Parse("71000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid TemplateBgwDomainContosoVersionId = Guid.Parse("71000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid TemplateBgwSharePointContosoVersionId = Guid.Parse("71000000-0000-0000-0000-000000000103");
|
||||
internal static readonly Guid TemplateBgwStageInstallVersionId = Guid.Parse("71000000-0000-0000-0000-000000000104");
|
||||
|
||||
internal static readonly Guid DeploymentBatchSharePointId = Guid.Parse("80000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DeploymentBatchBgwMergeId = Guid.Parse("80000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid DeploymentSp01Id = Guid.Parse("81000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DeploymentSp02Id = Guid.Parse("81000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid DeploymentBgwSp01Id = Guid.Parse("81000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid DeploymentBgwSp02Id = Guid.Parse("81000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid DeploymentBgwSp03Id = Guid.Parse("81000000-0000-0000-0000-000000000103");
|
||||
internal static readonly Guid DeploymentSharePointTemplateSelectionId = Guid.Parse("82000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DeploymentBgwEnvironmentSelectionId = Guid.Parse("82000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid DeploymentBgwDomainSelectionId = Guid.Parse("82000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid DeploymentBgwSharePointSelectionId = Guid.Parse("82000000-0000-0000-0000-000000000103");
|
||||
internal static readonly Guid DeploymentBgwStageSelectionId = Guid.Parse("82000000-0000-0000-0000-000000000104");
|
||||
internal static readonly Guid DeploymentParameterDatabasePrefixId = Guid.Parse("83000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DeploymentParameterFarmAccountId = Guid.Parse("83000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid DeploymentTargetSp01Id = Guid.Parse("84000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DeploymentTargetSp02Id = Guid.Parse("84000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid DeploymentTargetBgwSp01Id = Guid.Parse("84000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid DeploymentTargetBgwSp02Id = Guid.Parse("84000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid DeploymentTargetBgwSp03Id = Guid.Parse("84000000-0000-0000-0000-000000000103");
|
||||
|
||||
internal static void Seed(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -105,7 +129,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
Base(new TargetModel { Id = VmSp01Id, DomainID = DomainCentralId, Name = "CT-SHP-01", TargetType = "VirtualMachine", ProviderType = "OnPrem", MetadataJson = "{\"role\":\"SharePoint\",\"environment\":\"Test\"}" }),
|
||||
Base(new TargetModel { Id = VmSp02Id, DomainID = DomainCentralId, Name = "CT-SHP-02", TargetType = "VirtualMachine", ProviderType = "OnPrem", MetadataJson = "{\"role\":\"SharePoint\",\"environment\":\"Test\"}" }),
|
||||
Base(new TargetModel { Id = TargetM365TenantId, Name = "contoso.onmicrosoft.com", TargetType = "Tenant", ProviderType = "Microsoft365", ExternalId = "11111111-1111-1111-1111-111111111111", MetadataJson = "{\"environment\":\"Production\",\"workload\":\"M365\"}" }),
|
||||
Base(new TargetModel { Id = TargetTeamsPolicyScopeId, Name = "Teams - Standard Users", TargetType = "PolicyScope", ProviderType = "Microsoft365", ExternalId = "Teams.StandardUsers", MetadataJson = "{\"workload\":\"Teams\",\"scope\":\"StandardUsers\"}" })
|
||||
Base(new TargetModel { Id = TargetTeamsPolicyScopeId, Name = "Teams - Standard Users", TargetType = "PolicyScope", ProviderType = "Microsoft365", ExternalId = "Teams.StandardUsers", MetadataJson = "{\"workload\":\"Teams\",\"scope\":\"StandardUsers\"}" }),
|
||||
Base(new TargetModel { Id = TargetBgwSp01Id, DomainID = DomainCentralId, Name = "CLD-SHP-01", TargetType = "VirtualMachine", ProviderType = "OnPrem", MetadataJson = "{\"role\":\"SharePoint\",\"environment\":\"Test\",\"source\":\"Test.Merge.ps1\"}" }),
|
||||
Base(new TargetModel { Id = TargetBgwSp02Id, DomainID = DomainCentralId, Name = "CLD-SHP-02", TargetType = "VirtualMachine", ProviderType = "OnPrem", MetadataJson = "{\"role\":\"SharePoint\",\"environment\":\"Test\",\"source\":\"Test.Merge.ps1\"}" }),
|
||||
Base(new TargetModel { Id = TargetBgwSp03Id, DomainID = DomainCentralId, Name = "CLD-SHP-03", TargetType = "VirtualMachine", ProviderType = "OnPrem", MetadataJson = "{\"role\":\"SharePoint\",\"environment\":\"Test\",\"source\":\"Test.Merge.ps1\"}" })
|
||||
];
|
||||
|
||||
private static object[] Services() =>
|
||||
@@ -113,7 +140,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
Base(new ServiceModel { Id = ServiceActiveDirectoryId, Name = "Active Directory", Description = "On-premises directory and identity service.", IconKey = "network", IsCloudService = false }),
|
||||
Base(new ServiceModel { Id = ServiceSqlServerId, Name = "SQL Server", Description = "Database platform for application workloads.", IconKey = "database", IsCloudService = false }),
|
||||
Base(new ServiceModel { Id = ServiceSharePointId, Name = "SharePoint Server", Description = "Collaboration platform for on-premises workloads.", IconKey = "sharepoint", IsCloudService = false }),
|
||||
Base(new ServiceModel { Id = ServiceTeamsId, Name = "Microsoft Teams", Description = "Cloud collaboration workload in Microsoft 365.", IconKey = "messages-square", IsCloudService = true })
|
||||
Base(new ServiceModel { Id = ServiceTeamsId, Name = "Microsoft Teams", Description = "Cloud collaboration workload in Microsoft 365.", IconKey = "messages-square", IsCloudService = true }),
|
||||
Base(new ServiceModel { Id = ServiceConfigurationDataId, Name = "DSC Configuration Data", Description = "Reusable configuration-data template building blocks for deployment composition.", IconKey = "braces", IsCloudService = false })
|
||||
];
|
||||
|
||||
private static object[] ServiceRoleDefinitions() =>
|
||||
@@ -129,7 +157,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
Base(new TemplateCategoryModel { Id = CategoryActiveDirectoryId, ServiceId = ServiceActiveDirectoryId, Name = "Domain Services", Description = "Templates for domain controller and domain configuration.", Color = "#2563EB", IsActive = true }),
|
||||
Base(new TemplateCategoryModel { Id = CategorySqlServerId, ServiceId = ServiceSqlServerId, Name = "Database Platform", Description = "Templates for SQL Server workloads.", Color = "#16A34A", IsActive = true }),
|
||||
Base(new TemplateCategoryModel { Id = CategorySharePointId, ServiceId = ServiceSharePointId, Name = "Collaboration Farm", Description = "Templates for SharePoint Server farms.", Color = "#0F766E", IsActive = true }),
|
||||
Base(new TemplateCategoryModel { Id = CategoryTeamsId, ServiceId = ServiceTeamsId, Name = "Teams Policies", Description = "Templates for Teams policy configuration.", Color = "#7C3AED", IsActive = true })
|
||||
Base(new TemplateCategoryModel { Id = CategoryTeamsId, ServiceId = ServiceTeamsId, Name = "Teams Policies", Description = "Templates for Teams policy configuration.", Color = "#7C3AED", IsActive = true }),
|
||||
Base(new TemplateCategoryModel { Id = CategoryBgwMergeTemplatesId, ServiceId = ServiceConfigurationDataId, Name = "Test.Merge.ps1 Templates", Description = "Materialized template building blocks from the BGW Test.Merge.ps1 scenario.", Color = "#2563EB", IsActive = true })
|
||||
];
|
||||
|
||||
private static object[] DeploymentRules() =>
|
||||
@@ -149,7 +178,11 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
Base(new TemplateModel { Id = TemplateActiveDirectoryId, TemplateCategoryId = CategoryActiveDirectoryId, DeploymentRuleId = RuleStandardId, Name = "Contoso Active Directory Domain", Version = "1.0.0", Description = "Creates a reusable Active Directory domain baseline.", JSONData = ActiveDirectoryTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateSqlServerId, TemplateCategoryId = CategorySqlServerId, DeploymentRuleId = RuleStandardId, Name = "Contoso SQL Server", Version = "1.0.0", Description = "Creates a SQL Server baseline for application workloads.", JSONData = SqlServerTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateSharePointId, TemplateCategoryId = CategorySharePointId, DeploymentRuleId = RuleStandardId, Name = "Contoso SharePoint Server", Version = "1.0.0", Description = "Creates a SharePoint Server farm baseline.", JSONData = SharePointTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateTeamsId, TemplateCategoryId = CategoryTeamsId, DeploymentRuleId = RuleStandardId, Name = "Contoso Teams Policies", Version = "1.0.0", Description = "Creates a Microsoft Teams policy baseline.", JSONData = TeamsTemplateJson })
|
||||
Base(new TemplateModel { Id = TemplateTeamsId, TemplateCategoryId = CategoryTeamsId, DeploymentRuleId = RuleStandardId, Name = "Contoso Teams Policies", Version = "1.0.0", Description = "Creates a Microsoft Teams policy baseline.", JSONData = TeamsTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateBgwEnvironmentTestId, TemplateCategoryId = CategoryBgwMergeTemplatesId, DeploymentRuleId = RuleStandardId, Name = "BGW Environment Test", Version = "1.0.0", Description = "Materialized Environment/Test.psd1 template from Test.Merge.ps1.", JSONData = BgwEnvironmentTestTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateBgwDomainContosoId, TemplateCategoryId = CategoryBgwMergeTemplatesId, DeploymentRuleId = RuleStandardId, Name = "BGW Domain Contoso", Version = "1.0.0", Description = "Materialized Domain/Contoso.psd1 template from Test.Merge.ps1.", JSONData = BgwDomainContosoTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateBgwSharePointContosoId, TemplateCategoryId = CategoryBgwMergeTemplatesId, DeploymentRuleId = RuleStandardId, Name = "BGW SharePoint Contoso", Version = "1.0.0", Description = "Materialized Service/SharePoint/Contoso.psd1 template from Test.Merge.ps1.", JSONData = BgwSharePointContosoTemplateJson }),
|
||||
Base(new TemplateModel { Id = TemplateBgwStageInstallId, TemplateCategoryId = CategoryBgwMergeTemplatesId, DeploymentRuleId = RuleStandardId, Name = "BGW Stage Install", Version = "1.0.0", Description = "Materialized Stage/Install.psd1 template from Test.Merge.ps1.", JSONData = BgwStageInstallTemplateJson })
|
||||
];
|
||||
|
||||
private static object[] TemplateVersions() =>
|
||||
@@ -157,23 +190,35 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
TemplateVersion(TemplateActiveDirectoryVersionId, TemplateActiveDirectoryId, ActiveDirectoryTemplateJson),
|
||||
TemplateVersion(TemplateSqlServerVersionId, TemplateSqlServerId, SqlServerTemplateJson),
|
||||
TemplateVersion(TemplateSharePointVersionId, TemplateSharePointId, SharePointTemplateJson),
|
||||
TemplateVersion(TemplateTeamsVersionId, TemplateTeamsId, TeamsTemplateJson)
|
||||
TemplateVersion(TemplateTeamsVersionId, TemplateTeamsId, TeamsTemplateJson),
|
||||
TemplateVersion(TemplateBgwEnvironmentTestVersionId, TemplateBgwEnvironmentTestId, BgwEnvironmentTestTemplateJson),
|
||||
TemplateVersion(TemplateBgwDomainContosoVersionId, TemplateBgwDomainContosoId, BgwDomainContosoTemplateJson),
|
||||
TemplateVersion(TemplateBgwSharePointContosoVersionId, TemplateBgwSharePointContosoId, BgwSharePointContosoTemplateJson),
|
||||
TemplateVersion(TemplateBgwStageInstallVersionId, TemplateBgwStageInstallId, BgwStageInstallTemplateJson)
|
||||
];
|
||||
|
||||
private static object[] DeploymentBatches() =>
|
||||
[
|
||||
Base(new DeploymentGroupModel { Id = DeploymentBatchSharePointId, TemplateId = TemplateSharePointId, DeploymentRuleId = RuleStandardId, Status = QueueJobStatus.Pending })
|
||||
Base(new DeploymentGroupModel { Id = DeploymentBatchSharePointId, TemplateId = TemplateSharePointId, DeploymentRuleId = RuleStandardId, Status = QueueJobStatus.Pending }),
|
||||
Base(new DeploymentGroupModel { Id = DeploymentBatchBgwMergeId, TemplateId = TemplateBgwSharePointContosoId, DeploymentRuleId = RuleStandardId, Status = QueueJobStatus.Pending })
|
||||
];
|
||||
|
||||
private static object[] Deployments() =>
|
||||
[
|
||||
Base(new DeploymentModel { Id = DeploymentSp01Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp01Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"WebFrontEnd\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Application\"}" })
|
||||
Base(new DeploymentModel { Id = DeploymentSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Application\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp01Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp01Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp02Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp02Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" })
|
||||
];
|
||||
|
||||
private static object[] DeploymentTemplateSelections() =>
|
||||
[
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentSharePointTemplateSelectionId, DeploymentGroupId = DeploymentBatchSharePointId, TemplateVersionId = TemplateSharePointVersionId, TemplateRole = "Service", SortOrder = 10, Alias = "SharePoint" })
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentSharePointTemplateSelectionId, DeploymentGroupId = DeploymentBatchSharePointId, TemplateVersionId = TemplateSharePointVersionId, TemplateRole = "Service", SortOrder = 10, Alias = "SharePoint" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwEnvironmentSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwEnvironmentTestVersionId, TemplateRole = "Environment", SortOrder = 10, Alias = "Environment-Test" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwDomainSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwDomainContosoVersionId, TemplateRole = "Domain", SortOrder = 20, Alias = "Domain-Contoso" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwSharePointSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwSharePointContosoVersionId, TemplateRole = "Service", SortOrder = 30, Alias = "Service-SharePoint-Contoso" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwStageSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwStageInstallVersionId, TemplateRole = "Stage", SortOrder = 40, Alias = "Stage-Install" })
|
||||
];
|
||||
|
||||
private static object[] DeploymentParameterValues() =>
|
||||
@@ -185,7 +230,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
private static object[] DeploymentTargetAssignments() =>
|
||||
[
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetSp01Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp01Id, RoleKey = "WebFrontEnd", SortOrder = 10, NodeDataJson = "{\"nodeName\":\"CT-SHP-01\"}" }),
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, RoleKey = "Application", SortOrder = 20, NodeDataJson = "{\"nodeName\":\"CT-SHP-02\"}" })
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, RoleKey = "Application", SortOrder = 20, NodeDataJson = "{\"nodeName\":\"CT-SHP-02\"}" }),
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetBgwSp01Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp01Id, RoleKey = "Node", SortOrder = 10, NodeDataJson = "{\"nodeName\":\"CLD-SHP-01\",\"RunCentralAdministration\":true}" }),
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetBgwSp02Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp02Id, RoleKey = "Node", SortOrder = 20, NodeDataJson = "{\"nodeName\":\"CLD-SHP-02\",\"RunCentralAdministration\":false}" }),
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, RoleKey = "Node", SortOrder = 30, NodeDataJson = "{\"nodeName\":\"CLD-SHP-03\",\"RunCentralAdministration\":false}" })
|
||||
];
|
||||
|
||||
private static TemplateVersionModel TemplateVersion(Guid id, Guid templateId, string jsonData)
|
||||
|
||||
Reference in New Issue
Block a user