feat(deploy): pending-deployment repository with supersession + purge
This commit is contained in:
+38
@@ -136,6 +136,44 @@ public interface IDeploymentManagerRepository
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
|
||||
// Notify-and-fetch: PendingDeployment staging store
|
||||
/// <summary>
|
||||
/// Stages a flattened instance config for an in-flight deployment. Supersession:
|
||||
/// any prior pending row for the same <c>InstanceId</c> is removed first, so at most
|
||||
/// one pending row exists per instance (safe because the per-instance operation lock
|
||||
/// serializes same-instance deploys). Does NOT call <see cref="SaveChangesAsync"/> —
|
||||
/// the caller commits, mirroring the other Add/Delete repository methods.
|
||||
/// </summary>
|
||||
/// <param name="pending">The pending deployment to stage.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets a pending deployment by its deployment ID (the fetch key).
|
||||
/// </summary>
|
||||
/// <param name="deploymentId">The deployment ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The pending deployment, or null if not found.</returns>
|
||||
Task<PendingDeployment?> GetPendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes a pending deployment by its deployment ID. No-op if not found. Does NOT
|
||||
/// call <see cref="SaveChangesAsync"/> — the caller commits, mirroring the other
|
||||
/// Add/Delete repository methods.
|
||||
/// </summary>
|
||||
/// <param name="deploymentId">The deployment ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Purges all pending deployments whose <c>ExpiresAtUtc</c> is at or before
|
||||
/// <paramref name="nowUtc"/> (TTL maintenance). This is a self-contained maintenance
|
||||
/// operation: it commits its own delete and returns the number of rows removed.
|
||||
/// </summary>
|
||||
/// <param name="nowUtc">The current UTC time; rows with <c>ExpiresAtUtc <= nowUtc</c> are purged.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The number of pending deployments purged.</returns>
|
||||
Task<int> PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default);
|
||||
|
||||
// Instance lookups for deployment pipeline
|
||||
/// <summary>
|
||||
/// Gets an instance by ID.
|
||||
|
||||
+54
@@ -197,6 +197,60 @@ public class DeploymentManagerRepository : IDeploymentManagerRepository
|
||||
}
|
||||
}
|
||||
|
||||
// --- Notify-and-fetch: PendingDeployment staging store ---
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pending);
|
||||
|
||||
// Supersession: a newer deploy for the same instance replaces any prior pending
|
||||
// row. Safe because the per-instance operation lock serializes same-instance
|
||||
// deploys. SaveChanges is deferred to the caller, matching the other Add methods.
|
||||
var prior = await _dbContext.Set<PendingDeployment>()
|
||||
.Where(p => p.InstanceId == pending.InstanceId)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (prior.Count > 0)
|
||||
{
|
||||
_dbContext.Set<PendingDeployment>().RemoveRange(prior);
|
||||
}
|
||||
await _dbContext.Set<PendingDeployment>().AddAsync(pending, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PendingDeployment?> GetPendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _dbContext.Set<PendingDeployment>()
|
||||
.FirstOrDefaultAsync(p => p.DeploymentId == deploymentId, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var row = await _dbContext.Set<PendingDeployment>()
|
||||
.FirstOrDefaultAsync(p => p.DeploymentId == deploymentId, cancellationToken);
|
||||
if (row != null)
|
||||
{
|
||||
_dbContext.Set<PendingDeployment>().Remove(row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Self-contained TTL maintenance op: commits its own delete and returns the count.
|
||||
var expired = await _dbContext.Set<PendingDeployment>()
|
||||
.Where(p => p.ExpiresAtUtc <= nowUtc)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (expired.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_dbContext.Set<PendingDeployment>().RemoveRange(expired);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
return expired.Count;
|
||||
}
|
||||
|
||||
// --- Instance lookups for deployment pipeline ---
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user