Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
- Implemented CreateInstanceView for creating new instances. - Added HostsView for managing SSH hosts with CRUD operations. - Created InstancesView for displaying and managing instances. - Developed LogsView for viewing operation logs. - Introduced SecretsView for managing secrets associated with hosts. - Established SettingsView for configuring application settings. - Created MainWindow as the main application window with navigation. - Added app manifest and configuration files for logging and settings.
117 lines
4.2 KiB
C#
117 lines
4.2 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using YamlDotNet.RepresentationModel;
|
|
|
|
namespace OTSSignsOrchestrator.Core.Services;
|
|
|
|
/// <summary>
|
|
/// Validates a rendered Compose YAML before deployment.
|
|
/// </summary>
|
|
public class ComposeValidationService
|
|
{
|
|
private readonly ILogger<ComposeValidationService> _logger;
|
|
|
|
public ComposeValidationService(ILogger<ComposeValidationService> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public ValidationResult Validate(string composeYaml, string? customerAbbrev = null)
|
|
{
|
|
var errors = new List<string>();
|
|
var warnings = new List<string>();
|
|
|
|
YamlStream yamlStream;
|
|
try
|
|
{
|
|
yamlStream = new YamlStream();
|
|
using var reader = new StringReader(composeYaml);
|
|
yamlStream.Load(reader);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors.Add($"YAML parse error: {ex.Message}");
|
|
return new ValidationResult { Errors = errors, Warnings = warnings };
|
|
}
|
|
|
|
if (yamlStream.Documents.Count == 0)
|
|
{
|
|
errors.Add("YAML document is empty.");
|
|
return new ValidationResult { Errors = errors, Warnings = warnings };
|
|
}
|
|
|
|
var root = yamlStream.Documents[0].RootNode as YamlMappingNode;
|
|
if (root == null)
|
|
{
|
|
errors.Add("YAML root is not a mapping node.");
|
|
return new ValidationResult { Errors = errors, Warnings = warnings };
|
|
}
|
|
|
|
if (!HasKey(root, "services"))
|
|
errors.Add("Missing required top-level key: 'services'.");
|
|
if (!HasKey(root, "secrets"))
|
|
warnings.Add("Missing top-level key: 'secrets'. Secrets may not be available.");
|
|
|
|
if (HasKey(root, "services") && root.Children[new YamlScalarNode("services")] is YamlMappingNode services)
|
|
{
|
|
var presentServices = services.Children.Keys
|
|
.OfType<YamlScalarNode>()
|
|
.Select(k => k.Value!)
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
// Determine required service suffixes
|
|
var requiredSuffixes = new[] { "-web", "-memcached", "-quickchart" };
|
|
var prefix = customerAbbrev ?? string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(prefix))
|
|
{
|
|
foreach (var suffix in requiredSuffixes)
|
|
{
|
|
if (!presentServices.Contains($"{prefix}{suffix}"))
|
|
errors.Add($"Missing required service: '{prefix}{suffix}'.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fallback: at least check that there are web/memcached/quickchart services
|
|
if (!presentServices.Any(s => s.EndsWith("-web", StringComparison.OrdinalIgnoreCase)))
|
|
errors.Add("Missing a '-web' service.");
|
|
if (!presentServices.Any(s => s.EndsWith("-memcached", StringComparison.OrdinalIgnoreCase)))
|
|
errors.Add("Missing a '-memcached' service.");
|
|
}
|
|
|
|
foreach (var (key, value) in services.Children)
|
|
{
|
|
if (key is YamlScalarNode keyNode && value is YamlMappingNode svcNode)
|
|
{
|
|
if (!HasKey(svcNode, "image"))
|
|
errors.Add($"Service '{keyNode.Value}' is missing 'image'.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HasKey(root, "secrets") && root.Children[new YamlScalarNode("secrets")] is YamlMappingNode secretsNode)
|
|
{
|
|
foreach (var (key, value) in secretsNode.Children)
|
|
{
|
|
if (value is YamlMappingNode secretNode)
|
|
{
|
|
if (!HasKey(secretNode, "external"))
|
|
warnings.Add($"Secret '{(key as YamlScalarNode)?.Value}' is not external.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return new ValidationResult { Errors = errors, Warnings = warnings };
|
|
}
|
|
|
|
private static bool HasKey(YamlMappingNode node, string key)
|
|
=> node.Children.ContainsKey(new YamlScalarNode(key));
|
|
}
|
|
|
|
public class ValidationResult
|
|
{
|
|
public List<string> Errors { get; set; } = new();
|
|
public List<string> Warnings { get; set; } = new();
|
|
public bool IsValid => Errors.Count == 0;
|
|
}
|