feat: Implement API client and token models with hashing and JWT authentication
- Added ApiClientModel and ApiTokenModel for managing API clients and tokens. - Introduced ConfigurationDefinitionModel and ConfigurationValueModel for configuration management. - Created CredentialSecretModel for storing credential secrets. - Developed DeploymentArtifactModel and DeploymentBatchModel for deployment management. - Enhanced DeploymentTargetModel and DeploymentTemplateSelectionModel to support template revisions. - Added TemplateRevisionModel and TemplateVersionModel for versioning templates. - Implemented ApiClientSecretHasher for secure secret hashing. - Created ApiTokenService for generating and validating JWT tokens. - Updated QueueJobService to handle deployment requests with artifacts. - Configured authentication settings in appsettings.json for JWT and Negotiate authentication.
This commit is contained in:
@@ -19,6 +19,13 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
public DbSet<DeploymentGroupModel> DeploymentGroups { get; set; }
|
||||
public DbSet<TemplateModel> Templates { get; set; }
|
||||
public DbSet<TemplateVersionModel> TemplateVersions { get; set; }
|
||||
public DbSet<TemplateRevisionModel> TemplateRevisions { get; set; }
|
||||
public DbSet<ConfigurationDefinitionModel> ConfigurationDefinitions { get; set; }
|
||||
public DbSet<ConfigurationValueModel> ConfigurationValues { get; set; }
|
||||
public DbSet<DeploymentArtifactModel> DeploymentArtifacts { get; set; }
|
||||
public DbSet<CredentialSecretModel> CredentialSecrets { get; set; }
|
||||
public DbSet<ApiClientModel> ApiClients { get; set; }
|
||||
public DbSet<ApiTokenModel> ApiTokens { get; set; }
|
||||
public DbSet<DeploymentRuleModel> DeploymentRules { get; set; }
|
||||
public DbSet<DeploymentRuleStepModel> DeploymentRuleSteps { get; set; }
|
||||
public DbSet<DeploymentTemplateSelectionModel> DeploymentTemplateSelections { get; set; }
|
||||
@@ -148,6 +155,18 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
.WithMany(dg => dg.Deployments)
|
||||
.HasForeignKey(d => d.DeploymentGroupId);
|
||||
|
||||
modelBuilder.Entity<DeploymentModel>()
|
||||
.HasOne(deployment => deployment.TemplateRevision)
|
||||
.WithMany(revision => revision.Deployments)
|
||||
.HasForeignKey(deployment => deployment.TemplateRevisionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeploymentModel>()
|
||||
.HasOne(deployment => deployment.CurrentArtifact)
|
||||
.WithMany()
|
||||
.HasForeignKey(deployment => deployment.CurrentArtifactId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
modelBuilder.Entity<DeploymentGroupModel>()
|
||||
.HasOne(dg => dg.Template)
|
||||
.WithMany(t => t.DeploymentGroups)
|
||||
@@ -170,6 +189,12 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
.HasForeignKey(selection => selection.TemplateVersionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeploymentTemplateSelectionModel>()
|
||||
.HasOne(selection => selection.TemplateRevision)
|
||||
.WithMany(revision => revision.DeploymentTemplateSelections)
|
||||
.HasForeignKey(selection => selection.TemplateRevisionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeploymentTemplateSelectionModel>()
|
||||
.HasIndex(selection => new { selection.DeploymentGroupId, selection.SortOrder })
|
||||
.IsUnique();
|
||||
@@ -250,6 +275,161 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
modelBuilder.Entity<TemplateVersionModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_TemplateVersions_JsonData_IsJson", "ISJSON([JsonData]) = 1"));
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.HasOne(revision => revision.TemplateVersion)
|
||||
.WithMany(version => version.TemplateRevisions)
|
||||
.HasForeignKey(revision => revision.TemplateVersionId);
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.HasIndex(revision => new { revision.TemplateVersionId, revision.RevisionNumber })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.HasIndex(revision => revision.TemplateVersionId)
|
||||
.IsUnique()
|
||||
.HasFilter("[IsCurrent] = 1");
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.Property(revision => revision.JsonHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.Property(revision => revision.SchemaVersion)
|
||||
.HasMaxLength(32);
|
||||
|
||||
modelBuilder.Entity<TemplateRevisionModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_TemplateRevisions_JsonData_IsJson", "ISJSON([JsonData]) = 1"));
|
||||
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>()
|
||||
.HasOne(definition => definition.TemplateRevision)
|
||||
.WithMany(revision => revision.ConfigurationDefinitions)
|
||||
.HasForeignKey(definition => definition.TemplateRevisionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>()
|
||||
.HasIndex(definition => new { definition.TemplateRevisionId, definition.Kind, definition.Name })
|
||||
.IsUnique()
|
||||
.HasFilter("[TemplateRevisionId] IS NOT NULL");
|
||||
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>()
|
||||
.Property(definition => definition.Kind)
|
||||
.HasMaxLength(32);
|
||||
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>()
|
||||
.Property(definition => definition.DefinitionHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_ConfigurationDefinitions_PropertiesJson_IsJson", "ISJSON([PropertiesJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.HasOne(value => value.ConfigurationDefinition)
|
||||
.WithMany(definition => definition.Values)
|
||||
.HasForeignKey(value => value.ConfigurationDefinitionId);
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.HasIndex(value => new { value.ConfigurationDefinitionId, value.ScopeType, value.ScopeId, value.SortOrder });
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.Property(value => value.ScopeType)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.Property(value => value.ValueSourceType)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.Property(value => value.ValueHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<ConfigurationValueModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_ConfigurationValues_ValueJson_IsJson", "[ValueJson] IS NULL OR ISJSON([ValueJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.HasOne(artifact => artifact.DeploymentGroup)
|
||||
.WithMany(group => group.Artifacts)
|
||||
.HasForeignKey(artifact => artifact.DeploymentGroupId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.HasOne(artifact => artifact.Deployment)
|
||||
.WithMany(deployment => deployment.Artifacts)
|
||||
.HasForeignKey(artifact => new { artifact.TargetId, artifact.DeploymentGroupId })
|
||||
.HasPrincipalKey(deployment => new { deployment.TargetId, deployment.DeploymentGroupId })
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.HasOne(artifact => artifact.Target)
|
||||
.WithMany()
|
||||
.HasForeignKey(artifact => artifact.TargetId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.Property(artifact => artifact.ArtifactType)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.Property(artifact => artifact.InputHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.Property(artifact => artifact.OutputHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_DeploymentJson_IsJson", "ISJSON([DeploymentJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_SourceJson_IsJson", "[SourceJson] IS NULL OR ISJSON([SourceJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_ResolvedJson_IsJson", "[ResolvedJson] IS NULL OR ISJSON([ResolvedJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentArtifactModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_SourceSnapshotJson_IsJson", "[SourceSnapshotJson] IS NULL OR ISJSON([SourceSnapshotJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<CredentialSecretModel>()
|
||||
.HasIndex(secret => secret.Name)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<CredentialSecretModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_CredentialSecrets_MetadataJson_IsJson", "[MetadataJson] IS NULL OR ISJSON([MetadataJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<ApiClientModel>()
|
||||
.HasIndex(client => client.ClientId)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<ApiClientModel>()
|
||||
.Property(client => client.ClientId)
|
||||
.HasMaxLength(128);
|
||||
|
||||
modelBuilder.Entity<ApiClientModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_ApiClients_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.HasOne(token => token.ApiClient)
|
||||
.WithMany()
|
||||
.HasForeignKey(token => token.ApiClientId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.HasIndex(token => token.Jti)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.HasIndex(token => new { token.SubjectType, token.Subject });
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.Property(token => token.Jti)
|
||||
.HasMaxLength(64);
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.Property(token => token.SubjectType)
|
||||
.HasMaxLength(32);
|
||||
|
||||
modelBuilder.Entity<ApiTokenModel>()
|
||||
.ToTable(table => table.HasCheckConstraint("CK_ApiTokens_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1"));
|
||||
|
||||
modelBuilder.Entity<DeploymentRuleStepModel>()
|
||||
.HasOne(step => step.DeploymentRule)
|
||||
.WithMany(rule => rule.Steps)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.SelfService.Portal.Core.API.Models;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.SelfService.Portal.Core.API.Services;
|
||||
|
||||
namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
{
|
||||
@@ -61,6 +65,15 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
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 TemplateActiveDirectoryRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid TemplateSqlServerRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid TemplateSharePointRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid TemplateTeamsRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid TemplateBgwEnvironmentTestRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000101");
|
||||
internal static readonly Guid TemplateBgwDomainContosoRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000102");
|
||||
internal static readonly Guid TemplateBgwSharePointContosoRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000103");
|
||||
internal static readonly Guid TemplateBgwStageInstallRevisionId = Guid.Parse("72000000-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");
|
||||
@@ -81,6 +94,15 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
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 readonly Guid DeploymentArtifactBgwResolvedId = Guid.Parse("85000000-0000-0000-0000-000000000101");
|
||||
|
||||
internal static readonly Guid CredentialSharePointSetupId = Guid.Parse("90000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid CredentialSharePointFarmId = Guid.Parse("90000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid CredentialSharePointFarmPassphraseId = Guid.Parse("90000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid CredentialSharePointDefaultServiceId = Guid.Parse("90000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid CredentialSharePointSearchId = Guid.Parse("90000000-0000-0000-0000-000000000005");
|
||||
internal static readonly Guid ApiClientDemoWorkerId = Guid.Parse("91000000-0000-0000-0000-000000000001");
|
||||
|
||||
internal static void Seed(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<EnvironmentModel>().HasData(Environments());
|
||||
@@ -94,11 +116,17 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
modelBuilder.Entity<DeploymentRuleStepModel>().HasData(DeploymentRuleSteps());
|
||||
modelBuilder.Entity<TemplateModel>().HasData(Templates());
|
||||
modelBuilder.Entity<TemplateVersionModel>().HasData(TemplateVersions());
|
||||
modelBuilder.Entity<TemplateRevisionModel>().HasData(TemplateRevisions());
|
||||
modelBuilder.Entity<ConfigurationDefinitionModel>().HasData(ConfigurationDefinitions());
|
||||
modelBuilder.Entity<ConfigurationValueModel>().HasData(ConfigurationValues());
|
||||
modelBuilder.Entity<DeploymentGroupModel>().HasData(DeploymentBatches());
|
||||
modelBuilder.Entity<DeploymentModel>().HasData(Deployments());
|
||||
modelBuilder.Entity<DeploymentTemplateSelectionModel>().HasData(DeploymentTemplateSelections());
|
||||
modelBuilder.Entity<DeploymentParameterValueModel>().HasData(DeploymentParameterValues());
|
||||
modelBuilder.Entity<DeploymentTargetAssignmentModel>().HasData(DeploymentTargetAssignments());
|
||||
modelBuilder.Entity<DeploymentArtifactModel>().HasData(DeploymentArtifacts());
|
||||
modelBuilder.Entity<CredentialSecretModel>().HasData(CredentialSecrets());
|
||||
modelBuilder.Entity<ApiClientModel>().HasData(ApiClients());
|
||||
}
|
||||
|
||||
private static object[] Environments() =>
|
||||
@@ -197,6 +225,18 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
TemplateVersion(TemplateBgwStageInstallVersionId, TemplateBgwStageInstallId, BgwStageInstallTemplateJson)
|
||||
];
|
||||
|
||||
private static object[] TemplateRevisions() =>
|
||||
[
|
||||
TemplateRevision(TemplateActiveDirectoryRevisionId, TemplateActiveDirectoryVersionId, ActiveDirectoryTemplateJson),
|
||||
TemplateRevision(TemplateSqlServerRevisionId, TemplateSqlServerVersionId, SqlServerTemplateJson),
|
||||
TemplateRevision(TemplateSharePointRevisionId, TemplateSharePointVersionId, SharePointTemplateJson),
|
||||
TemplateRevision(TemplateTeamsRevisionId, TemplateTeamsVersionId, TeamsTemplateJson),
|
||||
TemplateRevision(TemplateBgwEnvironmentTestRevisionId, TemplateBgwEnvironmentTestVersionId, BgwEnvironmentTestTemplateJson),
|
||||
TemplateRevision(TemplateBgwDomainContosoRevisionId, TemplateBgwDomainContosoVersionId, BgwDomainContosoTemplateJson),
|
||||
TemplateRevision(TemplateBgwSharePointContosoRevisionId, TemplateBgwSharePointContosoVersionId, BgwSharePointContosoTemplateJson),
|
||||
TemplateRevision(TemplateBgwStageInstallRevisionId, TemplateBgwStageInstallVersionId, BgwStageInstallTemplateJson)
|
||||
];
|
||||
|
||||
private static object[] DeploymentBatches() =>
|
||||
[
|
||||
Base(new DeploymentGroupModel { Id = DeploymentBatchSharePointId, TemplateId = TemplateSharePointId, DeploymentRuleId = RuleStandardId, Status = QueueJobStatus.Pending }),
|
||||
@@ -205,20 +245,20 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
|
||||
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 = 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\"}" })
|
||||
Base(new DeploymentModel { Id = DeploymentSp01Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp01Id, TemplateRevisionId = TemplateSharePointRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"WebFrontEnd\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, TemplateRevisionId = TemplateSharePointRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Application\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp01Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp01Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp02Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp02Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
|
||||
Base(new DeploymentModel { Id = DeploymentBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, 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 = 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" })
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentSharePointTemplateSelectionId, DeploymentGroupId = DeploymentBatchSharePointId, TemplateVersionId = TemplateSharePointVersionId, TemplateRevisionId = TemplateSharePointRevisionId, TemplateRole = "Service", SortOrder = 10, Alias = "SharePoint" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwEnvironmentSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwEnvironmentTestVersionId, TemplateRevisionId = TemplateBgwEnvironmentTestRevisionId, TemplateRole = "Environment", SortOrder = 10, Alias = "Environment-Test" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwDomainSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwDomainContosoVersionId, TemplateRevisionId = TemplateBgwDomainContosoRevisionId, TemplateRole = "Domain", SortOrder = 20, Alias = "Domain-Contoso" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwSharePointSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwSharePointContosoVersionId, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, TemplateRole = "Service", SortOrder = 30, Alias = "Service-SharePoint-Contoso" }),
|
||||
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwStageSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwStageInstallVersionId, TemplateRevisionId = TemplateBgwStageInstallRevisionId, TemplateRole = "Stage", SortOrder = 40, Alias = "Stage-Install" })
|
||||
];
|
||||
|
||||
private static object[] DeploymentParameterValues() =>
|
||||
@@ -236,6 +276,99 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, RoleKey = "Node", SortOrder = 30, NodeDataJson = "{\"nodeName\":\"CLD-SHP-03\",\"RunCentralAdministration\":false}" })
|
||||
];
|
||||
|
||||
private static object[] ConfigurationDefinitions()
|
||||
{
|
||||
return TemplateRevisionJsonDocuments()
|
||||
.SelectMany(template => ExtractConfigurationDefinitions(template.RevisionId, template.JsonData))
|
||||
.Select(definition => Base(definition))
|
||||
.Cast<object>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static object[] ConfigurationValues()
|
||||
{
|
||||
return TemplateRevisionJsonDocuments()
|
||||
.SelectMany(template => ExtractConfigurationValues(template.RevisionId, template.JsonData))
|
||||
.Select(value => Base(value))
|
||||
.Cast<object>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static object[] DeploymentArtifacts()
|
||||
{
|
||||
var deploymentJson = """
|
||||
{
|
||||
"metadata": {
|
||||
"name": "Contoso-Test-SharePoint",
|
||||
"deploymentId": "8f6c2c1a",
|
||||
"source": "DemoData"
|
||||
},
|
||||
"parameters": {
|
||||
"DatabasePrefix": "SharePoint_Contoso_Test"
|
||||
},
|
||||
"variables": {
|
||||
"DatabasePrefix": "SharePoint_Contoso_Test",
|
||||
"ServiceDbPrefix": "SharePoint_Contoso_Test_Services",
|
||||
"ConfigDbName": "SharePoint_Contoso_Test_Farm_Config"
|
||||
},
|
||||
"resources": {
|
||||
"AllNodes": [
|
||||
{ "NodeName": "CLD-SHP-01", "RunCentralAdministration": true },
|
||||
{ "NodeName": "CLD-SHP-02", "RunCentralAdministration": false },
|
||||
{ "NodeName": "CLD-SHP-03", "RunCentralAdministration": false }
|
||||
],
|
||||
"NonNodeData": {
|
||||
"LocalConfigurationManager": {
|
||||
"ConfigurationMode": "ApplyOnly"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var sourceSnapshotJson = BgwMergeSourceSnapshotJson();
|
||||
|
||||
return
|
||||
[
|
||||
Base(new DeploymentArtifactModel
|
||||
{
|
||||
Id = DeploymentArtifactBgwResolvedId,
|
||||
DeploymentGroupId = DeploymentBatchBgwMergeId,
|
||||
ArtifactType = DeploymentArtifactTypes.ResolvedConfigurationData,
|
||||
Status = QueueJobStatus.Pending,
|
||||
DeploymentJson = deploymentJson,
|
||||
SourceJson = "{\"source\":\"DemoData\",\"composition\":\"Test.Merge.ps1\"}",
|
||||
ResolvedJson = deploymentJson,
|
||||
SourceSnapshotJson = sourceSnapshotJson,
|
||||
InputHash = TemplateVersionModel.ComputeSha256(sourceSnapshotJson),
|
||||
OutputHash = TemplateVersionModel.ComputeSha256(deploymentJson),
|
||||
IsStale = false
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
private static object[] CredentialSecrets() =>
|
||||
[
|
||||
CredentialSecret(CredentialSharePointSetupId, "Windows/SharePoint/SetupAccount", "CONTOSO\\svc_sp_setup"),
|
||||
CredentialSecret(CredentialSharePointFarmId, "Windows/SharePoint/FarmAccount", "CONTOSO\\svc_sp_farm"),
|
||||
CredentialSecret(CredentialSharePointFarmPassphraseId, "Windows/SharePoint/FarmPassphrase", null, "Secret"),
|
||||
CredentialSecret(CredentialSharePointDefaultServiceId, "Windows/SharePoint/DefaultServiceAccount", "CONTOSO\\svc_sp_service"),
|
||||
CredentialSecret(CredentialSharePointSearchId, "Windows/SharePoint/SearchAccount", "CONTOSO\\svc_sp_search")
|
||||
];
|
||||
|
||||
private static object[] ApiClients() =>
|
||||
[
|
||||
Base(new ApiClientModel
|
||||
{
|
||||
Id = ApiClientDemoWorkerId,
|
||||
ClientId = "ssp-demo-worker",
|
||||
Name = "Demo Worker Client",
|
||||
SecretHash = ApiClientSecretHasher.HashSecretForDemoData("DemoOnly-DoNotUseInProduction!", "ssp-demo-worker"),
|
||||
ScopesJson = "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\",\"token.manage\",\"token.admin\"]",
|
||||
IsEnabled = true
|
||||
})
|
||||
];
|
||||
|
||||
private static TemplateVersionModel TemplateVersion(Guid id, Guid templateId, string jsonData)
|
||||
{
|
||||
return Base(new TemplateVersionModel
|
||||
@@ -252,6 +385,229 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
|
||||
});
|
||||
}
|
||||
|
||||
private static TemplateRevisionModel TemplateRevision(Guid id, Guid templateVersionId, string jsonData)
|
||||
{
|
||||
return Base(new TemplateRevisionModel
|
||||
{
|
||||
Id = id,
|
||||
TemplateVersionId = templateVersionId,
|
||||
RevisionNumber = 1,
|
||||
JsonData = jsonData,
|
||||
JsonHash = TemplateVersionModel.ComputeSha256(jsonData),
|
||||
SchemaVersion = "1.0",
|
||||
IsCurrent = true,
|
||||
IsPublished = true,
|
||||
PublishedAt = Timestamp,
|
||||
PublishedBy = Owner
|
||||
});
|
||||
}
|
||||
|
||||
private static CredentialSecretModel CredentialSecret(Guid id, string name, string? userName, string secretType = "Credential")
|
||||
{
|
||||
return Base(new CredentialSecretModel
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
UserName = userName,
|
||||
SecretValue = "DemoOnly-DoNotUseInProduction!",
|
||||
SecretType = secretType,
|
||||
MetadataJson = "{\"environment\":\"Demo\",\"plaintext\":true}",
|
||||
IsEnabled = true
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<(Guid RevisionId, string JsonData)> TemplateRevisionJsonDocuments()
|
||||
{
|
||||
yield return (TemplateActiveDirectoryRevisionId, ActiveDirectoryTemplateJson);
|
||||
yield return (TemplateSqlServerRevisionId, SqlServerTemplateJson);
|
||||
yield return (TemplateSharePointRevisionId, SharePointTemplateJson);
|
||||
yield return (TemplateTeamsRevisionId, TeamsTemplateJson);
|
||||
yield return (TemplateBgwEnvironmentTestRevisionId, BgwEnvironmentTestTemplateJson);
|
||||
yield return (TemplateBgwDomainContosoRevisionId, BgwDomainContosoTemplateJson);
|
||||
yield return (TemplateBgwSharePointContosoRevisionId, BgwSharePointContosoTemplateJson);
|
||||
yield return (TemplateBgwStageInstallRevisionId, BgwStageInstallTemplateJson);
|
||||
}
|
||||
|
||||
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, string jsonData)
|
||||
{
|
||||
using var document = JsonDocument.Parse(jsonData);
|
||||
var root = document.RootElement;
|
||||
|
||||
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "parameters", ConfigurationDefinitionKinds.Parameter))
|
||||
yield return definition;
|
||||
|
||||
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "variables", ConfigurationDefinitionKinds.Variable))
|
||||
yield return definition;
|
||||
}
|
||||
|
||||
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, JsonElement root, string propertyName, string kind)
|
||||
{
|
||||
if (!root.TryGetProperty(propertyName, out var definitions) || definitions.ValueKind != JsonValueKind.Object)
|
||||
yield break;
|
||||
|
||||
foreach (var property in definitions.EnumerateObject())
|
||||
{
|
||||
var propertiesJson = kind == ConfigurationDefinitionKinds.Variable
|
||||
? $$"""{"Expression":{{property.Value.GetRawText()}}}"""
|
||||
: property.Value.GetRawText();
|
||||
yield return new ConfigurationDefinitionModel
|
||||
{
|
||||
Id = ConfigurationDefinitionId(revisionId, kind, property.Name),
|
||||
TemplateRevisionId = revisionId,
|
||||
Kind = kind,
|
||||
Name = property.Name,
|
||||
PropertiesJson = propertiesJson,
|
||||
DefinitionHash = TemplateVersionModel.ComputeSha256(propertiesJson)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ConfigurationValueModel> ExtractConfigurationValues(Guid revisionId, string jsonData)
|
||||
{
|
||||
using var document = JsonDocument.Parse(jsonData);
|
||||
var root = document.RootElement;
|
||||
|
||||
foreach (var value in ExtractParameterValues(revisionId, root))
|
||||
yield return value;
|
||||
|
||||
foreach (var value in ExtractVariableValues(revisionId, root))
|
||||
yield return value;
|
||||
}
|
||||
|
||||
private static IEnumerable<ConfigurationValueModel> ExtractParameterValues(Guid revisionId, JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("parameters", out var parameters) || parameters.ValueKind != JsonValueKind.Object)
|
||||
yield break;
|
||||
|
||||
var sortOrder = 0;
|
||||
foreach (var parameter in parameters.EnumerateObject())
|
||||
{
|
||||
sortOrder += 10;
|
||||
|
||||
var valueSourceType = ConfigurationValueSourceTypes.Static;
|
||||
var valueJson = "{}";
|
||||
|
||||
if (parameter.Value.ValueKind == JsonValueKind.Object && parameter.Value.TryGetProperty("Value", out var explicitValue))
|
||||
{
|
||||
valueJson = $$"""{"value":{{explicitValue.GetRawText()}}}""";
|
||||
}
|
||||
else if (parameter.Value.ValueKind == JsonValueKind.Object && parameter.Value.TryGetProperty("DefaultValue", out var defaultValue))
|
||||
{
|
||||
valueJson = $$"""{"value":{{defaultValue.GetRawText()}}}""";
|
||||
}
|
||||
|
||||
if (parameter.Value.ValueKind == JsonValueKind.Object
|
||||
&& parameter.Value.TryGetProperty("Sensitive", out var sensitive)
|
||||
&& sensitive.ValueKind == JsonValueKind.True)
|
||||
{
|
||||
valueSourceType = ConfigurationValueSourceTypes.SecretReference;
|
||||
}
|
||||
|
||||
yield return ConfigurationValue(
|
||||
revisionId,
|
||||
ConfigurationDefinitionKinds.Parameter,
|
||||
parameter.Name,
|
||||
valueSourceType,
|
||||
valueJson,
|
||||
sortOrder);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ConfigurationValueModel> ExtractVariableValues(Guid revisionId, JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("variables", out var variables) || variables.ValueKind != JsonValueKind.Object)
|
||||
yield break;
|
||||
|
||||
var sortOrder = 0;
|
||||
foreach (var variable in variables.EnumerateObject())
|
||||
{
|
||||
sortOrder += 10;
|
||||
var valueJson = $$"""{"expression":{{variable.Value.GetRawText()}}}""";
|
||||
|
||||
yield return ConfigurationValue(
|
||||
revisionId,
|
||||
ConfigurationDefinitionKinds.Variable,
|
||||
variable.Name,
|
||||
ConfigurationValueSourceTypes.Expression,
|
||||
valueJson,
|
||||
sortOrder);
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigurationValueModel ConfigurationValue(Guid revisionId, string kind, string name, string valueSourceType, string valueJson, int sortOrder)
|
||||
{
|
||||
return new ConfigurationValueModel
|
||||
{
|
||||
Id = ConfigurationValueId(revisionId, kind, name),
|
||||
ConfigurationDefinitionId = ConfigurationDefinitionId(revisionId, kind, name),
|
||||
ScopeType = ConfigurationValueScopeTypes.TemplateRevision,
|
||||
ScopeId = revisionId,
|
||||
ValueSourceType = valueSourceType,
|
||||
ValueJson = valueJson,
|
||||
ValueHash = TemplateVersionModel.ComputeSha256(valueJson),
|
||||
SortOrder = sortOrder
|
||||
};
|
||||
}
|
||||
|
||||
private static Guid ConfigurationDefinitionId(Guid revisionId, string kind, string name)
|
||||
{
|
||||
return DeterministicGuid($"ConfigurationDefinition:{revisionId:N}:{kind}:{name}");
|
||||
}
|
||||
|
||||
private static Guid ConfigurationValueId(Guid revisionId, string kind, string name)
|
||||
{
|
||||
return DeterministicGuid($"ConfigurationValue:{revisionId:N}:{kind}:{name}");
|
||||
}
|
||||
|
||||
private static Guid DeterministicGuid(string value)
|
||||
{
|
||||
var hash = MD5.HashData(Encoding.UTF8.GetBytes(value));
|
||||
return new Guid(hash);
|
||||
}
|
||||
|
||||
private static string BgwMergeSourceSnapshotJson()
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
"templateRevisions": [
|
||||
{
|
||||
"id": "{{TemplateBgwEnvironmentTestRevisionId}}",
|
||||
"templateVersionId": "{{TemplateBgwEnvironmentTestVersionId}}",
|
||||
"role": "Environment",
|
||||
"alias": "Environment-Test",
|
||||
"hash": "{{TemplateVersionModel.ComputeSha256(BgwEnvironmentTestTemplateJson)}}"
|
||||
},
|
||||
{
|
||||
"id": "{{TemplateBgwDomainContosoRevisionId}}",
|
||||
"templateVersionId": "{{TemplateBgwDomainContosoVersionId}}",
|
||||
"role": "Domain",
|
||||
"alias": "Domain-Contoso",
|
||||
"hash": "{{TemplateVersionModel.ComputeSha256(BgwDomainContosoTemplateJson)}}"
|
||||
},
|
||||
{
|
||||
"id": "{{TemplateBgwSharePointContosoRevisionId}}",
|
||||
"templateVersionId": "{{TemplateBgwSharePointContosoVersionId}}",
|
||||
"role": "Service",
|
||||
"alias": "Service-SharePoint-Contoso",
|
||||
"hash": "{{TemplateVersionModel.ComputeSha256(BgwSharePointContosoTemplateJson)}}"
|
||||
},
|
||||
{
|
||||
"id": "{{TemplateBgwStageInstallRevisionId}}",
|
||||
"templateVersionId": "{{TemplateBgwStageInstallVersionId}}",
|
||||
"role": "Stage",
|
||||
"alias": "Stage-Install",
|
||||
"hash": "{{TemplateVersionModel.ComputeSha256(BgwStageInstallTemplateJson)}}"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{ "id": "{{TargetBgwSp01Id}}", "nodeName": "CLD-SHP-01" },
|
||||
{ "id": "{{TargetBgwSp02Id}}", "nodeName": "CLD-SHP-02" },
|
||||
{ "id": "{{TargetBgwSp03Id}}", "nodeName": "CLD-SHP-03" }
|
||||
]
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static T Base<T>(T model)
|
||||
where T : BaseModel
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user