feat: Add main application views and structure
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
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.
This commit is contained in:
116
OTSSignsOrchestrator.Core/Services/ComposeValidationService.cs
Normal file
116
OTSSignsOrchestrator.Core/Services/ComposeValidationService.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user