-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSftpUploader.cs
More file actions
154 lines (132 loc) · 5.38 KB
/
Copy pathSftpUploader.cs
File metadata and controls
154 lines (132 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using Renci.SshNet;
namespace SCAScanner;
/// <summary>
/// Handles SFTP upload of generated report files to a remote server.
/// Supports both password and SSH key-based authentication.
/// </summary>
public class SftpUploader
{
/// <summary>
/// Uploads a list of local files to an SFTP server.
/// </summary>
/// <param name="config">SFTP configuration (host, credentials, remote path)</param>
/// <param name="filePaths">List of local file paths to upload</param>
/// <param name="reporter">Optional reporter for logging upload progress</param>
/// <exception cref="InvalidOperationException">If connection fails, authentication fails, or any file fails to upload</exception>
public async Task UploadFilesAsync(SftpConfig config, List<string> filePaths, IReporter reporter)
{
if (!config.Enabled)
{
return;
}
config.Validate();
reporter?.PrintInfo($"Connecting to SFTP server: {config.Host}:{config.Port}...");
ConnectionInfo connectionInfo;
try
{
connectionInfo = CreateConnectionInfo(config);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to configure SFTP connection: {ex.Message}", ex);
}
using (var client = new SftpClient(connectionInfo))
{
try
{
client.Connect();
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to connect to SFTP server {config.Host}:{config.Port}: {ex.Message}", ex);
}
reporter?.PrintInfo($"Successfully connected to SFTP server. Remote path: {config.RemotePath}");
// Create remote directory if it doesn't exist
try
{
if (!client.Exists(config.RemotePath))
{
client.CreateDirectory(config.RemotePath);
reporter?.PrintInfo($"Created remote directory: {config.RemotePath}");
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to access remote path '{config.RemotePath}': {ex.Message}", ex);
}
int successCount = 0;
int failureCount = 0;
var failures = new List<string>();
// Upload each file
foreach (var filePath in filePaths)
{
if (!File.Exists(filePath))
{
reporter?.PrintWarning($"Local file not found, skipping: {filePath}");
failureCount++;
failures.Add($"{filePath} (file not found)");
continue;
}
try
{
string fileName = Path.GetFileName(filePath);
string remotePath = $"{config.RemotePath.TrimEnd('/')}/{fileName}";
using (var fileStream = File.OpenRead(filePath))
{
client.UploadFile(fileStream, remotePath, true);
}
reporter?.PrintInfo($"Uploaded: {fileName}");
successCount++;
}
catch (Exception ex)
{
reporter?.PrintWarning($"Failed to upload {filePath}: {ex.Message}");
failureCount++;
failures.Add($"{filePath} ({ex.Message})");
}
}
client.Disconnect();
// If any files failed to upload, throw an exception (all-or-nothing semantic)
if (failureCount > 0)
{
string failureDetails = string.Join(", ", failures);
throw new InvalidOperationException($"SFTP upload completed with errors. Uploaded: {successCount}, Failed: {failureCount}. Details: {failureDetails}");
}
reporter?.PrintInfo($"SFTP upload complete. {successCount} file(s) uploaded successfully.");
}
}
/// <summary>
/// Creates a ConnectionInfo object based on config (password or key-based auth).
/// </summary>
private ConnectionInfo CreateConnectionInfo(SftpConfig config)
{
var authMethods = new List<AuthenticationMethod>();
// Try password auth first
if (!string.IsNullOrEmpty(config.Password))
{
authMethods.Add(new PasswordAuthenticationMethod(config.User, config.Password));
}
// Try key-based auth
if (!string.IsNullOrEmpty(config.KeyPath))
{
if (!File.Exists(config.KeyPath))
{
throw new InvalidOperationException($"SSH private key file not found: {config.KeyPath}");
}
try
{
var keyFile = new PrivateKeyFile(config.KeyPath);
authMethods.Add(new PrivateKeyAuthenticationMethod(config.User, keyFile));
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to load SSH private key: {ex.Message}", ex);
}
}
if (authMethods.Count == 0)
{
throw new InvalidOperationException("No authentication method configured (neither password nor key)");
}
return new ConnectionInfo(config.Host, config.Port, config.User, [.. authMethods]);
}
}