You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by GitBox <gi...@apache.org> on 2020/09/30 13:40:22 UTC
[GitHub] [nifi-minifi-cpp] lordgamez opened a new pull request #915: MINIFICPP-1376 Create PutS3Object processor
lordgamez opened a new pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915
Implementation of PutS3Object processor. The purpose of the processor is to upload an S3 object created from a flow file to AWS storage.
Jira issue: https://issues.apache.org/jira/browse/MINIFICPP-1376
---------------------------------------------------------------------------------------------------
Thank you for submitting a contribution to Apache NiFi - MiNiFi C++.
In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:
### For all changes:
- [ ] Is there a JIRA ticket associated with this PR? Is it referenced
in the commit message?
- [ ] Does your PR title start with MINIFICPP-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
- [ ] Has your PR been rebased against the latest commit within the target branch (typically main)?
- [ ] Is your initial contribution a single, squashed commit?
### For code changes:
- [ ] If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under [ASF 2.0](http://www.apache.org/legal/resolved.html#category-a)?
- [ ] If applicable, have you updated the LICENSE file?
- [ ] If applicable, have you updated the NOTICE file?
### For documentation related changes:
- [ ] Have you ensured that format looks appropriate for the output in which it is rendered?
### Note:
Please ensure that once the PR is submitted, you check GitHub Actions CI results for build issues and submit an update to your PR as soon as possible.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509426886
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
I searched for the `s3.permission` parameters in NiFi if any of the processors have them as outputs, but I could not find any, only as the default input values of PutS3Object. I am not sure, where these defaults come from, but I suppose in this case it would be more advantageous to have support for agents without EL support if we cannot find any other references to them. I removed the EL dependent default values in [bc6b752](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/bc6b752e03ddd29dc150cbb598414cb332ca5592)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498867046
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
+ result_list += "emailAddress=\"" + trimmed_user + "\"";
+ } else {
+ result_list += "id=" + trimmed_user;
+ }
+ }
+
+ return result_list;
+}
+
+bool PutS3Object::setCannedAcl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(CannedACL, put_s3_request_params_.canned_acl, flow_file);
+ if (!put_s3_request_params_.canned_acl.empty() && CANNED_ACLS.find(put_s3_request_params_.canned_acl) == CANNED_ACLS.end()) {
+ logger_->log_error("Canned ACL is invalid!");
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Canned ACL [%s]", put_s3_request_params_.canned_acl);
+ return true;
+}
+
+bool PutS3Object::setAccessControl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ std::string value;
+ if (context->getProperty(FullControlUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.fullcontrol_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Full Control User List [%s]", value);
+ }
+ if (context->getProperty(ReadPermissionUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_permission_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read Permission User List [%s]", value);
+ }
+ if (context->getProperty(ReadACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read ACL User List [%s]", value);
+ }
+ if (context->getProperty(WriteACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.write_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Write ACL User List [%s]", value);
+ }
+
+ return setCannedAcl(context, flow_file);
+}
+
+bool PutS3Object::getExpressionLanguageSupportedProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(ObjectKey, put_s3_request_params_.object_key, flow_file);
+ logger_->log_debug("PutS3Object: Object Key [%s]", put_s3_request_params_.object_key);
+ if (!context->getProperty(Bucket, put_s3_request_params_.bucket, flow_file) || put_s3_request_params_.bucket.empty()) {
+ logger_->log_error("Object Key is invalid or empty!", put_s3_request_params_.bucket);
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ context->getProperty(ContentType, put_s3_request_params_.content_type, flow_file);
+ logger_->log_debug("PutS3Object: Content Type [%s]", put_s3_request_params_.content_type);
+
+ auto credentials = getAWSCredentials(context, flow_file);
+ if (!credentials) {
+ logger_->log_error("AWS Credentials have not been set!");
+ return false;
+ }
+ s3_wrapper_->setCredentials(credentials.value());
+
+ if (!setProxy(context, flow_file)) {
+ context->yield();
+ return false;
+ }
+
+ std::string value;
+ if (context->getProperty(EndpointOverrideURL, value, flow_file) && !value.empty()) {
+ s3_wrapper_->setEndpointOverrideUrl(value);
+ logger_->log_debug("PutS3Object: Endpoint Override URL [%d]", value);
+ }
+
+ return setAccessControl(context, flow_file);
+}
+
+void PutS3Object::onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) {
Review comment:
I think it's a good idea as an upload could take a some time depending on the latency/region/object size so the scheduling and load balancing could be optimized with this. I'm still discussing some requirements with @arpadboda I'll check with him about this as well.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498765800
##########
File path: extensions/aws/AWSLoader.h
##########
@@ -16,43 +16,48 @@
* limitations under the License.
*/
-#ifndef NIFI_MINIFI_CPP_AWSLOADER_H
-#define NIFI_MINIFI_CPP_AWSLOADER_H
+#pragma once
+
+#include <vector>
+#include <string>
+#include <memory>
#include "core/ClassLoader.h"
#include "utils/StringUtils.h"
#include "controllerservices/AWSCredentialsService.h"
+#include "processors/PutS3Object.h"
class AWSObjectFactory : public core::ObjectFactory {
-
public:
AWSObjectFactory() = default;
/**
* Gets the name of the object.
* @return class name of processor
*/
- virtual std::string getName() override{
+ std::string getName() override {
return "AWSObjectFactory";
}
- virtual std::string getClassName() override{
+ std::string getClassName() override {
return "AWSObjectFactory";
}
/**
* Gets the class name for the object
* @return class name for the processor.
*/
- virtual std::vector<std::string> getClassNames() override{
+ std::vector<std::string> getClassNames() override {
std::vector<std::string> class_names;
class_names.push_back("AWSCredentialsService");
Review comment:
I suppose we do, I will have to check how it worked without it even in EFM. Fixed in [d2fc8cd](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/d2fc8cdf064177ec7188ff4e80228724c1711fb2)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499475376
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
+ result_list += "emailAddress=\"" + trimmed_user + "\"";
+ } else {
+ result_list += "id=" + trimmed_user;
+ }
+ }
+
+ return result_list;
+}
+
+bool PutS3Object::setCannedAcl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(CannedACL, put_s3_request_params_.canned_acl, flow_file);
+ if (!put_s3_request_params_.canned_acl.empty() && CANNED_ACLS.find(put_s3_request_params_.canned_acl) == CANNED_ACLS.end()) {
+ logger_->log_error("Canned ACL is invalid!");
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Canned ACL [%s]", put_s3_request_params_.canned_acl);
+ return true;
+}
+
+bool PutS3Object::setAccessControl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ std::string value;
+ if (context->getProperty(FullControlUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.fullcontrol_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Full Control User List [%s]", value);
+ }
+ if (context->getProperty(ReadPermissionUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_permission_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read Permission User List [%s]", value);
+ }
+ if (context->getProperty(ReadACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read ACL User List [%s]", value);
+ }
+ if (context->getProperty(WriteACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.write_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Write ACL User List [%s]", value);
+ }
+
+ return setCannedAcl(context, flow_file);
+}
+
+bool PutS3Object::getExpressionLanguageSupportedProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(ObjectKey, put_s3_request_params_.object_key, flow_file);
+ logger_->log_debug("PutS3Object: Object Key [%s]", put_s3_request_params_.object_key);
+ if (!context->getProperty(Bucket, put_s3_request_params_.bucket, flow_file) || put_s3_request_params_.bucket.empty()) {
+ logger_->log_error("Object Key is invalid or empty!", put_s3_request_params_.bucket);
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ context->getProperty(ContentType, put_s3_request_params_.content_type, flow_file);
+ logger_->log_debug("PutS3Object: Content Type [%s]", put_s3_request_params_.content_type);
+
+ auto credentials = getAWSCredentials(context, flow_file);
+ if (!credentials) {
+ logger_->log_error("AWS Credentials have not been set!");
+ return false;
+ }
+ s3_wrapper_->setCredentials(credentials.value());
+
+ if (!setProxy(context, flow_file)) {
+ context->yield();
+ return false;
+ }
+
+ std::string value;
+ if (context->getProperty(EndpointOverrideURL, value, flow_file) && !value.empty()) {
+ s3_wrapper_->setEndpointOverrideUrl(value);
+ logger_->log_debug("PutS3Object: Endpoint Override URL [%d]", value);
+ }
+
+ return setAccessControl(context, flow_file);
+}
+
+void PutS3Object::onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) {
Review comment:
I discussed with @arpadboda and we do not really have any benefit of the batch processing in case of an `EVENT_DRIVEN` processor (which should be a main use case) as we still have to do every processing step on a single flow file basis. Unfortunately [S3 does not support batch upload of objects](https://stackoverflow.com/questions/15050146/is-it-possible-to-perform-a-batch-upload-to-amazon-s3) so cannot benefit from a single request batch upload either.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498888963
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
Fixed in [2e71a70](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/2e71a707a31f5564360c40c7bfdd38c3d97302ef)
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
Fixed in [2e71a70](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/2e71a707a31f5564360c40c7bfdd38c3d97302ef)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509348502
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
These values control the permissions of the uploaded object. We can give email addresses or AWS IDs that should have read/write/full control permissions on the uploaded object, to which by default only the owner (the uploader's account) have access if no other permissions are given. I think that should be okay in our case. The current default values like ${s3.permissions.full.users} are from the Nifi version of PutS3Object which can probably come from other AWS processors, but we don't have those yet, so we can remove those.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r502231479
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,195 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+ constexpr const char *AF_SOUTH_1 = "af-south-1";
+ constexpr const char *AP_EAST_1 = "ap-east-1";
+ constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+ constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+ constexpr const char *EU_SOUTH_1 = "eu-south-1";
+ constexpr const char *EU_WEST_3 = "eu-west-3";
+ constexpr const char *EU_NORTH_1 = "eu-north-1";
+ constexpr const char *ME_SOUTH_1 = "me-south-1";
+ constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
Review comment:
Fixed in [dff0c8a](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/dff0c8a327c5c6e13a4f6008314986e3bd57b441)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r501503306
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,195 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+ constexpr const char *AF_SOUTH_1 = "af-south-1";
+ constexpr const char *AP_EAST_1 = "ap-east-1";
+ constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+ constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+ constexpr const char *EU_SOUTH_1 = "eu-south-1";
+ constexpr const char *EU_WEST_3 = "eu-west-3";
+ constexpr const char *EU_NORTH_1 = "eu-north-1";
+ constexpr const char *ME_SOUTH_1 = "me-south-1";
+ constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
Review comment:
1. I think we do not indent inside namespaces (although I do not think that it hinders readability here)
2. also do you think that putting the regions in lexicographical order could help us add-remove (or diff) the list in the future?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498792335
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,188 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+
+} // namespace region
+
+class PutS3Object : public core::Processor {
+ public:
+ static constexpr char const* ProcessorName = "PutS3Object";
+
+ static const std::set<std::string> CANNED_ACLS;
+ static const std::set<std::string> REGIONS;
+ static const std::set<std::string> STORAGE_CLASSES;
+ static const std::set<std::string> SERVER_SIDE_ENCRYPTIONS;
+
+ // Supported Properties
+ static const core::Property ObjectKey;
+ static const core::Property Bucket;
+ static const core::Property ContentType;
+ static const core::Property AccessKey;
+ static const core::Property SecretKey;
+ static const core::Property CredentialsFile;
+ static const core::Property AWSCredentialsProviderService;
+ static const core::Property StorageClass;
+ static const core::Property ServerSideEncryption;
+ static const core::Property Region;
+ static const core::Property CommunicationsTimeout;
+ static const core::Property FullControlUserList;
+ static const core::Property ReadPermissionUserList;
+ static const core::Property ReadACLUserList;
+ static const core::Property WriteACLUserList;
+ static const core::Property CannedACL;
+ static const core::Property EndpointOverrideURL;
+ static const core::Property ProxyHost;
+ static const core::Property ProxyPort;
+ static const core::Property ProxyUsername;
+ static const core::Property ProxyPassword;
+
+ // Supported Relationships
+ static const core::Relationship Failure;
+ static const core::Relationship Success;
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid = minifi::utils::Identifier())
+ : PutS3Object(name, uuid, minifi::utils::make_unique<aws::s3::S3Wrapper>()) {
+ }
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid, std::unique_ptr<aws::s3::S3WrapperBase> s3_wrapper)
+ : core::Processor(std::move(name), uuid)
+ , s3_wrapper_(std::move(s3_wrapper)) {
+ }
+
+ ~PutS3Object() override = default;
+
+ bool supportsDynamicProperties() override { return true; }
+ void initialize() override;
+ void onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) override;
+ void onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) override;
+
+ class ReadCallback : public InputStreamCallback {
+ public:
+ static const uint64_t MAX_SIZE = 5UL * 1024UL * 1024UL * 1024UL; // 5GB limit on AWS
+ static const uint64_t BUFFER_SIZE = 4096;
+
+ ReadCallback(uint64_t flow_size, const minifi::aws::s3::PutObjectRequestParameters& options, aws::s3::S3WrapperBase* s3_wrapper)
+ : flow_size_(flow_size)
+ , options_(std::move(options))
+ , s3_wrapper_(s3_wrapper) {
+ }
+
+ ~ReadCallback() = default;
+
+ int64_t process(std::shared_ptr<io::BaseStream> stream) {
Review comment:
we should add `override` to `~ReadCallback` and `process`
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] szaszm commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
szaszm commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r501682430
##########
File path: cmake/BundledAwsSdkCpp.cmake
##########
@@ -23,8 +23,8 @@ function(use_bundled_libaws SOURCE_DIR BINARY_DIR)
set(SUFFIX "a")
endif()
set(BYPRODUCTS
- "lib/libaws-cpp-sdk-core.${SUFFIX}"
- "lib/libaws-cpp-sdk-s3.${SUFFIX}")
+ "${CMAKE_INSTALL_LIBDIR}/libaws-cpp-sdk-core.${SUFFIX}"
Review comment:
You need to `include(GNUInstallDirs)` for this variable.
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
Maybe an empty default with the same meaning would be better, so that agents without Expression Language support are also working in a reasonable way.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498754719
##########
File path: extensions/aws/AWSLoader.h
##########
@@ -16,43 +16,48 @@
* limitations under the License.
*/
-#ifndef NIFI_MINIFI_CPP_AWSLOADER_H
-#define NIFI_MINIFI_CPP_AWSLOADER_H
+#pragma once
+
+#include <vector>
+#include <string>
+#include <memory>
#include "core/ClassLoader.h"
#include "utils/StringUtils.h"
#include "controllerservices/AWSCredentialsService.h"
+#include "processors/PutS3Object.h"
class AWSObjectFactory : public core::ObjectFactory {
-
public:
AWSObjectFactory() = default;
/**
* Gets the name of the object.
* @return class name of processor
*/
- virtual std::string getName() override{
+ std::string getName() override {
return "AWSObjectFactory";
}
- virtual std::string getClassName() override{
+ std::string getClassName() override {
return "AWSObjectFactory";
}
/**
* Gets the class name for the object
* @return class name for the processor.
*/
- virtual std::vector<std::string> getClassNames() override{
+ std::vector<std::string> getClassNames() override {
std::vector<std::string> class_names;
class_names.push_back("AWSCredentialsService");
Review comment:
don't we need to add `PutS3Object` to the `class_names` vector as well?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509427851
##########
File path: cmake/BundledAwsSdkCpp.cmake
##########
@@ -23,8 +23,8 @@ function(use_bundled_libaws SOURCE_DIR BINARY_DIR)
set(SUFFIX "a")
endif()
set(BYPRODUCTS
- "lib/libaws-cpp-sdk-core.${SUFFIX}"
- "lib/libaws-cpp-sdk-s3.${SUFFIX}")
+ "${CMAKE_INSTALL_LIBDIR}/libaws-cpp-sdk-core.${SUFFIX}"
Review comment:
Added it in [8a05f1a](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/8a05f1a3821aabe6a72607d9dae28a418dd9f57d) and I also found some problems with the AWS build on Windows. Fixed them in [42eb471](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/42eb471011efb7c93e13a553d6a04f129ccd1439)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499476107
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
Checking only for '@' for email addresses in [967b355](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/967b355ca776f8a2dba705e390a91bc6aa5380a3)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] arpadboda commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
arpadboda commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r512638860
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, "
+ "PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ if (aws_credentials_service) {
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->getString("accessKey", access_key) && !access_key.empty() && properties->getString("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
Review comment:
This is already implemented in join.
I bit more simple way of doing this:
```
auto users : minifi::utils::StringUtils::split(comma_separated_list, ",")
for(auto& user: users) {
auto trimmed_user = minifi::utils::StringUtils::trim(user);
if (trimmed_user.find('@') != std::string::npos) {
user = "emailAddress=\"" + trimmed_user + "\"";
} else {
user = "id=" + trimmed_user;
}
}
return minifi::utils::StringUtils::join(", ", users);
```
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r512663056
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,197 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+#include <set>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+constexpr const char *AF_SOUTH_1 = "af-south-1";
+constexpr const char *AP_EAST_1 = "ap-east-1";
+constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+constexpr const char *AP_SOUTH_1 = "ap-south-1";
+constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+constexpr const char *CN_NORTH_1 = "cn-north-1";
+constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+constexpr const char *EU_NORTH_1 = "eu-north-1";
+constexpr const char *EU_SOUTH_1 = "eu-south-1";
+constexpr const char *EU_WEST_1 = "eu-west-1";
+constexpr const char *EU_WEST_2 = "eu-west-2";
+constexpr const char *EU_WEST_3 = "eu-west-3";
+constexpr const char *ME_SOUTH_1 = "me-south-1";
+constexpr const char *SA_EAST_1 = "sa-east-1";
+constexpr const char *US_EAST_1 = "us-east-1";
+constexpr const char *US_EAST_2 = "us-east-2";
+constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
+constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+constexpr const char *US_WEST_1 = "us-west-1";
+constexpr const char *US_WEST_2 = "us-west-2";
+
+} // namespace region
+
+class PutS3Object : public core::Processor {
+ public:
+ static constexpr char const* ProcessorName = "PutS3Object";
+
+ static const std::set<std::string> CANNED_ACLS;
+ static const std::set<std::string> REGIONS;
+ static const std::set<std::string> STORAGE_CLASSES;
+ static const std::set<std::string> SERVER_SIDE_ENCRYPTIONS;
+
+ // Supported Properties
+ static const core::Property ObjectKey;
+ static const core::Property Bucket;
+ static const core::Property ContentType;
+ static const core::Property AccessKey;
+ static const core::Property SecretKey;
+ static const core::Property CredentialsFile;
+ static const core::Property AWSCredentialsProviderService;
+ static const core::Property StorageClass;
+ static const core::Property ServerSideEncryption;
+ static const core::Property Region;
+ static const core::Property CommunicationsTimeout;
+ static const core::Property FullControlUserList;
+ static const core::Property ReadPermissionUserList;
+ static const core::Property ReadACLUserList;
+ static const core::Property WriteACLUserList;
+ static const core::Property CannedACL;
+ static const core::Property EndpointOverrideURL;
+ static const core::Property ProxyHost;
+ static const core::Property ProxyPort;
+ static const core::Property ProxyUsername;
+ static const core::Property ProxyPassword;
+
+ // Supported Relationships
+ static const core::Relationship Failure;
+ static const core::Relationship Success;
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid = minifi::utils::Identifier())
+ : PutS3Object(name, uuid, minifi::utils::make_unique<aws::s3::S3Wrapper>()) {
+ }
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid, std::unique_ptr<aws::s3::S3WrapperBase> s3_wrapper)
+ : core::Processor(std::move(name), uuid)
+ , s3_wrapper_(std::move(s3_wrapper)) {
+ }
+
+ ~PutS3Object() override = default;
+
+ bool supportsDynamicProperties() override { return true; }
+ void initialize() override;
+ void onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) override;
+ void onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) override;
+
+ class ReadCallback : public InputStreamCallback {
+ public:
+ static const uint64_t MAX_SIZE = 5UL * 1024UL * 1024UL * 1024UL; // 5GB limit on AWS
+ static const uint64_t BUFFER_SIZE = 4096;
+
+ ReadCallback(uint64_t flow_size, const minifi::aws::s3::PutObjectRequestParameters& options, aws::s3::S3WrapperBase* s3_wrapper)
+ : flow_size_(flow_size)
+ , options_(options)
+ , s3_wrapper_(s3_wrapper) {
+ }
+
+ int64_t process(std::shared_ptr<io::BaseStream> stream) override {
+ if (flow_size_ > MAX_SIZE) {
+ return -1;
+ }
+ std::vector<uint8_t> buffer;
+ auto data_stream = std::make_shared<std::stringstream>();
+ buffer.reserve(BUFFER_SIZE);
+ read_size_ = 0;
+ while (read_size_ < flow_size_) {
+ auto next_read_size = flow_size_ - read_size_ < BUFFER_SIZE ? flow_size_ - read_size_ : BUFFER_SIZE;
Review comment:
Fixed in [e994558](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/e994558ba8904488b0e3489d51236fb7d4e31ce1)
##########
File path: extensions/aws/utils/AWSSdkLogger.cpp
##########
@@ -0,0 +1,105 @@
+/**
+ * @file AWSSdkLogger.cpp
+ * AWSSdkLogger class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AWSSdkLogger.h"
+
+#include "aws/core/utils/logging/LogLevel.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace utils {
+
+Aws::Utils::Logging::LogLevel AWSSdkLogger::GetLogLevel() const {
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::trace))
+ return Aws::Utils::Logging::LogLevel::Trace;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::debug))
+ return Aws::Utils::Logging::LogLevel::Debug;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::info))
+ return Aws::Utils::Logging::LogLevel::Info;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::warn))
+ return Aws::Utils::Logging::LogLevel::Warn;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::err))
+ return Aws::Utils::Logging::LogLevel::Error;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::critical))
+ return Aws::Utils::Logging::LogLevel::Fatal;
+ return Aws::Utils::Logging::LogLevel::Off;
+}
+
+void AWSSdkLogger::Log(Aws::Utils::Logging::LogLevel log_level, const char* tag, const char* format_str, ...) {
+ switch (log_level) {
+ case Aws::Utils::Logging::LogLevel::Trace:
+ logger_->log_trace("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Debug:
+ logger_->log_debug("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Info:
+ logger_->log_info("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Warn:
+ logger_->log_warn("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Error:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Fatal:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ default:
+ break;
+ }
+}
+
+void AWSSdkLogger::LogStream(Aws::Utils::Logging::LogLevel log_level, const char* tag, const Aws::OStringStream &message_stream) {
+ switch (log_level) {
Review comment:
Fixed in [e994558](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/e994558ba8904488b0e3489d51236fb7d4e31ce1)
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, "
+ "PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ if (aws_credentials_service) {
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->getString("accessKey", access_key) && !access_key.empty() && properties->getString("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
Review comment:
Fixed in [e994558](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/e994558ba8904488b0e3489d51236fb7d4e31ce1)
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, "
+ "PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ if (aws_credentials_service) {
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->getString("accessKey", access_key) && !access_key.empty() && properties->getString("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
Review comment:
Fixed in [e994558](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/e994558ba8904488b0e3489d51236fb7d4e31ce1)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499382672
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
the regex rejects a number of otherwise valid email addresses, e.g. the "local-part" (part before the '@' character) may contain "!~#" and others, it could even be quoted or contain comments, the domain part is similarly complex
I don't know what the requirement is here, do we aim to catch all valid email addresses? (if not, could checking for the '@' character be enough?)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499386356
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,489 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
should we omit the `nullptr != service` check? `dynamic_pointer_cast` should be safe to use on an empty `shared_ptr`
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498884382
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
Yes, you are right and it is unnecessary to complicate it in this case.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] arpadboda commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
arpadboda commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r512621151
##########
File path: cmake/BundledAwsSdkCpp.cmake
##########
@@ -68,40 +77,40 @@ function(use_bundled_libaws SOURCE_DIR BINARY_DIR)
ExternalProject_Add(
aws-c-common-external
GIT_REPOSITORY "https://github.com/awslabs/aws-c-common.git"
- GIT_TAG "ac02e1728d740bb9106b6ea727cd3378f8ea438a"
+ GIT_TAG "d8f6f067975cd3670c62cca0455b9d381db19756"
SOURCE_DIR "${BINARY_DIR}/thirdparty/aws-c-common-src"
INSTALL_DIR "${BINARY_DIR}/thirdparty/libaws-install"
LIST_SEPARATOR % # This is needed for passing semicolon-separated lists
CMAKE_ARGS ${AWS_C_COMMON_CMAKE_ARGS}
- BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/lib/libaws-c-common.${SUFFIX}"
+ BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/${CMAKE_INSTALL_LIBDIR}/${PREFIX}aws-c-common.${SUFFIX}"
EXCLUDE_FROM_ALL TRUE
)
ExternalProject_Add(
aws-checksum-external
GIT_REPOSITORY "https://github.com/awslabs/aws-checksums.git"
- GIT_TAG "41dc36d14b0898bd34e3f91c808fcb00f5e21875"
+ GIT_TAG "8e1a84c2924774db1b9d945c556343b217d71d05"
SOURCE_DIR "${BINARY_DIR}/thirdparty/aws-checksums-src"
INSTALL_DIR "${BINARY_DIR}/thirdparty/libaws-install"
LIST_SEPARATOR % # This is needed for passing semicolon-separated lists
CMAKE_ARGS ${AWS_CHECKSUM_CMAKE_ARGS}
- BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/lib/libaws-checksums.${SUFFIX}"
+ BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/${CMAKE_INSTALL_LIBDIR}/${PREFIX}aws-checksums.${SUFFIX}"
EXCLUDE_FROM_ALL TRUE
)
ExternalProject_Add(
aws-c-event-stream-external
GIT_REPOSITORY "https://github.com/awslabs/aws-c-event-stream.git"
- GIT_TAG "97ab2e57e83ad114679dbee0dcfb5048640debe7"
+ GIT_TAG "3462b68d563d8f9b3a26517b833671a24ab81cc5"
SOURCE_DIR "${BINARY_DIR}/thirdparty/aws-c-event-stream-src"
INSTALL_DIR "${BINARY_DIR}/thirdparty/libaws-install"
LIST_SEPARATOR % # This is needed for passing semicolon-separated lists
CMAKE_ARGS ${AWS_C_EVENT_STREAM_CMAKE_ARGS}
- BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/lib/libaws-c-event-stream.${SUFFIX}"
+ BUILD_BYPRODUCTS "${BINARY_DIR}/thirdparty/libaws-install/${CMAKE_INSTALL_LIBDIR}/${PREFIX}aws-c-event-stream.${SUFFIX}"
EXCLUDE_FROM_ALL TRUE
)
ExternalProject_Add(
aws-sdk-cpp-external
GIT_REPOSITORY "https://github.com/aws/aws-sdk-cpp.git"
- GIT_TAG "1.7.109"
+ GIT_TAG "1.8.52"
Review comment:
👍
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,197 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+#include <set>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+constexpr const char *AF_SOUTH_1 = "af-south-1";
+constexpr const char *AP_EAST_1 = "ap-east-1";
+constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+constexpr const char *AP_SOUTH_1 = "ap-south-1";
+constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+constexpr const char *CN_NORTH_1 = "cn-north-1";
+constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+constexpr const char *EU_NORTH_1 = "eu-north-1";
+constexpr const char *EU_SOUTH_1 = "eu-south-1";
+constexpr const char *EU_WEST_1 = "eu-west-1";
+constexpr const char *EU_WEST_2 = "eu-west-2";
+constexpr const char *EU_WEST_3 = "eu-west-3";
+constexpr const char *ME_SOUTH_1 = "me-south-1";
+constexpr const char *SA_EAST_1 = "sa-east-1";
+constexpr const char *US_EAST_1 = "us-east-1";
+constexpr const char *US_EAST_2 = "us-east-2";
+constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
+constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+constexpr const char *US_WEST_1 = "us-west-1";
+constexpr const char *US_WEST_2 = "us-west-2";
+
+} // namespace region
+
+class PutS3Object : public core::Processor {
+ public:
+ static constexpr char const* ProcessorName = "PutS3Object";
+
+ static const std::set<std::string> CANNED_ACLS;
+ static const std::set<std::string> REGIONS;
+ static const std::set<std::string> STORAGE_CLASSES;
+ static const std::set<std::string> SERVER_SIDE_ENCRYPTIONS;
+
+ // Supported Properties
+ static const core::Property ObjectKey;
+ static const core::Property Bucket;
+ static const core::Property ContentType;
+ static const core::Property AccessKey;
+ static const core::Property SecretKey;
+ static const core::Property CredentialsFile;
+ static const core::Property AWSCredentialsProviderService;
+ static const core::Property StorageClass;
+ static const core::Property ServerSideEncryption;
+ static const core::Property Region;
+ static const core::Property CommunicationsTimeout;
+ static const core::Property FullControlUserList;
+ static const core::Property ReadPermissionUserList;
+ static const core::Property ReadACLUserList;
+ static const core::Property WriteACLUserList;
+ static const core::Property CannedACL;
+ static const core::Property EndpointOverrideURL;
+ static const core::Property ProxyHost;
+ static const core::Property ProxyPort;
+ static const core::Property ProxyUsername;
+ static const core::Property ProxyPassword;
+
+ // Supported Relationships
+ static const core::Relationship Failure;
+ static const core::Relationship Success;
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid = minifi::utils::Identifier())
+ : PutS3Object(name, uuid, minifi::utils::make_unique<aws::s3::S3Wrapper>()) {
+ }
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid, std::unique_ptr<aws::s3::S3WrapperBase> s3_wrapper)
+ : core::Processor(std::move(name), uuid)
+ , s3_wrapper_(std::move(s3_wrapper)) {
+ }
+
+ ~PutS3Object() override = default;
+
+ bool supportsDynamicProperties() override { return true; }
+ void initialize() override;
+ void onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) override;
+ void onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) override;
+
+ class ReadCallback : public InputStreamCallback {
+ public:
+ static const uint64_t MAX_SIZE = 5UL * 1024UL * 1024UL * 1024UL; // 5GB limit on AWS
+ static const uint64_t BUFFER_SIZE = 4096;
+
+ ReadCallback(uint64_t flow_size, const minifi::aws::s3::PutObjectRequestParameters& options, aws::s3::S3WrapperBase* s3_wrapper)
+ : flow_size_(flow_size)
+ , options_(options)
+ , s3_wrapper_(s3_wrapper) {
+ }
+
+ int64_t process(std::shared_ptr<io::BaseStream> stream) override {
+ if (flow_size_ > MAX_SIZE) {
+ return -1;
+ }
+ std::vector<uint8_t> buffer;
+ auto data_stream = std::make_shared<std::stringstream>();
+ buffer.reserve(BUFFER_SIZE);
+ read_size_ = 0;
+ while (read_size_ < flow_size_) {
+ auto next_read_size = flow_size_ - read_size_ < BUFFER_SIZE ? flow_size_ - read_size_ : BUFFER_SIZE;
Review comment:
The right side looks like an std::min to me :)
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, "
+ "PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ if (aws_credentials_service) {
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->getString("accessKey", access_key) && !access_key.empty() && properties->getString("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
Review comment:
Just fancy: in case you use set, you can rely on count instead of comparing the result of find to end.
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
Review comment:
Why do keys need to support EL?
I don't think these could anyhow depend on flowfile or whatever expression, I expect them to be purely static data.
I think the same applies for most of the properties that are validated once (in onschedule)
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,495 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <set>
+#include <memory>
+#include <map>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+#include "utils/MapUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::AF_SOUTH_1, region::AP_EAST_1, region::AP_NORTHEAST_1,
+ region::AP_NORTHEAST_2, region::AP_NORTHEAST_3, region::AP_SOUTH_1, region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2,
+ region::CA_CENTRAL_1, region::CN_NORTH_1, region::CN_NORTHWEST_1, region::EU_CENTRAL_1, region::EU_NORTH_1,
+ region::EU_SOUTH_1, region::EU_WEST_1, region::EU_WEST_2, region::EU_WEST_3, region::ME_SOUTH_1, region::SA_EAST_1,
+ region::US_EAST_1, region::US_EAST_2, region::US_GOV_EAST_1, region::US_GOV_WEST_1, region::US_WEST_1, region::US_WEST_2});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(minifi::utils::MapUtils::getKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(minifi::utils::MapUtils::getKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object. If none is given the filename attribute will be used by default.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change "
+ "the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, "
+ "PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ if (aws_credentials_service) {
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->getString("accessKey", access_key) && !access_key.empty() && properties->getString("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
Review comment:
This is already implemented in join.
I bit more simple way of doing this:
```
auto users : minifi::utils::StringUtils::split(comma_separated_list, ",")
for(auto& user: users) {
auto trimmed_user = minifi::utils::StringUtils::trim(user);
if (trimmed_user.find('@') != std::string::npos) {
user = "emailAddress=\"" + trimmed_user + "\"";
} else {
user += "id=" + trimmed_user;
}
}
return minifi::utils::StringUtils::join(", ", users);
```
##########
File path: extensions/aws/utils/AWSSdkLogger.cpp
##########
@@ -0,0 +1,105 @@
+/**
+ * @file AWSSdkLogger.cpp
+ * AWSSdkLogger class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AWSSdkLogger.h"
+
+#include "aws/core/utils/logging/LogLevel.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace utils {
+
+Aws::Utils::Logging::LogLevel AWSSdkLogger::GetLogLevel() const {
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::trace))
+ return Aws::Utils::Logging::LogLevel::Trace;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::debug))
+ return Aws::Utils::Logging::LogLevel::Debug;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::info))
+ return Aws::Utils::Logging::LogLevel::Info;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::warn))
+ return Aws::Utils::Logging::LogLevel::Warn;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::err))
+ return Aws::Utils::Logging::LogLevel::Error;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::critical))
+ return Aws::Utils::Logging::LogLevel::Fatal;
+ return Aws::Utils::Logging::LogLevel::Off;
+}
+
+void AWSSdkLogger::Log(Aws::Utils::Logging::LogLevel log_level, const char* tag, const char* format_str, ...) {
+ switch (log_level) {
+ case Aws::Utils::Logging::LogLevel::Trace:
+ logger_->log_trace("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Debug:
+ logger_->log_debug("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Info:
+ logger_->log_info("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Warn:
+ logger_->log_warn("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Error:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Fatal:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ default:
+ break;
+ }
+}
+
+void AWSSdkLogger::LogStream(Aws::Utils::Logging::LogLevel log_level, const char* tag, const Aws::OStringStream &message_stream) {
+ switch (log_level) {
Review comment:
Instead of copy-pasting the logic, can't this simply call the one above?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] szaszm commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
szaszm commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509358873
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
Interesting point. NiFi compatibility would be nice, but in NiFi, expression language is not an optional feature. That made me neutral, I'm fine with either option.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499387495
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
is the format of AWS id specified? or is it "not email address"?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498889466
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,188 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+
+} // namespace region
+
+class PutS3Object : public core::Processor {
+ public:
+ static constexpr char const* ProcessorName = "PutS3Object";
+
+ static const std::set<std::string> CANNED_ACLS;
+ static const std::set<std::string> REGIONS;
+ static const std::set<std::string> STORAGE_CLASSES;
+ static const std::set<std::string> SERVER_SIDE_ENCRYPTIONS;
+
+ // Supported Properties
+ static const core::Property ObjectKey;
+ static const core::Property Bucket;
+ static const core::Property ContentType;
+ static const core::Property AccessKey;
+ static const core::Property SecretKey;
+ static const core::Property CredentialsFile;
+ static const core::Property AWSCredentialsProviderService;
+ static const core::Property StorageClass;
+ static const core::Property ServerSideEncryption;
+ static const core::Property Region;
+ static const core::Property CommunicationsTimeout;
+ static const core::Property FullControlUserList;
+ static const core::Property ReadPermissionUserList;
+ static const core::Property ReadACLUserList;
+ static const core::Property WriteACLUserList;
+ static const core::Property CannedACL;
+ static const core::Property EndpointOverrideURL;
+ static const core::Property ProxyHost;
+ static const core::Property ProxyPort;
+ static const core::Property ProxyUsername;
+ static const core::Property ProxyPassword;
+
+ // Supported Relationships
+ static const core::Relationship Failure;
+ static const core::Relationship Success;
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid = minifi::utils::Identifier())
+ : PutS3Object(name, uuid, minifi::utils::make_unique<aws::s3::S3Wrapper>()) {
+ }
+
+ explicit PutS3Object(std::string name, minifi::utils::Identifier uuid, std::unique_ptr<aws::s3::S3WrapperBase> s3_wrapper)
+ : core::Processor(std::move(name), uuid)
+ , s3_wrapper_(std::move(s3_wrapper)) {
+ }
+
+ ~PutS3Object() override = default;
+
+ bool supportsDynamicProperties() override { return true; }
+ void initialize() override;
+ void onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) override;
+ void onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) override;
+
+ class ReadCallback : public InputStreamCallback {
+ public:
+ static const uint64_t MAX_SIZE = 5UL * 1024UL * 1024UL * 1024UL; // 5GB limit on AWS
+ static const uint64_t BUFFER_SIZE = 4096;
+
+ ReadCallback(uint64_t flow_size, const minifi::aws::s3::PutObjectRequestParameters& options, aws::s3::S3WrapperBase* s3_wrapper)
+ : flow_size_(flow_size)
+ , options_(std::move(options))
+ , s3_wrapper_(s3_wrapper) {
+ }
+
+ ~ReadCallback() = default;
+
+ int64_t process(std::shared_ptr<io::BaseStream> stream) {
Review comment:
As there is no need for override in the destructor I removed it and added the override to the process in [2e71a70](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/2e71a707a31f5564360c40c7bfdd38c3d97302ef)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499401028
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,489 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
yes, but we are doing a check there too, ultimately it is not crucial to make this change
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498883803
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
That can be a possible user error in case the user gives the name of another service here. It can cause an error which could be avoided in this case, I'll use a dynamic cast here instead.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498821457
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
doesn't `\w` also match `_`?
also note that this won't match all valid email addresses (if that is the purpose), the regex for that is quite... a mouthful
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498808421
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
is `static_pointer_cast` a safe choice here? we are fetching (instantiating maybe?) a ControllerService based on a configuration parameter, could that contain a value that results in a ControllerService other than AWSCredentialsService?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509310005
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
Sounds good to me, I am not familiar how common are agents without EL support, but it's reasonable to support those as well. In that case the access control properties should lose the default values too, as if those are invalid the put operation fails on AWS. Those properties are not mandatory so we can have empty defaults if that sounds okay?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r502231479
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,195 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+ constexpr const char *AF_SOUTH_1 = "af-south-1";
+ constexpr const char *AP_EAST_1 = "ap-east-1";
+ constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+ constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+ constexpr const char *EU_SOUTH_1 = "eu-south-1";
+ constexpr const char *EU_WEST_3 = "eu-west-3";
+ constexpr const char *EU_NORTH_1 = "eu-north-1";
+ constexpr const char *ME_SOUTH_1 = "me-south-1";
+ constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
Review comment:
Fixed in [dff0c8a](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/dff0c8a327c5c6e13a4f6008314986e3bd57b441)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] szaszm commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
szaszm commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r509319208
##########
File path: PROCESSORS.md
##########
@@ -1125,3 +1126,40 @@ In the list below, the names of required properties appear in bold. Any other pr
|success|All files are routed to success|
+## PutS3Object
+
+### Description
+
+Puts FlowFiles to an Amazon S3 Bucket. The upload uses either the PutS3Object method. The PutS3Object method send the file in a single synchronous call, but it has a 5GB size limit. Larger files sent using the multipart upload methods are currently not supported. The AWS libraries select an endpoint URL based on the AWS region, but this can be overridden with the 'Endpoint Override URL' property for use with other S3-compatible endpoints. The S3 API specifies that the maximum file size for a PutS3Object upload is 5GB.
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
+
+| Name | Default Value | Allowable Values | Description |
+| - | - | - | - |
+|**Object Key**|${filename}||The key of the S3 object<br/>**Supports Expression Language: true**|
Review comment:
I'm not familiar enough with S3 to know this, but if empty is a reasonable default that's usable in simple setups, then I support it.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499396535
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,489 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::dynamic_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
Review comment:
But in that case the `aws_credentials_service` would be nullptr as well after the `dynamic_pointer_cast` and we would call on the nullptr in the next line.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] arpadboda closed pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
arpadboda closed pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498765800
##########
File path: extensions/aws/AWSLoader.h
##########
@@ -16,43 +16,48 @@
* limitations under the License.
*/
-#ifndef NIFI_MINIFI_CPP_AWSLOADER_H
-#define NIFI_MINIFI_CPP_AWSLOADER_H
+#pragma once
+
+#include <vector>
+#include <string>
+#include <memory>
#include "core/ClassLoader.h"
#include "utils/StringUtils.h"
#include "controllerservices/AWSCredentialsService.h"
+#include "processors/PutS3Object.h"
class AWSObjectFactory : public core::ObjectFactory {
-
public:
AWSObjectFactory() = default;
/**
* Gets the name of the object.
* @return class name of processor
*/
- virtual std::string getName() override{
+ std::string getName() override {
return "AWSObjectFactory";
}
- virtual std::string getClassName() override{
+ std::string getClassName() override {
return "AWSObjectFactory";
}
/**
* Gets the class name for the object
* @return class name for the processor.
*/
- virtual std::vector<std::string> getClassNames() override{
+ std::vector<std::string> getClassNames() override {
std::vector<std::string> class_names;
class_names.push_back("AWSCredentialsService");
Review comment:
I suppose we do, I will have to check how it worked without it even when configured via C2. Fixed in [d2fc8cd](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/d2fc8cdf064177ec7188ff4e80228724c1711fb2)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499386719
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
The goal here would be to distinguish email addresses and AWS ids . I am not sure if quotes and comments should be accepted in this case, but for our goal I suppose checking the '@' character should be enough.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r498795889
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
+ result_list += "emailAddress=\"" + trimmed_user + "\"";
+ } else {
+ result_list += "id=" + trimmed_user;
+ }
+ }
+
+ return result_list;
+}
+
+bool PutS3Object::setCannedAcl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(CannedACL, put_s3_request_params_.canned_acl, flow_file);
+ if (!put_s3_request_params_.canned_acl.empty() && CANNED_ACLS.find(put_s3_request_params_.canned_acl) == CANNED_ACLS.end()) {
+ logger_->log_error("Canned ACL is invalid!");
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Canned ACL [%s]", put_s3_request_params_.canned_acl);
+ return true;
+}
+
+bool PutS3Object::setAccessControl(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ std::string value;
+ if (context->getProperty(FullControlUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.fullcontrol_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Full Control User List [%s]", value);
+ }
+ if (context->getProperty(ReadPermissionUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_permission_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read Permission User List [%s]", value);
+ }
+ if (context->getProperty(ReadACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.read_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Read ACL User List [%s]", value);
+ }
+ if (context->getProperty(WriteACLUserList, value, flow_file) && !value.empty()) {
+ put_s3_request_params_.write_acl_user_list = parseAccessControlList(value);
+ logger_->log_debug("PutS3Object: Write ACL User List [%s]", value);
+ }
+
+ return setCannedAcl(context, flow_file);
+}
+
+bool PutS3Object::getExpressionLanguageSupportedProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) {
+ context->getProperty(ObjectKey, put_s3_request_params_.object_key, flow_file);
+ logger_->log_debug("PutS3Object: Object Key [%s]", put_s3_request_params_.object_key);
+ if (!context->getProperty(Bucket, put_s3_request_params_.bucket, flow_file) || put_s3_request_params_.bucket.empty()) {
+ logger_->log_error("Object Key is invalid or empty!", put_s3_request_params_.bucket);
+ return false;
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ context->getProperty(ContentType, put_s3_request_params_.content_type, flow_file);
+ logger_->log_debug("PutS3Object: Content Type [%s]", put_s3_request_params_.content_type);
+
+ auto credentials = getAWSCredentials(context, flow_file);
+ if (!credentials) {
+ logger_->log_error("AWS Credentials have not been set!");
+ return false;
+ }
+ s3_wrapper_->setCredentials(credentials.value());
+
+ if (!setProxy(context, flow_file)) {
+ context->yield();
+ return false;
+ }
+
+ std::string value;
+ if (context->getProperty(EndpointOverrideURL, value, flow_file) && !value.empty()) {
+ s3_wrapper_->setEndpointOverrideUrl(value);
+ logger_->log_debug("PutS3Object: Endpoint Override URL [%d]", value);
+ }
+
+ return setAccessControl(context, flow_file);
+}
+
+void PutS3Object::onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) {
Review comment:
some processors are now receiving a `Batch Size` property to consume more flowFiles (but at most `Batch Size`) in a single trigger, this processor looks like a good candidate for such a behavior, it could also be implemented later in a follow-up PR, what do you think?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r499407872
##########
File path: extensions/aws/processors/PutS3Object.cpp
##########
@@ -0,0 +1,487 @@
+/**
+ * @file PutS3Object.cpp
+ * PutS3Object class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PutS3Object.h"
+
+#include <string>
+#include <regex>
+#include <set>
+#include <memory>
+
+#include "AWSCredentialsService.h"
+#include "properties/Properties.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace {
+
+template<typename T, typename U>
+std::set<T> getMapKeys(const std::map<T,U>& m) {
+ std::set<T> keys;
+ for (const auto& pair : m) {
+ keys.insert(pair.first);
+ }
+ return keys;
+}
+
+} // namespace
+
+const std::set<std::string> PutS3Object::CANNED_ACLS(getMapKeys(minifi::aws::s3::CANNED_ACL_MAP));
+const std::set<std::string> PutS3Object::REGIONS({region::US_GOV_WEST_1, region::US_EAST_1, region::US_EAST_2, region::US_WEST_1,
+ region::US_WEST_2, region::EU_WEST_1, region::EU_WEST_2, region::EU_CENTRAL_1, region::AP_SOUTH_1,
+ region::AP_SOUTHEAST_1, region::AP_SOUTHEAST_2, region::AP_NORTHEAST_1, region::AP_NORTHEAST_2,
+ region::SA_EAST_1, region::CN_NORTH_1, region::CA_CENTRAL_1});
+const std::set<std::string> PutS3Object::STORAGE_CLASSES(getMapKeys(minifi::aws::s3::STORAGE_CLASS_MAP));
+const std::set<std::string> PutS3Object::SERVER_SIDE_ENCRYPTIONS(getMapKeys(minifi::aws::s3::SERVER_SIDE_ENCRYPTION_MAP));
+
+const core::Property PutS3Object::ObjectKey(
+ core::PropertyBuilder::createProperty("Object Key")
+ ->withDescription("The key of the S3 object")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${filename}")
+ ->build());
+const core::Property PutS3Object::Bucket(
+ core::PropertyBuilder::createProperty("Bucket")
+ ->withDescription("The S3 bucket")
+ ->isRequired(true)
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ContentType(
+ core::PropertyBuilder::createProperty("Content Type")
+ ->withDescription("Sets the Content-Type HTTP header indicating the type of content stored in "
+ "the associated object. The value of this header is a standard MIME type. "
+ "If no content type is provided the default content type "
+ "\"application/octet-stream\" will be used.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("application/octet-stream")
+ ->build());
+const core::Property PutS3Object::AccessKey(
+ core::PropertyBuilder::createProperty("Access Key")
+ ->withDescription("AWS account access key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::SecretKey(
+ core::PropertyBuilder::createProperty("Secret Key")
+ ->withDescription("AWS account secret key")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::CredentialsFile(
+ core::PropertyBuilder::createProperty("Credentials File")
+ ->withDescription("Path to a file containing AWS access key and secret key in properties file format. Properties used: accessKey and secretKey")
+ ->build());
+const core::Property PutS3Object::AWSCredentialsProviderService(
+ core::PropertyBuilder::createProperty("AWS Credentials Provider service")
+ ->withDescription("The name of the AWS Credentials Provider controller service that is used to obtain AWS credentials.")
+ ->build());
+const core::Property PutS3Object::StorageClass(
+ core::PropertyBuilder::createProperty("Storage Class")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("Standard")
+ ->withAllowableValues<std::string>(PutS3Object::STORAGE_CLASSES)
+ ->withDescription("AWS S3 Storage Class")
+ ->build());
+const core::Property PutS3Object::Region(
+ core::PropertyBuilder::createProperty("Region")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>(region::US_WEST_2)
+ ->withAllowableValues<std::string>(PutS3Object::REGIONS)
+ ->withDescription("AWS Region")
+ ->build());
+const core::Property PutS3Object::CommunicationsTimeout(
+ core::PropertyBuilder::createProperty("Communications Timeout")
+ ->isRequired(true)
+ ->withDefaultValue<core::TimePeriodValue>("30 sec")
+ ->withDescription("")
+ ->build());
+const core::Property PutS3Object::FullControlUserList(
+ core::PropertyBuilder::createProperty("FullControl User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Full Control for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.full.users}")
+ ->build());
+const core::Property PutS3Object::ReadPermissionUserList(
+ core::PropertyBuilder::createProperty("Read Permission User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have Read Access for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.read.users}")
+ ->build());
+const core::Property PutS3Object::ReadACLUserList(
+ core::PropertyBuilder::createProperty("Read ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to read the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.readacl.users}")
+ ->build());
+const core::Property PutS3Object::WriteACLUserList(
+ core::PropertyBuilder::createProperty("Write ACL User List")
+ ->withDescription("A comma-separated list of Amazon User ID's or E-mail addresses that specifies who should have permissions to change the Access Control List for an object.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.writeacl.users}")
+ ->build());
+const core::Property PutS3Object::CannedACL(
+ core::PropertyBuilder::createProperty("Canned ACL")
+ ->withDescription("Amazon Canned ACL for an object. Allowed values: BucketOwnerFullControl, BucketOwnerRead, AuthenticatedRead, PublicReadWrite, PublicRead, Private, AwsExecRead; will be ignored if any other ACL/permission property is specified.")
+ ->supportsExpressionLanguage(true)
+ ->withDefaultValue<std::string>("${s3.permissions.cannedacl}")
+ ->build());
+const core::Property PutS3Object::EndpointOverrideURL(
+ core::PropertyBuilder::createProperty("Endpoint Override URL")
+ ->withDescription("Endpoint URL to use instead of the AWS default including scheme, host, "
+ "port, and path. The AWS libraries select an endpoint URL based on the AWS "
+ "region, but this property overrides the selected endpoint URL, allowing use "
+ "with other S3-compatible endpoints.")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ServerSideEncryption(
+ core::PropertyBuilder::createProperty("Server Side Encryption")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("None")
+ ->withAllowableValues<std::string>(PutS3Object::SERVER_SIDE_ENCRYPTIONS)
+ ->withDescription("Specifies the algorithm used for server side encryption.")
+ ->build());
+const core::Property PutS3Object::ProxyHost(
+ core::PropertyBuilder::createProperty("Proxy Host")
+ ->withDescription("Proxy host name or IP")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPort(
+ core::PropertyBuilder::createProperty("Proxy Port")
+ ->withDescription("The port number of the proxy host")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyUsername(
+ core::PropertyBuilder::createProperty("Proxy Username")
+ ->withDescription("Username to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+const core::Property PutS3Object::ProxyPassword(
+ core::PropertyBuilder::createProperty("Proxy Password")
+ ->withDescription("Password to set when authenticating against proxy")
+ ->supportsExpressionLanguage(true)
+ ->build());
+
+const core::Relationship PutS3Object::Success("success", "FlowFiles are routed to success relationship");
+const core::Relationship PutS3Object::Failure("failure", "FlowFiles are routed to failure relationship");
+
+void PutS3Object::initialize() {
+ // Set the supported properties
+ std::set<core::Property> properties;
+ properties.insert(ObjectKey);
+ properties.insert(Bucket);
+ properties.insert(ContentType);
+ properties.insert(AccessKey);
+ properties.insert(SecretKey);
+ properties.insert(CredentialsFile);
+ properties.insert(AWSCredentialsProviderService);
+ properties.insert(StorageClass);
+ properties.insert(Region);
+ properties.insert(CommunicationsTimeout);
+ properties.insert(FullControlUserList);
+ properties.insert(ReadPermissionUserList);
+ properties.insert(ReadACLUserList);
+ properties.insert(WriteACLUserList);
+ properties.insert(CannedACL);
+ properties.insert(EndpointOverrideURL);
+ properties.insert(ServerSideEncryption);
+ properties.insert(ProxyHost);
+ properties.insert(ProxyPort);
+ properties.insert(ProxyUsername);
+ properties.insert(ProxyPassword);
+ setSupportedProperties(properties);
+ // Set the supported relationships
+ std::set<core::Relationship> relationships;
+ relationships.insert(Failure);
+ relationships.insert(Success);
+ setSupportedRelationships(relationships);
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromControllerService(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string service_name;
+ if (context->getProperty(AWSCredentialsProviderService.getName(), service_name) && !service_name.empty()) {
+ std::shared_ptr<core::controller::ControllerService> service = context->getControllerService(service_name);
+ if (nullptr != service) {
+ auto aws_credentials_service = std::static_pointer_cast<minifi::aws::controllers::AWSCredentialsService>(service);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(aws_credentials_service->getAWSCredentials());
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromProperties(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ std::string access_key;
+ context->getProperty(AccessKey, access_key, flow_file);
+ std::string secret_key;
+ context->getProperty(SecretKey, secret_key, flow_file);
+ if (!access_key.empty() && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentialsFromFile(const std::shared_ptr<core::ProcessContext> &context) const {
+ std::string credential_file;
+ if (context->getProperty(CredentialsFile.getName(), credential_file) && !credential_file.empty()) {
+ auto properties = std::make_shared<minifi::Properties>();
+ properties->loadConfigureFile(credential_file.c_str());
+ std::string access_key;
+ std::string secret_key;
+ if (properties->get("accessKey", access_key) && !access_key.empty() && properties->get("secretKey", secret_key) && !secret_key.empty()) {
+ Aws::Auth::AWSCredentials creds(access_key, secret_key);
+ return minifi::utils::make_optional<Aws::Auth::AWSCredentials>(creds);
+ }
+ }
+ return minifi::utils::nullopt;
+}
+
+minifi::utils::optional<Aws::Auth::AWSCredentials> PutS3Object::getAWSCredentials(
+ const std::shared_ptr<core::ProcessContext> &context,
+ const std::shared_ptr<core::FlowFile> &flow_file) const {
+ auto prop_cred = getAWSCredentialsFromProperties(context, flow_file);
+ if (prop_cred) {
+ logger_->log_info("AWS Credentials successfully set from properties");
+ return prop_cred.value();
+ }
+
+ auto file_cred = getAWSCredentialsFromFile(context);
+ if (file_cred) {
+ logger_->log_info("AWS Credentials successfully set from file");
+ return file_cred.value();
+ }
+
+ auto service_cred = getAWSCredentialsFromControllerService(context);
+ if (service_cred) {
+ logger_->log_info("AWS Credentials successfully set from controller service");
+ return service_cred.value();
+ }
+
+ return minifi::utils::nullopt;
+}
+
+void PutS3Object::fillUserMetadata(const std::shared_ptr<core::ProcessContext> &context) {
+ const auto &dynamic_prop_keys = context->getDynamicPropertyKeys();
+ bool first_property = true;
+ for (const auto &prop_key : dynamic_prop_keys) {
+ std::string prop_value = "";
+ if (context->getDynamicProperty(prop_key, prop_value) && !prop_value.empty()) {
+ logger_->log_debug("PutS3Object: DynamicProperty: [%s] -> [%s]", prop_key, prop_value);
+ put_s3_request_params_.user_metadata_map.emplace(prop_key, prop_value);
+ if (first_property) {
+ user_metadata_ = prop_key + "=" + prop_value;
+ first_property = false;
+ } else {
+ user_metadata_ += "," + prop_key + "=" + prop_value;
+ }
+ }
+ }
+ logger_->log_debug("PutS3Object: User metadata [%s]", user_metadata_);
+}
+
+bool PutS3Object::setProxy(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::FlowFile> &flow_file) {
+ aws::s3::ProxyOptions proxy;
+ context->getProperty(ProxyHost, proxy.host, flow_file);
+ std::string port_str;
+ if (context->getProperty(ProxyPort, port_str, flow_file) && !port_str.empty() && !core::Property::StringToInt(port_str, proxy.port)) {
+ logger_->log_error("Proxy port invalid");
+ return false;
+ }
+ context->getProperty(ProxyUsername, proxy.username, flow_file);
+ context->getProperty(ProxyPassword, proxy.password, flow_file);
+ if (!proxy.host.empty()) {
+ s3_wrapper_->setProxy(proxy);
+ logger_->log_info("Proxy for PutS3Object was set.");
+ }
+ return true;
+}
+
+void PutS3Object::onSchedule(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+ if (!context->getProperty(Bucket.getName(), put_s3_request_params_.bucket) || put_s3_request_params_.bucket.empty()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Bucket property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Bucket [%s]", put_s3_request_params_.bucket);
+
+ if (!context->getProperty(StorageClass.getName(), put_s3_request_params_.storage_class)
+ || put_s3_request_params_.storage_class.empty()
+ || STORAGE_CLASSES.find(put_s3_request_params_.storage_class) == STORAGE_CLASSES.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Storage Class property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Storage Class [%s]", put_s3_request_params_.storage_class);
+
+ std::string value;
+ if (!context->getProperty(Region.getName(), value) || value.empty() || REGIONS.find(value) == REGIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Region property missing or invalid");
+ }
+ s3_wrapper_->setRegion(value);
+ logger_->log_debug("PutS3Object: Region [%s]", value);
+
+ uint64_t timeout_val;
+ if (context->getProperty(CommunicationsTimeout.getName(), value) && !value.empty() && core::Property::getTimeMSFromString(value, timeout_val)) {
+ s3_wrapper_->setTimeout(timeout_val);
+ logger_->log_debug("PutS3Object: Communications Timeout [%d]", timeout_val);
+ } else {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Communications Timeout missing or invalid");
+ }
+
+ if (!context->getProperty(ServerSideEncryption.getName(), put_s3_request_params_.server_side_encryption)
+ || put_s3_request_params_.server_side_encryption.empty()
+ || SERVER_SIDE_ENCRYPTIONS.find(put_s3_request_params_.server_side_encryption) == SERVER_SIDE_ENCRYPTIONS.end()) {
+ throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Server Side Encryption property missing or invalid");
+ }
+ logger_->log_debug("PutS3Object: Server Side Encryption [%s]", put_s3_request_params_.server_side_encryption);
+
+ fillUserMetadata(context);
+}
+
+std::string PutS3Object::parseAccessControlList(const std::string &comma_separated_list) const {
+ std::string result_list;
+ bool is_first = true;
+ for (const auto& user : minifi::utils::StringUtils::split(comma_separated_list, ",")) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ result_list += ", ";
+ }
+
+ auto trimmed_user = minifi::utils::StringUtils::trim(user);
+ static const std::regex email_pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
+ if (std::regex_match(trimmed_user, email_pattern)) {
Review comment:
In this case as we only accept email addresses and AWS ids so currently I only checked if it is an email address or not in which case we assume it is an AWS id. The canonical user id is not really specified it is only defined [here](https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html) as "An alpha-numeric identifier, such as 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be, that is an obfuscated form of the AWS account ID.". It seems to me it is a 64 long hex string literal, but I am not sure if the length is fixed as there are some questions about it [here for example](https://forums.aws.amazon.com/thread.jspa?threadID=286019).
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] adamdebreceni commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
adamdebreceni commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r501503306
##########
File path: extensions/aws/processors/PutS3Object.h
##########
@@ -0,0 +1,195 @@
+/**
+ * @file PutS3Object.h
+ * PutS3Object class declaration
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <utility>
+#include <vector>
+#include <memory>
+#include <string>
+
+#include "aws/core/auth/AWSCredentialsProvider.h"
+
+#include "S3Wrapper.h"
+#include "core/Property.h"
+#include "core/Processor.h"
+#include "core/logging/Logger.h"
+#include "core/logging/LoggerConfiguration.h"
+#include "utils/OptionalUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace processors {
+
+namespace region {
+
+ constexpr const char *US_GOV_WEST_1 = "us-gov-west-1";
+ constexpr const char *US_EAST_1 = "us-east-1";
+ constexpr const char *US_EAST_2 = "us-east-2";
+ constexpr const char *US_WEST_1 = "us-west-1";
+ constexpr const char *US_WEST_2 = "us-west-2";
+ constexpr const char *EU_WEST_1 = "eu-west-1";
+ constexpr const char *EU_WEST_2 = "eu-west-2";
+ constexpr const char *EU_CENTRAL_1 = "eu-central-1";
+ constexpr const char *AP_SOUTH_1 = "ap-south-1";
+ constexpr const char *AP_SOUTHEAST_1 = "ap-southeast-1";
+ constexpr const char *AP_SOUTHEAST_2 = "ap-southeast-2";
+ constexpr const char *AP_NORTHEAST_1 = "ap-northeast-1";
+ constexpr const char *AP_NORTHEAST_2 = "ap-northeast-2";
+ constexpr const char *SA_EAST_1 = "sa-east-1";
+ constexpr const char *CN_NORTH_1 = "cn-north-1";
+ constexpr const char *CA_CENTRAL_1 = "ca-central-1";
+ constexpr const char *AF_SOUTH_1 = "af-south-1";
+ constexpr const char *AP_EAST_1 = "ap-east-1";
+ constexpr const char *AP_NORTHEAST_3 = "ap-northeast-3";
+ constexpr const char *CN_NORTHWEST_1 = "cn-northwest-1";
+ constexpr const char *EU_SOUTH_1 = "eu-south-1";
+ constexpr const char *EU_WEST_3 = "eu-west-3";
+ constexpr const char *EU_NORTH_1 = "eu-north-1";
+ constexpr const char *ME_SOUTH_1 = "me-south-1";
+ constexpr const char *US_GOV_EAST_1 = "us-gov-east-1";
Review comment:
1. I think we do not indent inside namespaces (although I do not think that it hinders readability here)
2. also do you think that putting the regions in lexicographical order could help us add-remove (or diff) the list in the future?
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [nifi-minifi-cpp] lordgamez commented on a change in pull request #915: MINIFICPP-1376 Create PutS3Object processor
Posted by GitBox <gi...@apache.org>.
lordgamez commented on a change in pull request #915:
URL: https://github.com/apache/nifi-minifi-cpp/pull/915#discussion_r512663081
##########
File path: extensions/aws/utils/AWSSdkLogger.cpp
##########
@@ -0,0 +1,105 @@
+/**
+ * @file AWSSdkLogger.cpp
+ * AWSSdkLogger class implementation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AWSSdkLogger.h"
+
+#include "aws/core/utils/logging/LogLevel.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace aws {
+namespace utils {
+
+Aws::Utils::Logging::LogLevel AWSSdkLogger::GetLogLevel() const {
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::trace))
+ return Aws::Utils::Logging::LogLevel::Trace;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::debug))
+ return Aws::Utils::Logging::LogLevel::Debug;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::info))
+ return Aws::Utils::Logging::LogLevel::Info;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::warn))
+ return Aws::Utils::Logging::LogLevel::Warn;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::err))
+ return Aws::Utils::Logging::LogLevel::Error;
+ if (logger_->should_log(minifi::core::logging::LOG_LEVEL::critical))
+ return Aws::Utils::Logging::LogLevel::Fatal;
+ return Aws::Utils::Logging::LogLevel::Off;
+}
+
+void AWSSdkLogger::Log(Aws::Utils::Logging::LogLevel log_level, const char* tag, const char* format_str, ...) {
+ switch (log_level) {
+ case Aws::Utils::Logging::LogLevel::Trace:
+ logger_->log_trace("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Debug:
+ logger_->log_debug("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Info:
+ logger_->log_info("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Warn:
+ logger_->log_warn("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Error:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ case Aws::Utils::Logging::LogLevel::Fatal:
+ logger_->log_error("[%s] %s", tag, format_str);
+ break;
+ default:
+ break;
+ }
+}
+
+void AWSSdkLogger::LogStream(Aws::Utils::Logging::LogLevel log_level, const char* tag, const Aws::OStringStream &message_stream) {
+ switch (log_level) {
Review comment:
Fixed in [e994558](https://github.com/apache/nifi-minifi-cpp/pull/915/commits/e994558ba8904488b0e3489d51236fb7d4e31ce1)
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org