Remove appsettings.json, rename CifsShareBasePath to CifsShareName, add CifsShareFolder to CmsInstances, and create a template.yml for Docker configuration.
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
This commit is contained in:
@@ -151,9 +151,145 @@ public class SshDockerCliService : IDockerCliService
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> EnsureDirectoryAsync(string path)
|
||||
{
|
||||
EnsureHost();
|
||||
var (exitCode, _, stderr) = await _ssh.RunCommandAsync(_currentHost!, $"mkdir -p {path}");
|
||||
if (exitCode != 0)
|
||||
_logger.LogWarning("Failed to create directory {Path} on {Host}: {Error}", path, _currentHost!.Label, stderr);
|
||||
else
|
||||
_logger.LogInformation("Ensured directory exists on {Host}: {Path}", _currentHost!.Label, path);
|
||||
return exitCode == 0;
|
||||
}
|
||||
|
||||
public async Task<bool> EnsureSmbFoldersAsync(
|
||||
string cifsServer,
|
||||
string cifsShareName,
|
||||
string cifsUsername,
|
||||
string cifsPassword,
|
||||
IEnumerable<string> folderNames,
|
||||
string? cifsShareFolder = null)
|
||||
{
|
||||
EnsureHost();
|
||||
var allSucceeded = true;
|
||||
var subFolder = (cifsShareFolder ?? string.Empty).Trim('/');
|
||||
|
||||
// If a subfolder is specified, ensure it exists first
|
||||
if (!string.IsNullOrEmpty(subFolder))
|
||||
{
|
||||
var mkdirCmd = $"smbclient //{cifsServer}/{cifsShareName} -U '{cifsUsername}%{cifsPassword}' -c 'mkdir {subFolder}' 2>&1";
|
||||
var (_, mkdirOut, _) = await _ssh.RunCommandAsync(_currentHost!, mkdirCmd);
|
||||
var mkdirOutput = mkdirOut ?? string.Empty;
|
||||
|
||||
var alreadyExists = mkdirOutput.Contains("NT_STATUS_OBJECT_NAME_COLLISION", StringComparison.OrdinalIgnoreCase)
|
||||
|| mkdirOutput.Contains("already exists", StringComparison.OrdinalIgnoreCase);
|
||||
var success = alreadyExists || !mkdirOutput.Contains("NT_STATUS_", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (success)
|
||||
_logger.LogInformation("SMB subfolder ensured: //{Server}/{Share}/{Folder}", cifsServer, cifsShareName, subFolder);
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Failed to create SMB subfolder //{Server}/{Share}/{Folder}: {Output}",
|
||||
cifsServer, cifsShareName, subFolder, mkdirOutput.Trim());
|
||||
allSucceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the target path prefix for volume folders
|
||||
var pathPrefix = string.IsNullOrEmpty(subFolder) ? string.Empty : $"{subFolder}/";
|
||||
|
||||
foreach (var folder in folderNames)
|
||||
{
|
||||
var targetFolder = $"{pathPrefix}{folder}";
|
||||
// Run smbclient on the remote Docker host to create the folder on the share.
|
||||
// NT_STATUS_OBJECT_NAME_COLLISION means it already exists — treat as success.
|
||||
var cmd = $"smbclient //{cifsServer}/{cifsShareName} -U '{cifsUsername}%{cifsPassword}' -c 'mkdir {targetFolder}' 2>&1";
|
||||
var (_, stdout, _) = await _ssh.RunCommandAsync(_currentHost!, cmd);
|
||||
var output = stdout ?? string.Empty;
|
||||
|
||||
var exists = output.Contains("NT_STATUS_OBJECT_NAME_COLLISION", StringComparison.OrdinalIgnoreCase)
|
||||
|| output.Contains("already exists", StringComparison.OrdinalIgnoreCase);
|
||||
var ok = exists || !output.Contains("NT_STATUS_", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (ok)
|
||||
_logger.LogInformation("SMB folder ensured: //{Server}/{Share}/{Folder}", cifsServer, cifsShareName, targetFolder);
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Failed to create SMB folder //{Server}/{Share}/{Folder}: {Output}",
|
||||
cifsServer, cifsShareName, targetFolder, output.Trim());
|
||||
allSucceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allSucceeded;
|
||||
}
|
||||
|
||||
private void EnsureHost()
|
||||
{
|
||||
if (_currentHost == null)
|
||||
throw new InvalidOperationException("No SSH host configured. Call SetHost() before using Docker commands.");
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveStackVolumesAsync(string stackName)
|
||||
{
|
||||
EnsureHost();
|
||||
|
||||
// ── 1. Remove the stack first so containers release the volumes ─────
|
||||
_logger.LogInformation("Removing stack {StackName} before volume cleanup", stackName);
|
||||
var (rmExit, _, rmErr) = await _ssh.RunCommandAsync(_currentHost!,
|
||||
$"docker stack rm {stackName} 2>&1 || true");
|
||||
if (rmExit != 0)
|
||||
_logger.LogWarning("Stack rm returned non-zero for {StackName}: {Err}", stackName, rmErr);
|
||||
|
||||
// Give Swarm a moment to tear down containers on all nodes
|
||||
await Task.Delay(5000);
|
||||
|
||||
// ── 2. Clean volumes on the local (manager) node ────────────────────
|
||||
var localCmd = $"docker volume ls --filter \"name={stackName}_\" -q | xargs -r docker volume rm 2>&1 || true";
|
||||
var (_, localOut, _) = await _ssh.RunCommandAsync(_currentHost!, localCmd);
|
||||
if (!string.IsNullOrEmpty(localOut?.Trim()))
|
||||
_logger.LogInformation("Volume cleanup (manager): {Output}", localOut!.Trim());
|
||||
|
||||
// ── 3. Clean volumes on ALL swarm nodes via a temporary global service ──
|
||||
// This deploys a short-lived container on every node that mounts the Docker
|
||||
// socket and removes matching volumes. This handles worker nodes that the
|
||||
// orchestrator has no direct SSH access to.
|
||||
var cleanupSvcName = $"vol-cleanup-{stackName}".Replace("_", "-");
|
||||
|
||||
// Remove leftover cleanup service from a previous run (if any)
|
||||
await _ssh.RunCommandAsync(_currentHost!,
|
||||
$"docker service rm {cleanupSvcName} 2>/dev/null || true");
|
||||
|
||||
var createCmd = string.Join(" ",
|
||||
"docker service create",
|
||||
"--detach",
|
||||
"--mode global",
|
||||
"--restart-condition none",
|
||||
$"--name {cleanupSvcName}",
|
||||
"--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock",
|
||||
"docker:cli",
|
||||
"sh", "-c",
|
||||
$"'docker volume ls -q --filter name={stackName}_ | xargs -r docker volume rm 2>&1; echo done'");
|
||||
|
||||
_logger.LogInformation("Deploying global volume-cleanup service on all swarm nodes for {StackName}", stackName);
|
||||
var (svcExit, svcOut, svcErr) = await _ssh.RunCommandAsync(_currentHost!, createCmd);
|
||||
|
||||
if (svcExit != 0)
|
||||
{
|
||||
_logger.LogWarning("Global volume cleanup service creation failed: {Err}", svcErr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wait for the cleanup tasks to finish on all nodes
|
||||
_logger.LogInformation("Waiting for volume cleanup tasks to complete on all nodes...");
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
|
||||
// Remove the cleanup service
|
||||
await _ssh.RunCommandAsync(_currentHost!,
|
||||
$"docker service rm {cleanupSvcName} 2>/dev/null || true");
|
||||
|
||||
_logger.LogInformation("Volume cleanup complete for stack {StackName}", stackName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user