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:
Torsten Brendgen
2026-07-09 14:44:38 +02:00
parent 1aa2adac08
commit dc93ea0813
72 changed files with 32906 additions and 516 deletions

View File

@@ -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
{