Mercurial > hg > orthanc-object-storage
annotate Aws/AwsS3StoragePlugin.cpp @ 37:f55b2afdf53d
upgrade to year 2021
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 06 Jan 2021 18:27:54 +0100 |
parents | 70da4ce5c7cc |
children | d99afdf6d872 |
rev | line source |
---|---|
1 | 1 /** |
2 * Cloud storage plugins for Orthanc | |
37
f55b2afdf53d
upgrade to year 2021
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
33
diff
changeset
|
3 * Copyright (C) 2020-2021 Osimis S.A., Belgium |
1 | 4 * |
5 * This program is free software: you can redistribute it and/or | |
6 * modify it under the terms of the GNU Affero General Public License | |
7 * as published by the Free Software Foundation, either version 3 of | |
8 * the License, or (at your option) any later version. | |
9 * | |
10 * This program is distributed in the hope that it will be useful, but | |
11 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 * Affero General Public License for more details. | |
14 * | |
15 * You should have received a copy of the GNU Affero General Public License | |
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 **/ | |
18 | |
19 #include "AwsS3StoragePlugin.h" | |
20 | |
21 #include <aws/core/Aws.h> | |
22 #include <aws/s3/S3Client.h> | |
23 #include <aws/s3/model/PutObjectRequest.h> | |
24 #include <aws/s3/model/GetObjectRequest.h> | |
25 #include <aws/s3/model/ListObjectsRequest.h> | |
26 #include <aws/s3/model/DeleteObjectRequest.h> | |
27 #include <aws/core/auth/AWSCredentialsProvider.h> | |
28 #include <aws/core/utils/memory/stl/AWSStringStream.h> | |
29 | |
30 #include <boost/lexical_cast.hpp> | |
31 #include <iostream> | |
32 #include <fstream> | |
33 | |
34 const char* ALLOCATION_TAG = "OrthancS3"; | |
15 | 35 static const char* const PLUGIN_SECTION = "AwsS3Storage"; |
1 | 36 |
15 | 37 class AwsS3StoragePlugin : public BaseStoragePlugin |
1 | 38 { |
39 public: | |
40 | |
41 Aws::S3::S3Client client_; | |
42 std::string bucketName_; | |
43 | |
44 public: | |
45 | |
15 | 46 AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName, bool enableLegacyStorageStructure); |
1 | 47 |
48 virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled); | |
49 virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled); | |
50 virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled); | |
15 | 51 virtual const char* GetConfigurationSectionName() {return PLUGIN_SECTION;}; |
1 | 52 }; |
53 | |
54 | |
55 class Writer : public IStoragePlugin::IWriter | |
56 { | |
57 std::string path_; | |
58 Aws::S3::S3Client client_; | |
59 std::string bucketName_; | |
60 | |
61 public: | |
62 Writer(const Aws::S3::S3Client& client, const std::string& bucketName, const std::string& path) | |
63 : path_(path), | |
64 client_(client), | |
65 bucketName_(bucketName) | |
66 { | |
67 } | |
68 | |
69 virtual ~Writer() | |
70 { | |
71 } | |
72 | |
73 virtual void Write(const char* data, size_t size) | |
74 { | |
75 Aws::S3::Model::PutObjectRequest putObjectRequest; | |
76 | |
77 putObjectRequest.SetBucket(bucketName_.c_str()); | |
78 putObjectRequest.SetKey(path_.c_str()); | |
79 | |
80 std::shared_ptr<Aws::StringStream> stream = Aws::MakeShared<Aws::StringStream>(ALLOCATION_TAG, std::ios_base::in | std::ios_base::binary); | |
81 | |
82 stream->rdbuf()->pubsetbuf(const_cast<char*>(data), size); | |
83 stream->rdbuf()->pubseekpos(size); | |
84 stream->seekg(0); | |
85 | |
86 putObjectRequest.SetBody(stream); | |
87 | |
88 auto result = client_.PutObject(putObjectRequest); | |
89 | |
90 if (!result.IsSuccess()) | |
91 { | |
92 throw StoragePluginException(std::string("error while writing file ") + path_ + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str()); | |
93 } | |
94 } | |
95 }; | |
96 | |
97 | |
98 class Reader : public IStoragePlugin::IReader | |
99 { | |
100 std::string path_; | |
101 Aws::S3::S3Client client_; | |
102 std::string bucketName_; | |
103 | |
104 public: | |
105 Reader(const Aws::S3::S3Client& client, const std::string& bucketName, const std::string& path) | |
106 : path_(path), | |
107 client_(client), | |
108 bucketName_(bucketName) | |
109 { | |
110 } | |
111 | |
112 virtual ~Reader() | |
113 { | |
114 | |
115 } | |
116 virtual size_t GetSize() | |
117 { | |
118 Aws::S3::Model::ListObjectsRequest listObjectRequest; | |
119 listObjectRequest.SetBucket(bucketName_.c_str()); | |
120 listObjectRequest.SetPrefix(path_.c_str()); | |
121 | |
122 auto result = client_.ListObjects(listObjectRequest); | |
123 | |
124 if (result.IsSuccess()) | |
125 { | |
126 Aws::Vector<Aws::S3::Model::Object> objectList = | |
127 result.GetResult().GetContents(); | |
128 | |
129 if (objectList.size() == 1) | |
130 { | |
131 return objectList[0].GetSize(); | |
132 } | |
32 | 133 else if (objectList.size() > 1) |
134 { | |
135 throw StoragePluginException(std::string("error while reading file ") + path_ + ": multiple objet with same name !"); | |
136 } | |
137 throw StoragePluginException(std::string("error while reading file ") + path_ + ": object not found !"); | |
1 | 138 } |
139 else | |
140 { | |
141 throw StoragePluginException(std::string("error while reading file ") + path_ + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str()); | |
142 } | |
143 } | |
144 virtual void Read(char* data, size_t size) | |
145 { | |
146 Aws::S3::Model::GetObjectRequest getObjectRequest; | |
147 getObjectRequest.SetBucket(bucketName_.c_str()); | |
148 getObjectRequest.SetKey(path_.c_str()); | |
149 | |
150 getObjectRequest.SetResponseStreamFactory( | |
151 [data, size]() | |
152 { | |
153 std::unique_ptr<Aws::StringStream> | |
154 istream(Aws::New<Aws::StringStream>(ALLOCATION_TAG)); | |
155 | |
156 istream->rdbuf()->pubsetbuf(static_cast<char*>(data), | |
157 size); | |
158 | |
159 return istream.release(); | |
160 }); | |
161 | |
162 // Get the object | |
163 auto result = client_.GetObject(getObjectRequest); | |
164 if (result.IsSuccess()) | |
165 { | |
166 } | |
167 else | |
168 { | |
169 throw StoragePluginException(std::string("error while reading file ") + path_ + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str()); | |
170 } | |
171 } | |
172 | |
173 }; | |
174 | |
175 | |
176 | |
177 const char* AwsS3StoragePluginFactory::GetStoragePluginName() | |
178 { | |
179 return "AWS S3 Storage"; | |
180 } | |
181 | |
182 IStoragePlugin* AwsS3StoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig) | |
183 { | |
15 | 184 bool enableLegacyStorageStructure; |
185 | |
1 | 186 if (!orthancConfig.IsSection(PLUGIN_SECTION)) |
187 { | |
188 OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing. Plugin is not enabled."); | |
189 return nullptr; | |
190 } | |
191 | |
192 OrthancPlugins::OrthancConfiguration pluginSection; | |
193 orthancConfig.GetSection(pluginSection, PLUGIN_SECTION); | |
194 | |
15 | 195 if (!BaseStoragePlugin::ReadCommonConfiguration(enableLegacyStorageStructure, pluginSection)) |
196 { | |
197 return nullptr; | |
198 } | |
199 | |
1 | 200 std::string bucketName; |
201 std::string region; | |
202 std::string accessKey; | |
203 std::string secretKey; | |
204 | |
205 if (!pluginSection.LookupStringValue(bucketName, "BucketName")) | |
206 { | |
207 OrthancPlugins::LogError("AwsS3Storage/BucketName configuration missing. Unable to initialize plugin"); | |
208 return nullptr; | |
209 } | |
210 | |
211 if (!pluginSection.LookupStringValue(region, "Region")) | |
212 { | |
213 OrthancPlugins::LogError("AwsS3Storage/Region configuration missing. Unable to initialize plugin"); | |
214 return nullptr; | |
215 } | |
216 | |
217 if (!pluginSection.LookupStringValue(accessKey, "AccessKey")) | |
218 { | |
219 OrthancPlugins::LogError("AwsS3Storage/AccessKey configuration missing. Unable to initialize plugin"); | |
220 return nullptr; | |
221 } | |
222 | |
223 if (!pluginSection.LookupStringValue(secretKey, "SecretKey")) | |
224 { | |
225 OrthancPlugins::LogError("AwsS3Storage/SecretKey configuration missing. Unable to initialize plugin"); | |
226 return nullptr; | |
227 } | |
228 | |
6
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
229 std::string endpoint = pluginSection.GetStringValue("Endpoint", ""); |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
230 unsigned int connectTimeout = pluginSection.GetUnsignedIntegerValue("ConnectTimeout", 30); |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
231 unsigned int requestTimeout = pluginSection.GetUnsignedIntegerValue("RequestTimeout", 1200); |
27
e1f52b851827
Added "VirtualAddressing" configuration option in the AWS S3 plugin (for compatibility with minio)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
15
diff
changeset
|
232 bool virtualAddressing = pluginSection.GetBooleanValue("VirtualAddressing", true); |
e1f52b851827
Added "VirtualAddressing" configuration option in the AWS S3 plugin (for compatibility with minio)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
15
diff
changeset
|
233 |
1 | 234 try |
235 { | |
236 Aws::SDKOptions options; | |
237 Aws::InitAPI(options); | |
238 | |
239 Aws::Auth::AWSCredentials credentials(accessKey.c_str(), secretKey.c_str()); | |
240 Aws::Client::ClientConfiguration configuration; | |
241 configuration.region = region.c_str(); | |
6
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
242 configuration.scheme = Aws::Http::Scheme::HTTPS; |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
243 configuration.connectTimeoutMs = connectTimeout * 1000; |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
244 configuration.requestTimeoutMs = requestTimeout * 1000; |
27
e1f52b851827
Added "VirtualAddressing" configuration option in the AWS S3 plugin (for compatibility with minio)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
15
diff
changeset
|
245 configuration.httpRequestTimeoutMs = requestTimeout * 1000; |
6
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
246 |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
247 if (!endpoint.empty()) |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
248 { |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
249 configuration.endpointOverride = endpoint.c_str(); |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
250 } |
393fcf337462
AWS: added 3 configurations: Endpoint, ConnectionTimeout, RequestTimeout
Alain Mazy
parents:
1
diff
changeset
|
251 |
27
e1f52b851827
Added "VirtualAddressing" configuration option in the AWS S3 plugin (for compatibility with minio)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
15
diff
changeset
|
252 Aws::S3::S3Client client(credentials, configuration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, virtualAddressing); |
1 | 253 |
254 OrthancPlugins::LogInfo("AWS S3 storage initialized"); | |
255 | |
15 | 256 return new AwsS3StoragePlugin(client, bucketName, enableLegacyStorageStructure); |
1 | 257 } |
258 catch (const std::exception& e) | |
259 { | |
260 OrthancPlugins::LogError(std::string("AzureBlobStorage plugin: failed to initialize plugin: ") + e.what()); | |
261 return nullptr; | |
262 } | |
263 | |
264 } | |
265 | |
15 | 266 AwsS3StoragePlugin::AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName, bool enableLegacyStorageStructure) |
267 : BaseStoragePlugin(enableLegacyStorageStructure), | |
268 client_(client), | |
1 | 269 bucketName_(bucketName) |
270 { | |
271 | |
272 } | |
273 | |
274 IStoragePlugin::IWriter* AwsS3StoragePlugin::GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) | |
275 { | |
276 return new Writer(client_, bucketName_, GetPath(uuid, type, encryptionEnabled)); | |
277 } | |
278 | |
279 IStoragePlugin::IReader* AwsS3StoragePlugin::GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) | |
280 { | |
281 return new Reader(client_, bucketName_, GetPath(uuid, type, encryptionEnabled)); | |
282 } | |
283 | |
284 void AwsS3StoragePlugin::DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) | |
285 { | |
286 std::string path = GetPath(uuid, type, encryptionEnabled); | |
287 | |
288 Aws::S3::Model::DeleteObjectRequest deleteObjectRequest; | |
289 deleteObjectRequest.SetBucket(bucketName_.c_str()); | |
290 deleteObjectRequest.SetKey(path.c_str()); | |
291 | |
292 auto result = client_.DeleteObject(deleteObjectRequest); | |
293 | |
294 if (!result.IsSuccess()) | |
295 { | |
296 throw StoragePluginException(std::string("error while deleting file ") + path + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str()); | |
297 } | |
298 | |
299 } |