feat(cli): deploy area --id <areaId> [--force] bulk-deploys an area subtree with deploy site's deadline and exit-code contract (Gitea #40)

This commit is contained in:
Joseph Doherty
2026-10-22 14:12:46 -04:00
parent f72c72c52f
commit 74d5fba2b0
5 changed files with 165 additions and 3 deletions
@@ -93,6 +93,7 @@ internal static class CommandHelpers
// Every alias of a deploy contract shares the same policy. The server bounds
// the operation; a shorter client deadline can report failure while it applies.
var requestTimeout = timeout ?? (command is MgmtDeployInstanceCommand or MgmtDeployArtifactsCommand or MgmtDeploySiteCommand
or MgmtDeployAreaCommand
? Timeout.InfiniteTimeSpan
: TimeSpan.FromSeconds(30));
var response = await client.SendCommandAsync(commandName, command, requestTimeout);
@@ -21,6 +21,7 @@ public static class DeployCommands
command.Add(BuildInstance(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildSite(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildArea(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildArtifacts(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildStatus(urlOption, formatOption, usernameOption, passwordOption));
@@ -84,6 +85,41 @@ public static class DeployCommands
return cmd;
}
/// <summary>
/// Builds <c>deploy area</c> — bulk-deploy every instance in one area subtree
/// (the area plus all of its descendant areas), Gitea #40.
///
/// <para>
/// <c>deploy site</c> narrowed to an area: same skip rules (Disabled always,
/// up-to-date unless <c>--force</c>), same not-all-or-nothing summary, same
/// server-owned deadline and the same exit-code contract.
/// </para>
/// </summary>
private static Command BuildArea(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var idOption = new Option<int>("--id") { Description = "Area ID (its whole subtree is deployed)", Required = true };
var forceOption = new Option<bool>("--force")
{
Description =
"Redeploy instances whose config is unchanged (default: skip them — a redeploy " +
"resets static overrides and native alarm state at the site)",
};
var cmd = new Command("area") { Description = "Deploy every deployable instance in an area and its descendant areas" };
cmd.Add(idOption);
cmd.Add(forceOption);
cmd.SetAction(async (ParseResult result) =>
{
var areaId = result.GetValue(idOption);
var force = result.GetValue(forceOption);
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new MgmtDeployAreaCommand(areaId, force),
timeout: BulkDeployTimeout,
onSuccess: json => HandleSiteDeploymentResponse(json, CommandHelpers.ResolveFormat(result, formatOption, CliConfig.Load())));
});
return cmd;
}
/// <summary>
/// The server owns the bounded overall bulk deadline; do not abandon it with a shorter client timer.
/// </summary>
@@ -91,8 +127,8 @@ public static class DeployCommands
/// <summary>
/// Renders a deployment summary and turns its <c>failureCount</c> into the exit code:
/// 0 when every site/instance succeeded, 1 when any failed. Shared by <c>deploy site</c>
/// and both artifact-deploy commands (<c>deploy artifacts</c>, <c>site deploy-artifacts</c>),
/// 0 when every site/instance succeeded, 1 when any failed. Shared by <c>deploy site</c>,
/// <c>deploy area</c> and both artifact-deploy commands (<c>deploy artifacts</c>, <c>site deploy-artifacts</c>),
/// whose summaries all carry <c>failureCount</c> — a per-site failure is a successful HTTP
/// call, so without this the process exited 0 and scripts could not detect it.
/// </summary>