diff options
author | crupest <crupest@outlook.com> | 2022-11-24 16:33:17 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-11-24 16:33:17 +0800 |
commit | d0e7d02642bd28aa737fb37330235192efcda899 (patch) | |
tree | 1349e372382bcf3e85060fb40b2e7636ca9ebaeb | |
parent | 450ee4d5e417cf8d23f87cad81d997221c1c5d44 (diff) | |
download | crupest-d0e7d02642bd28aa737fb37330235192efcda899.tar.gz crupest-d0e7d02642bd28aa737fb37330235192efcda899.tar.bz2 crupest-d0e7d02642bd28aa737fb37330235192efcda899.zip |
No COS sdk.
-rw-r--r-- | docker/auto-backup/AutoBackup/AutoBackup.csproj | 4 | ||||
-rw-r--r-- | docker/auto-backup/AutoBackup/Program.cs | 49 | ||||
-rw-r--r-- | docker/auto-backup/AutoBackup/TencentCloudCOS.cs | 210 |
3 files changed, 227 insertions, 36 deletions
diff --git a/docker/auto-backup/AutoBackup/AutoBackup.csproj b/docker/auto-backup/AutoBackup/AutoBackup.csproj index 24000b2..d439800 100644 --- a/docker/auto-backup/AutoBackup/AutoBackup.csproj +++ b/docker/auto-backup/AutoBackup/AutoBackup.csproj @@ -7,8 +7,4 @@ <Nullable>enable</Nullable>
</PropertyGroup>
- <ItemGroup> - <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.32" /> - </ItemGroup>
-
</Project>
diff --git a/docker/auto-backup/AutoBackup/Program.cs b/docker/auto-backup/AutoBackup/Program.cs index 96e1b3e..cfa5c5b 100644 --- a/docker/auto-backup/AutoBackup/Program.cs +++ b/docker/auto-backup/AutoBackup/Program.cs @@ -1,8 +1,4 @@ -using COSXML;
-using COSXML.Auth;
-using COSXML.Transfer;
-
-// Check I'm root
+// Check I'm root
if (Environment.UserName != "root")
{
Console.WriteLine("You must run this program as root");
@@ -67,6 +63,13 @@ if (File.GetAttributes(uploadFilePath).HasFlag(FileAttributes.Directory)) Environment.Exit(4);
}
+// Check the upload file is not bigger than 5G
+if (new FileInfo(uploadFilePath).Length > 5L * 1024L * 1024L * 1024L)
+{
+ Console.Error.WriteLine($"The file {uploadFilePath} is bigger than 5G, which is not support now.");
+ Environment.Exit(5);
+}
+
// Get config from environment variables
var configNameList = new List<string>{
"CRUPEST_AUTO_BACKUP_COS_SECRET_ID",
@@ -87,23 +90,12 @@ foreach (var configName in configNameList) config.Add(configName, configValue);
}
-var cosConfig = new CosXmlConfig.Builder()
- .IsHttps(true)
- .SetRegion(config["CRUPEST_AUTO_BACKUP_COS_REGION"])
- .Build();
-
-QCloudCredentialProvider cosCredentialProvider =
- new DefaultQCloudCredentialProvider(
- config["CRUPEST_AUTO_BACKUP_COS_SECRET_ID"],
- config["CRUPEST_AUTO_BACKUP_COS_SECRET_KEY"],
- 60
- );
-
-CosXml cosXml = new CosXmlServer(cosConfig, cosCredentialProvider);
+var region = config["CRUPEST_AUTO_BACKUP_COS_REGION"];
+var secretId = config["CRUPEST_AUTO_BACKUP_COS_SECRET_ID"];
+var secretKey = config["CRUPEST_AUTO_BACKUP_COS_SECRET_KEY"];
+var bucketName = config["CRUPEST_AUTO_BACKUP_BUCKET_NAME"];
-TransferConfig transferConfig = new TransferConfig();
-
-TransferManager transferManager = new TransferManager(cosXml, transferConfig);
+var credentials = new TencentCloudCOSHelper.Credentials(secretId, secretKey);
if (uploadDestinationPath is null)
{
@@ -121,23 +113,16 @@ Console.WriteLine($"Upload COS region: {config["CRUPEST_AUTO_BACKUP_COS_REGION"] Console.WriteLine($"Upload bucket name: {config["CRUPEST_AUTO_BACKUP_BUCKET_NAME"]}");
Console.WriteLine($"Upload file destination: {uploadDestinationPath}");
-// 上传对象
-COSXMLUploadTask uploadTask = new COSXMLUploadTask(config["CRUPEST_AUTO_BACKUP_BUCKET_NAME"], uploadDestinationPath);
-uploadTask.SetSrcPath(uploadFilePath);
-
-uploadTask.progressCallback = delegate (long completed, long total)
-{
- Console.WriteLine(String.Format("progress = {0:##.##}%", completed * 100.0 / total));
-};
+using var fileStream = File.OpenRead(uploadFilePath);
+// 上传对象
try
{
- COSXMLUploadTask.UploadTaskResult result = await transferManager.UploadAsync(uploadTask);
- Console.WriteLine(result.GetResultInfo());
+ await TencentCloudCOSHelper.PutObject(credentials, region, bucketName, uploadDestinationPath, fileStream);
Console.WriteLine("Upload completed!");
}
catch (Exception e)
{
- Console.Error.WriteLine("CosException: " + e);
+ Console.Error.WriteLine("Exception: " + e);
Environment.Exit(6);
}
diff --git a/docker/auto-backup/AutoBackup/TencentCloudCOS.cs b/docker/auto-backup/AutoBackup/TencentCloudCOS.cs new file mode 100644 index 0000000..c3d245c --- /dev/null +++ b/docker/auto-backup/AutoBackup/TencentCloudCOS.cs @@ -0,0 +1,210 @@ +using System.Net; +using System.Security.Cryptography; +using System.Text; + + +public static class TencentCloudCOSHelper +{ + public class Credentials + { + public Credentials(string secretId, string secretKey) + { + SecretId = secretId; + SecretKey = secretKey; + } + + public string SecretId { get; } + public string SecretKey { get; } + } + + public class RequestInfo + { + public RequestInfo(string method, string uri, IEnumerable<KeyValuePair<string, string>> parameters, IEnumerable<KeyValuePair<string, string>> headers) + { + Method = method; + Uri = uri; + Parameters = new Dictionary<string, string>(parameters); + Headers = new Dictionary<string, string>(headers); + } + + public string Method { get; } + public string Uri { get; } + public IReadOnlyDictionary<string, string> Parameters { get; } + public IReadOnlyDictionary<string, string> Headers { get; } + } + + public class TimeDuration + { + public TimeDuration(DateTimeOffset start, DateTimeOffset end) + { + if (start > end) + { + throw new ArgumentException("Start time must be earlier than end time."); + } + + Start = start; + End = end; + } + + public DateTimeOffset Start { get; } + public DateTimeOffset End { get; } + } + + public static string GenerateSign(Credentials credentials, RequestInfo request, TimeDuration signValidTime) + { + List<(string key, string value)> Transform(IEnumerable<KeyValuePair<string, string>> raw) + { + if (raw == null) + return new List<(string key, string value)>(); + + var sorted = raw.Select(p => (key: p.Key.ToLower(), value: WebUtility.UrlEncode(p.Value))).ToList(); + sorted.Sort((left, right) => string.CompareOrdinal(left.key, right.key)); + return sorted; + } + + var transformedParameters = Transform(request.Parameters); + var transformedHeaders = Transform(request.Headers); + + List<(string, string)> result = new List<(string, string)>(); + + const string signAlgorithm = "sha1"; + result.Add(("q-sign-algorithm", signAlgorithm)); + + result.Add(("q-ak", credentials.SecretId)); + + var signTime = $"{signValidTime.Start.ToUnixTimeSeconds().ToString()};{signValidTime.End.ToUnixTimeSeconds().ToString()}"; + var keyTime = signTime; + result.Add(("q-sign-time", signTime)); + result.Add(("q-key-time", keyTime)); + + result.Add(("q-header-list", string.Join(';', transformedHeaders.Select(h => h.key)))); + result.Add(("q-url-param-list", string.Join(';', transformedParameters.Select(p => p.key)))); + + using HMACSHA1 hmac = new HMACSHA1(); + + string ByteArrayToString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", "").ToLower(); + } + + hmac.Key = Encoding.UTF8.GetBytes(credentials.SecretKey); + var signKey = ByteArrayToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(keyTime))); + + string Join(IEnumerable<(string key, string value)> raw) + { + return string.Join('&', raw.Select(p => string.Concat(p.key, "=", p.value))); + } + + var httpString = new StringBuilder() + .Append(request.Method.ToLower()).Append('\n') + .Append(request.Uri).Append('\n') + .Append(Join(transformedParameters)).Append('\n') + .Append(Join(transformedHeaders)).Append('\n') + .ToString(); + + string Sha1(string data) + { + using var sha1 = SHA1.Create(); + var result = sha1.ComputeHash(Encoding.UTF8.GetBytes(data)); + return ByteArrayToString(result); + } + + var stringToSign = new StringBuilder() + .Append(signAlgorithm).Append('\n') + .Append(signTime).Append('\n') + .Append(Sha1(httpString)).Append('\n') + .ToString(); + + hmac.Key = Encoding.UTF8.GetBytes(signKey); + var signature = ByteArrayToString(hmac.ComputeHash( + Encoding.UTF8.GetBytes(stringToSign))); + + result.Add(("q-signature", signature)); + + return Join(result); + } + + private static string GetHost(string bucket, string region) + { + return $"{bucket}.cos.{region}.myqcloud.com"; + } + + public static async Task<bool> IsObjectExists(Credentials credentials, string region, string bucket, string key) + { + var host = GetHost(bucket, region); + var encodedKey = WebUtility.UrlEncode(key); + + using var request = new HttpRequestMessage(); + request.Method = HttpMethod.Head; + request.RequestUri = new Uri($"https://{host}/{encodedKey}"); + request.Headers.Host = host; + request.Headers.Date = DateTimeOffset.Now; + request.Headers.TryAddWithoutValidation("Authorization", GenerateSign(credentials, new RequestInfo( + "head", "/" + encodedKey, new Dictionary<string, string>(), + new Dictionary<string, string> + { + ["Host"] = host + } + ), new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(5)))); + + using var client = new HttpClient(); + using var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + return true; + if (response.StatusCode == HttpStatusCode.NotFound) + return false; + + throw new Exception($"Unknown response code. {response.ToString()}"); + } + + public static async Task PutObject(Credentials credentials, string region, string bucket, string key, Stream dataStream) + { + if (dataStream.CanSeek) + { + throw new ArgumentException("Data stream must be seekable."); + } + + if (dataStream.Seek(0, SeekOrigin.End) > 5L * 1024L * 1024L * 1024L) + { + throw new ArgumentException("Data stream must be smaller than 5GB."); + } + + var host = GetHost(bucket, region); + var encodedKey = WebUtility.UrlEncode(key); + using var md5Handler = MD5.Create(); + var md5 = Convert.ToBase64String(await md5Handler.ComputeHashAsync(dataStream)); + + dataStream.Seek(0, SeekOrigin.Begin); + + const string kContentMD5HeaderName = "Content-MD5"; + + using var httpRequest = new HttpRequestMessage() + { + Method = HttpMethod.Put, + RequestUri = new Uri($"https://{host}/{encodedKey}") + }; + httpRequest.Headers.Host = host; + httpRequest.Headers.Date = DateTimeOffset.Now; + + using var httpContent = new StreamContent(dataStream); + httpContent.Headers.Add(kContentMD5HeaderName, md5); + httpRequest.Content = httpContent; + + var signedHeaders = new Dictionary<string, string> + { + ["Host"] = host, + [kContentMD5HeaderName] = md5 + }; + + httpRequest.Headers.TryAddWithoutValidation("Authorization", GenerateSign(credentials, new RequestInfo( + "put", "/" + encodedKey, new Dictionary<string, string>(), signedHeaders + ), new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(10)))); + + using var client = new HttpClient(); + using var response = await client.SendAsync(httpRequest); + + if (!response.IsSuccessStatusCode) + throw new Exception($"Not success status code. {response.ToString()}"); + } +}
\ No newline at end of file |