1
|
1 /**
|
|
2 * Cloud storage plugins for Orthanc
|
|
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
|
|
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";
|
|
35
|
|
36 class AwsS3StoragePlugin : public IStoragePlugin
|
|
37 {
|
|
38 public:
|
|
39
|
|
40 Aws::S3::S3Client client_;
|
|
41 std::string bucketName_;
|
|
42
|
|
43 public:
|
|
44
|
|
45 AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName);
|
|
46
|
|
47 virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
48 virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
49 virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
50 private:
|
|
51 virtual std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
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 }
|
|
133 throw StoragePluginException(std::string("error while reading file ") + path_ + ": multiple objet with same name !");
|
|
134 }
|
|
135 else
|
|
136 {
|
|
137 throw StoragePluginException(std::string("error while reading file ") + path_ + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str());
|
|
138 }
|
|
139 }
|
|
140
|
|
141 virtual void Read(char* data, size_t size)
|
|
142 {
|
|
143 Aws::S3::Model::GetObjectRequest getObjectRequest;
|
|
144 getObjectRequest.SetBucket(bucketName_.c_str());
|
|
145 getObjectRequest.SetKey(path_.c_str());
|
|
146
|
|
147 getObjectRequest.SetResponseStreamFactory(
|
|
148 [data, size]()
|
|
149 {
|
|
150 std::unique_ptr<Aws::StringStream>
|
|
151 istream(Aws::New<Aws::StringStream>(ALLOCATION_TAG));
|
|
152
|
|
153 istream->rdbuf()->pubsetbuf(static_cast<char*>(data),
|
|
154 size);
|
|
155
|
|
156 return istream.release();
|
|
157 });
|
|
158
|
|
159 // Get the object
|
|
160 auto result = client_.GetObject(getObjectRequest);
|
|
161 if (result.IsSuccess())
|
|
162 {
|
|
163 }
|
|
164 else
|
|
165 {
|
|
166 throw StoragePluginException(std::string("error while reading file ") + path_ + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str());
|
|
167 }
|
|
168 }
|
|
169
|
|
170 };
|
|
171
|
|
172
|
|
173
|
|
174 const char* AwsS3StoragePluginFactory::GetStoragePluginName()
|
|
175 {
|
|
176 return "AWS S3 Storage";
|
|
177 }
|
|
178
|
|
179 IStoragePlugin* AwsS3StoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig)
|
|
180 {
|
|
181 static const char* const PLUGIN_SECTION = "AwsS3Storage";
|
|
182 if (!orthancConfig.IsSection(PLUGIN_SECTION))
|
|
183 {
|
|
184 OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing. Plugin is not enabled.");
|
|
185 return nullptr;
|
|
186 }
|
|
187
|
|
188 OrthancPlugins::OrthancConfiguration pluginSection;
|
|
189 orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
|
|
190
|
|
191 std::string bucketName;
|
|
192 std::string region;
|
|
193 std::string accessKey;
|
|
194 std::string secretKey;
|
|
195
|
|
196 if (!pluginSection.LookupStringValue(bucketName, "BucketName"))
|
|
197 {
|
|
198 OrthancPlugins::LogError("AwsS3Storage/BucketName configuration missing. Unable to initialize plugin");
|
|
199 return nullptr;
|
|
200 }
|
|
201
|
|
202 if (!pluginSection.LookupStringValue(region, "Region"))
|
|
203 {
|
|
204 OrthancPlugins::LogError("AwsS3Storage/Region configuration missing. Unable to initialize plugin");
|
|
205 return nullptr;
|
|
206 }
|
|
207
|
|
208 if (!pluginSection.LookupStringValue(accessKey, "AccessKey"))
|
|
209 {
|
|
210 OrthancPlugins::LogError("AwsS3Storage/AccessKey configuration missing. Unable to initialize plugin");
|
|
211 return nullptr;
|
|
212 }
|
|
213
|
|
214 if (!pluginSection.LookupStringValue(secretKey, "SecretKey"))
|
|
215 {
|
|
216 OrthancPlugins::LogError("AwsS3Storage/SecretKey configuration missing. Unable to initialize plugin");
|
|
217 return nullptr;
|
|
218 }
|
|
219
|
|
220 try
|
|
221 {
|
|
222 Aws::SDKOptions options;
|
|
223 Aws::InitAPI(options);
|
|
224
|
|
225 Aws::Auth::AWSCredentials credentials(accessKey.c_str(), secretKey.c_str());
|
|
226 Aws::Client::ClientConfiguration configuration;
|
|
227 configuration.region = region.c_str();
|
|
228 Aws::S3::S3Client client(credentials, configuration);
|
|
229
|
|
230 OrthancPlugins::LogInfo("AWS S3 storage initialized");
|
|
231
|
|
232 return new AwsS3StoragePlugin(client, bucketName);
|
|
233 }
|
|
234 catch (const std::exception& e)
|
|
235 {
|
|
236 OrthancPlugins::LogError(std::string("AzureBlobStorage plugin: failed to initialize plugin: ") + e.what());
|
|
237 return nullptr;
|
|
238 }
|
|
239
|
|
240 }
|
|
241
|
|
242 AwsS3StoragePlugin::AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName)
|
|
243 : client_(client),
|
|
244 bucketName_(bucketName)
|
|
245 {
|
|
246
|
|
247 }
|
|
248
|
|
249 IStoragePlugin::IWriter* AwsS3StoragePlugin::GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
250 {
|
|
251 return new Writer(client_, bucketName_, GetPath(uuid, type, encryptionEnabled));
|
|
252 }
|
|
253
|
|
254 IStoragePlugin::IReader* AwsS3StoragePlugin::GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
255 {
|
|
256 return new Reader(client_, bucketName_, GetPath(uuid, type, encryptionEnabled));
|
|
257 }
|
|
258
|
|
259 void AwsS3StoragePlugin::DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
260 {
|
|
261 std::string path = GetPath(uuid, type, encryptionEnabled);
|
|
262
|
|
263 Aws::S3::Model::DeleteObjectRequest deleteObjectRequest;
|
|
264 deleteObjectRequest.SetBucket(bucketName_.c_str());
|
|
265 deleteObjectRequest.SetKey(path.c_str());
|
|
266
|
|
267 auto result = client_.DeleteObject(deleteObjectRequest);
|
|
268
|
|
269 if (!result.IsSuccess())
|
|
270 {
|
|
271 throw StoragePluginException(std::string("error while deleting file ") + path + ": " + result.GetError().GetExceptionName().c_str() + " " + result.GetError().GetMessage().c_str());
|
|
272 }
|
|
273
|
|
274 }
|
|
275
|
|
276 std::string AwsS3StoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
277 {
|
|
278 std::string path = std::string(uuid);
|
|
279
|
|
280 if (type == OrthancPluginContentType_Dicom)
|
|
281 {
|
|
282 path += ".dcm";
|
|
283 }
|
|
284 else if (type == OrthancPluginContentType_DicomAsJson)
|
|
285 {
|
|
286 path += ".json";
|
|
287 }
|
|
288 else
|
|
289 {
|
|
290 path += ".unk";
|
|
291 }
|
|
292
|
|
293 if (encryptionEnabled)
|
|
294 {
|
|
295 path += ".enc";
|
|
296 }
|
|
297 return path;
|
|
298 }
|