- 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.
465 lines
18 KiB
C#
465 lines
18 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.SelfService.Portal.Core.API.Context;
|
|
using Microsoft.SelfService.Portal.Core.API.Interfaces;
|
|
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
|
|
using Microsoft.SelfService.Portal.Core.API.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace Microsoft.SelfService.Portal.Core.API.Repository
|
|
{
|
|
public class TemplateRepository : ITemplateInterface
|
|
{
|
|
private readonly DataContext _context;
|
|
|
|
public TemplateRepository(DataContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public ICollection<TemplateModel> GetTemplates()
|
|
{
|
|
var templates = _context.Templates
|
|
.AsNoTracking()
|
|
.Include(template => template.TemplateVersions)
|
|
.ToList();
|
|
|
|
foreach (var template in templates)
|
|
{
|
|
ApplyPublishedVersion(template);
|
|
}
|
|
|
|
return templates;
|
|
}
|
|
|
|
public TemplateModel GetTemplateById(Guid Id)
|
|
{
|
|
var template = _context.Templates
|
|
.AsNoTracking()
|
|
.Include(t => t.TemplateVersions)
|
|
.Where(t => t.Id == Id)
|
|
.FirstOrDefault();
|
|
|
|
if (template != null)
|
|
{
|
|
ApplyPublishedVersion(template);
|
|
}
|
|
|
|
return template;
|
|
}
|
|
|
|
public bool AddTemplateById(TemplateModel template)
|
|
{
|
|
if (template.Id == Guid.Empty)
|
|
{
|
|
template.Id = Guid.NewGuid();
|
|
}
|
|
|
|
var document = ConfigurationDocumentValidator.NormalizeAndValidate(template.JSONData, ConfigurationDocumentKind.Template);
|
|
template.JSONData = document.JsonData;
|
|
|
|
var version = new TemplateVersionModel
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TemplateId = template.Id,
|
|
Version = string.IsNullOrWhiteSpace(template.Version) ? "1.0.0" : template.Version,
|
|
SchemaVersion = document.SchemaVersion,
|
|
IsPublished = true,
|
|
PublishedAt = DateTime.UtcNow,
|
|
PublishedBy = template.CreatedBy
|
|
};
|
|
version.SetJsonData(document.JsonData);
|
|
version.TemplateRevisions.Add(CreateTemplateRevision(version.Id, document.JsonData, document.SchemaVersion, true, template.CreatedBy));
|
|
|
|
template.TemplateVersions.Add(version);
|
|
_context.Add(template);
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool EditTemplateById(TemplateModel template)
|
|
{
|
|
var existingTemplate = _context.Templates
|
|
.Include(existing => existing.TemplateVersions)
|
|
.FirstOrDefault(existing => existing.Id == template.Id);
|
|
|
|
if (existingTemplate == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var document = ConfigurationDocumentValidator.NormalizeAndValidate(template.JSONData, ConfigurationDocumentKind.Template);
|
|
var jsonData = document.JsonData;
|
|
var publishedVersion = existingTemplate.TemplateVersions.FirstOrDefault(version => version.IsPublished);
|
|
var currentRevision = publishedVersion == null ? null : GetCurrentTemplateRevision(publishedVersion.Id);
|
|
var jsonChanged = currentRevision == null || !string.Equals(currentRevision.JsonData, jsonData, StringComparison.Ordinal);
|
|
|
|
existingTemplate.Name = template.Name;
|
|
existingTemplate.Version = template.Version;
|
|
existingTemplate.Description = template.Description;
|
|
existingTemplate.JSONData = jsonData;
|
|
existingTemplate.TemplateCategoryId = template.TemplateCategoryId;
|
|
existingTemplate.DeploymentRuleId = template.DeploymentRuleId;
|
|
existingTemplate.Modified = DateTime.UtcNow;
|
|
existingTemplate.ModifiedBy = template.ModifiedBy;
|
|
|
|
if (jsonChanged)
|
|
{
|
|
var version = publishedVersion;
|
|
if (version == null || !string.Equals(version.Version, template.Version, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var versionName = GetUniqueVersionName(
|
|
existingTemplate.Id,
|
|
string.IsNullOrWhiteSpace(template.Version) ? DateTime.UtcNow.ToString("yyyyMMddHHmmss") : template.Version);
|
|
|
|
version = new TemplateVersionModel
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TemplateId = existingTemplate.Id,
|
|
Version = versionName,
|
|
SchemaVersion = document.SchemaVersion,
|
|
IsPublished = true,
|
|
PublishedAt = DateTime.UtcNow,
|
|
PublishedBy = template.ModifiedBy
|
|
};
|
|
existingTemplate.TemplateVersions.Add(version);
|
|
}
|
|
|
|
version.SetJsonData(jsonData);
|
|
version.SchemaVersion = document.SchemaVersion;
|
|
version.IsPublished = true;
|
|
version.PublishedAt = DateTime.UtcNow;
|
|
version.PublishedBy = template.ModifiedBy;
|
|
|
|
foreach (var existingVersion in existingTemplate.TemplateVersions.Where(existing => existing.Id != version.Id))
|
|
{
|
|
existingVersion.IsPublished = false;
|
|
}
|
|
|
|
var revision = CreateTemplateRevision(version.Id, jsonData, document.SchemaVersion, true, template.ModifiedBy);
|
|
_context.TemplateRevisions.Add(revision);
|
|
}
|
|
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool DeleteTemplateById(TemplateModel template)
|
|
{
|
|
_context.Remove(template);
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool CheckTemplateById(Guid Id)
|
|
{
|
|
return _context.Templates
|
|
.Any(t => t.Id == Id);
|
|
}
|
|
|
|
public TemplateModel GetTemplateByName(string Name)
|
|
{
|
|
return _context.Templates
|
|
.AsNoTracking()
|
|
.Where(t => t.Name == Name)
|
|
.FirstOrDefault();
|
|
}
|
|
|
|
public bool CheckTemplateByName(string Name)
|
|
{
|
|
return _context.Templates
|
|
.Any(t => t.Name == Name);
|
|
}
|
|
|
|
public ICollection<TemplateVersionModel> GetTemplateVersions(Guid templateId)
|
|
{
|
|
return _context.TemplateVersions
|
|
.AsNoTracking()
|
|
.Include(version => version.TemplateRevisions)
|
|
.Where(version => version.TemplateId == templateId)
|
|
.OrderByDescending(version => version.Created)
|
|
.ToList();
|
|
}
|
|
|
|
public TemplateVersionModel? GetTemplateVersionById(Guid templateId, Guid versionId)
|
|
{
|
|
return _context.TemplateVersions
|
|
.AsNoTracking()
|
|
.Include(version => version.TemplateRevisions)
|
|
.FirstOrDefault(version => version.TemplateId == templateId && version.Id == versionId);
|
|
}
|
|
|
|
public TemplateVersionModel? GetPublishedTemplateVersion(Guid templateId)
|
|
{
|
|
return _context.TemplateVersions
|
|
.AsNoTracking()
|
|
.Include(version => version.TemplateRevisions)
|
|
.FirstOrDefault(version => version.TemplateId == templateId && version.IsPublished);
|
|
}
|
|
|
|
public ICollection<TemplateRevisionModel> GetTemplateRevisions(Guid templateId, Guid versionId)
|
|
{
|
|
if (!_context.TemplateVersions.Any(version => version.TemplateId == templateId && version.Id == versionId))
|
|
{
|
|
return new List<TemplateRevisionModel>();
|
|
}
|
|
|
|
return _context.TemplateRevisions
|
|
.AsNoTracking()
|
|
.Include(revision => revision.ConfigurationDefinitions)
|
|
.Where(revision => revision.TemplateVersionId == versionId)
|
|
.OrderByDescending(revision => revision.RevisionNumber)
|
|
.ToList();
|
|
}
|
|
|
|
public TemplateRevisionModel? GetTemplateRevisionById(Guid templateId, Guid versionId, Guid revisionId)
|
|
{
|
|
if (!_context.TemplateVersions.Any(version => version.TemplateId == templateId && version.Id == versionId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return _context.TemplateRevisions
|
|
.AsNoTracking()
|
|
.Include(revision => revision.ConfigurationDefinitions)
|
|
.FirstOrDefault(revision => revision.TemplateVersionId == versionId && revision.Id == revisionId);
|
|
}
|
|
|
|
public TemplateRevisionModel? GetCurrentTemplateRevision(Guid versionId)
|
|
{
|
|
return _context.TemplateRevisions
|
|
.AsNoTracking()
|
|
.FirstOrDefault(revision => revision.TemplateVersionId == versionId && revision.IsCurrent);
|
|
}
|
|
|
|
public bool AddTemplateVersion(Guid templateId, TemplateVersionModel version, bool publish)
|
|
{
|
|
if (!_context.Templates.Any(template => template.Id == templateId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
version.Id = version.Id == Guid.Empty ? Guid.NewGuid() : version.Id;
|
|
version.TemplateId = templateId;
|
|
version.Version = string.IsNullOrWhiteSpace(version.Version) ? "1.0.0" : version.Version;
|
|
var document = ConfigurationDocumentValidator.NormalizeAndValidate(
|
|
version.JsonData,
|
|
ConfigurationDocumentKind.Template,
|
|
version.SchemaVersion);
|
|
version.SchemaVersion = document.SchemaVersion;
|
|
version.SetJsonData(document.JsonData);
|
|
version.TemplateRevisions.Add(CreateTemplateRevision(version.Id, document.JsonData, document.SchemaVersion, publish, version.PublishedBy));
|
|
|
|
if (publish)
|
|
{
|
|
foreach (var existingVersion in _context.TemplateVersions.Where(existing => existing.TemplateId == templateId && existing.IsPublished))
|
|
{
|
|
existingVersion.IsPublished = false;
|
|
}
|
|
|
|
version.IsPublished = true;
|
|
version.PublishedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
_context.TemplateVersions.Add(version);
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool AddTemplateRevision(Guid templateId, Guid versionId, TemplateRevisionModel revision, bool publish)
|
|
{
|
|
var version = _context.TemplateVersions
|
|
.Include(existing => existing.TemplateRevisions)
|
|
.FirstOrDefault(existing => existing.TemplateId == templateId && existing.Id == versionId);
|
|
|
|
if (version == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var document = ConfigurationDocumentValidator.NormalizeAndValidate(
|
|
revision.JsonData,
|
|
ConfigurationDocumentKind.Template,
|
|
revision.SchemaVersion);
|
|
|
|
revision = CreateTemplateRevision(versionId, document.JsonData, document.SchemaVersion, publish, revision.PublishedBy);
|
|
|
|
if (publish)
|
|
{
|
|
foreach (var existingRevision in _context.TemplateRevisions.Where(existing => existing.TemplateVersionId == versionId && existing.IsCurrent))
|
|
{
|
|
existingRevision.IsCurrent = false;
|
|
existingRevision.IsPublished = false;
|
|
}
|
|
|
|
version.SetJsonData(document.JsonData);
|
|
version.SchemaVersion = document.SchemaVersion;
|
|
version.PublishedAt = DateTime.UtcNow;
|
|
version.PublishedBy = revision.PublishedBy;
|
|
}
|
|
|
|
_context.TemplateRevisions.Add(revision);
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool PublishTemplateVersion(Guid templateId, Guid versionId, string? publishedBy)
|
|
{
|
|
var version = _context.TemplateVersions
|
|
.FirstOrDefault(existing => existing.TemplateId == templateId && existing.Id == versionId);
|
|
|
|
if (version == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var existingVersion in _context.TemplateVersions.Where(existing => existing.TemplateId == templateId && existing.IsPublished))
|
|
{
|
|
existingVersion.IsPublished = false;
|
|
}
|
|
|
|
version.IsPublished = true;
|
|
version.PublishedAt = DateTime.UtcNow;
|
|
version.PublishedBy = string.IsNullOrWhiteSpace(publishedBy) ? "System" : publishedBy;
|
|
var currentRevision = _context.TemplateRevisions.FirstOrDefault(revision => revision.TemplateVersionId == version.Id && revision.IsCurrent);
|
|
if (currentRevision != null)
|
|
{
|
|
currentRevision.IsPublished = true;
|
|
currentRevision.PublishedAt = DateTime.UtcNow;
|
|
currentRevision.PublishedBy = version.PublishedBy;
|
|
}
|
|
|
|
var template = _context.Templates.FirstOrDefault(existing => existing.Id == templateId);
|
|
if (template != null)
|
|
{
|
|
template.Version = version.Version;
|
|
template.JSONData = version.JsonData;
|
|
template.Modified = DateTime.UtcNow;
|
|
template.ModifiedBy = version.PublishedBy;
|
|
}
|
|
|
|
return SaveChanges();
|
|
}
|
|
|
|
public bool CheckTemplateVersionById(Guid templateId, Guid versionId)
|
|
{
|
|
return _context.TemplateVersions
|
|
.Any(version => version.TemplateId == templateId && version.Id == versionId);
|
|
}
|
|
|
|
public bool CheckTemplateVersionByVersion(Guid templateId, string version)
|
|
{
|
|
return _context.TemplateVersions
|
|
.Any(existing => existing.TemplateId == templateId && existing.Version == version);
|
|
}
|
|
|
|
public bool SaveChanges()
|
|
{
|
|
var saved = _context.SaveChanges();
|
|
return saved > 0 ? true : false;
|
|
}
|
|
|
|
private static void ApplyPublishedVersion(TemplateModel template)
|
|
{
|
|
var publishedVersion = template.TemplateVersions?.FirstOrDefault(version => version.IsPublished);
|
|
if (publishedVersion == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
template.Version = publishedVersion.Version;
|
|
var currentRevision = publishedVersion.TemplateRevisions?.FirstOrDefault(revision => revision.IsCurrent);
|
|
template.JSONData = currentRevision?.JsonData ?? publishedVersion.JsonData;
|
|
}
|
|
|
|
private TemplateRevisionModel CreateTemplateRevision(Guid templateVersionId, string jsonData, string schemaVersion, bool publish, string? publishedBy)
|
|
{
|
|
var revision = new TemplateRevisionModel
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TemplateVersionId = templateVersionId,
|
|
RevisionNumber = GetNextRevisionNumber(templateVersionId),
|
|
SchemaVersion = schemaVersion,
|
|
IsCurrent = true,
|
|
IsPublished = publish,
|
|
PublishedAt = publish ? DateTime.UtcNow : null,
|
|
PublishedBy = publishedBy ?? "System"
|
|
};
|
|
revision.SetJsonData(jsonData);
|
|
|
|
foreach (var existingRevision in _context.TemplateRevisions.Where(existing => existing.TemplateVersionId == templateVersionId && existing.IsCurrent))
|
|
{
|
|
existingRevision.IsCurrent = false;
|
|
}
|
|
|
|
foreach (var definition in ExtractConfigurationDefinitions(revision.Id, jsonData))
|
|
{
|
|
revision.ConfigurationDefinitions.Add(definition);
|
|
}
|
|
|
|
return revision;
|
|
}
|
|
|
|
private int GetNextRevisionNumber(Guid templateVersionId)
|
|
{
|
|
return (_context.TemplateRevisions
|
|
.Where(revision => revision.TemplateVersionId == templateVersionId)
|
|
.Select(revision => (int?)revision.RevisionNumber)
|
|
.Max() ?? 0) + 1;
|
|
}
|
|
|
|
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 = property.Value.GetRawText();
|
|
yield return new ConfigurationDefinitionModel
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TemplateRevisionId = revisionId,
|
|
Kind = kind,
|
|
Name = property.Name,
|
|
PropertiesJson = propertiesJson,
|
|
DefinitionHash = TemplateVersionModel.ComputeSha256(propertiesJson)
|
|
};
|
|
}
|
|
}
|
|
|
|
private string GetUniqueVersionName(Guid templateId, string version)
|
|
{
|
|
if (!CheckTemplateVersionByVersion(templateId, version))
|
|
{
|
|
return version;
|
|
}
|
|
|
|
var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
|
|
var candidate = $"{version}-{timestamp}";
|
|
var counter = 1;
|
|
|
|
while (CheckTemplateVersionByVersion(templateId, candidate))
|
|
{
|
|
counter++;
|
|
candidate = $"{version}-{timestamp}-{counter}";
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
}
|
|
}
|
|
|