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:
161
OTSSignsOrchestrator.Core/Services/GitTemplateService.cs
Normal file
161
OTSSignsOrchestrator.Core/Services/GitTemplateService.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using LibGit2Sharp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OTSSignsOrchestrator.Core.Configuration;
|
||||
using OTSSignsOrchestrator.Core.Models.DTOs;
|
||||
|
||||
namespace OTSSignsOrchestrator.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Fetches template.yml and template.env from a Git repository using LibGit2Sharp.
|
||||
/// Caches clones on disk, keyed by SHA-256 hash of the repo URL.
|
||||
/// </summary>
|
||||
public class GitTemplateService
|
||||
{
|
||||
private readonly GitOptions _options;
|
||||
private readonly ILogger<GitTemplateService> _logger;
|
||||
|
||||
public GitTemplateService(IOptions<GitOptions> options, ILogger<GitTemplateService> logger)
|
||||
{
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<TemplateConfig> FetchAsync(string repoUrl, string? pat = null, bool forceRefresh = false)
|
||||
{
|
||||
var cacheKey = ComputeCacheKey(repoUrl);
|
||||
var cacheDir = Path.Combine(_options.CacheDir, cacheKey);
|
||||
|
||||
_logger.LogInformation("Fetching templates from repo (cacheKey={CacheKey}, force={Force})", cacheKey, forceRefresh);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (!Directory.Exists(cacheDir) || !Repository.IsValid(cacheDir))
|
||||
{
|
||||
CloneRepo(repoUrl, pat, cacheDir);
|
||||
}
|
||||
else if (forceRefresh || IsCacheStale(cacheDir))
|
||||
{
|
||||
FetchLatest(repoUrl, pat, cacheDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Using cached clone at {CacheDir}", cacheDir);
|
||||
}
|
||||
});
|
||||
|
||||
var yamlPath = FindFile(cacheDir, "template.yml");
|
||||
var envPath = FindFile(cacheDir, "template.env");
|
||||
|
||||
if (yamlPath == null)
|
||||
throw new FileNotFoundException("template.yml not found in repository root.");
|
||||
if (envPath == null)
|
||||
throw new FileNotFoundException("template.env not found in repository root.");
|
||||
|
||||
var yaml = await File.ReadAllTextAsync(yamlPath);
|
||||
var envLines = (await File.ReadAllLinesAsync(envPath))
|
||||
.Where(l => !string.IsNullOrWhiteSpace(l) && !l.TrimStart().StartsWith('#'))
|
||||
.ToList();
|
||||
|
||||
return new TemplateConfig
|
||||
{
|
||||
Yaml = yaml,
|
||||
EnvLines = envLines,
|
||||
FetchedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private void CloneRepo(string repoUrl, string? pat, string cacheDir)
|
||||
{
|
||||
_logger.LogInformation("Shallow cloning repo to {CacheDir}", cacheDir);
|
||||
|
||||
if (Directory.Exists(cacheDir))
|
||||
Directory.Delete(cacheDir, recursive: true);
|
||||
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
|
||||
var cloneOpts = new CloneOptions { IsBare = false, RecurseSubmodules = false };
|
||||
|
||||
if (!string.IsNullOrEmpty(pat))
|
||||
{
|
||||
cloneOpts.FetchOptions.CredentialsProvider = (_, _, _) =>
|
||||
new UsernamePasswordCredentials { Username = pat, Password = string.Empty };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Repository.Clone(repoUrl, cacheDir, cloneOpts);
|
||||
}
|
||||
catch (LibGit2SharpException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Git clone failed for repo (cacheKey={CacheKey})", ComputeCacheKey(repoUrl));
|
||||
throw new InvalidOperationException($"Failed to clone repository. Check URL and credentials. Error: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void FetchLatest(string repoUrl, string? pat, string cacheDir)
|
||||
{
|
||||
_logger.LogInformation("Fetching latest from origin in {CacheDir}", cacheDir);
|
||||
|
||||
try
|
||||
{
|
||||
using var repo = new Repository(cacheDir);
|
||||
|
||||
var fetchOpts = new FetchOptions();
|
||||
if (!string.IsNullOrEmpty(pat))
|
||||
{
|
||||
fetchOpts.CredentialsProvider = (_, _, _) =>
|
||||
new UsernamePasswordCredentials { Username = pat, Password = string.Empty };
|
||||
}
|
||||
|
||||
var remote = repo.Network.Remotes["origin"];
|
||||
var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification).ToArray();
|
||||
Commands.Fetch(repo, "origin", refSpecs, fetchOpts, "Auto-fetch for template update");
|
||||
|
||||
var trackingBranch = repo.Head.TrackedBranch;
|
||||
if (trackingBranch != null)
|
||||
{
|
||||
repo.Reset(ResetMode.Hard, trackingBranch.Tip);
|
||||
}
|
||||
|
||||
WriteCacheTimestamp(cacheDir);
|
||||
}
|
||||
catch (LibGit2SharpException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Git fetch failed for repo at {CacheDir}", cacheDir);
|
||||
throw new InvalidOperationException($"Failed to fetch latest templates. Error: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCacheStale(string cacheDir)
|
||||
{
|
||||
var stampFile = Path.Combine(cacheDir, ".fetch-timestamp");
|
||||
if (!File.Exists(stampFile)) return true;
|
||||
|
||||
if (DateTime.TryParse(File.ReadAllText(stampFile), out var lastFetch))
|
||||
{
|
||||
return (DateTime.UtcNow - lastFetch).TotalMinutes > _options.CacheTtlMinutes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void WriteCacheTimestamp(string cacheDir)
|
||||
{
|
||||
var stampFile = Path.Combine(cacheDir, ".fetch-timestamp");
|
||||
File.WriteAllText(stampFile, DateTime.UtcNow.ToString("O"));
|
||||
}
|
||||
|
||||
private static string? FindFile(string dir, string fileName)
|
||||
{
|
||||
var path = Path.Combine(dir, fileName);
|
||||
return File.Exists(path) ? path : null;
|
||||
}
|
||||
|
||||
private static string ComputeCacheKey(string repoUrl)
|
||||
{
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(repoUrl));
|
||||
return Convert.ToHexString(hash)[..16].ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user