Fetch an AWS S3 object to use in Rekognition when uploaded via Carrierwave - ruby-on-rails-3

I have a Gallery and Attachment models. A gallery has_many attachments and essentially all attachments are images referenced in the ':content' attribute of Attachment.
The images are uploaded using Carrierwave gem and are stored in Aws S3 via fog-aws gem. This works OK. However, I'd like to conduct image recognition to the uploaded images with Amazon Rekognition.
I've installed aws-sdk gem and I'm able to instantiate Rekognition without a problem until I call the detect_labels method at which point I have been unable to use my attached images as arguments of this method.
So fat I've tried:
#attachement = Attachment.first
client = Aws::Rekognition::Client.new
resp = client.detect_labels(
image: #attachment
)
# I GET expected params[:image] to be a hash... and got class 'Attachment' instead
I've tried using:
client.detect_labels( image: { #attachment })
client.detect_labels( image: { #attachment.content.url })
client.detect_labels( image: { #attachment.content })
All with the same error. I wonder how can I fetch the s3 object form #attachment and, even if I could do that, how could I use it as an argument in detect_labels.
I've tried also fetching directly the s3 object to try this last bit:
s3 = AWS:S3:Client.new
s3_object = s3.list_objects(bucket: 'my-bucket-name').contents[0]
# and then
client.detect_labels( image: { s3_object })
Still no success...
Any tips?

I finally figured out what was the problem, helped by the following AWS forum
The 'Image' hash key takes as a value an object that must be named 's3_object' and which subsequently needs only the S3 bucket name and the path of the file to be processed. As a reference see the correct example below:
client = Aws::Rekognition::Client.new
resp = client.detect_labels(
image:
{ s3_object: {
bucket: "my-bucket-name",
name: #attachment.content.path,
},
}
)
# #attachment.content.path => "uploads/my_picture.jpg"

Related

How to set custom metadata when uploading image to s3 using carrierwave / carrierwave-aws?

I'm using Carrierwave with the carrierwave-aws gem to upload images to s3 from my rails app.
The images upload fine, but I need to set custom metadata on the S3 objects as described here: https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#object-metadata
I don't see anything online about this. This SO post: How to upload custom S3 metadata with Carrierwave shows how to do it using the fog gem, i.e.:
config.fog_attributes = {
"x-amz-meta-test": "x-amz-meta-test"
}
but I am not using fog. Is there a way to do this using carrierwave-aws instead?
I tried
config.aws_attributes = {
"x-amz-meta-test": "x-amz-meta-test"
}
but it did not work.
Thanks!
Figured it out:
def aws_attributes
{ metadata: { 'x-amz-meta-testing': 'test' }}
end

How to upload file to AWS S3 using AWS AppSync

Following this docs/tutorial in AWS AppSync Docs.
It states:
With AWS AppSync you can model these as GraphQL types. If any of your mutations have a variable with bucket, key, region, mimeType and localUri fields, the SDK will upload the file to Amazon S3 for you.
However, I cannot make my file to upload to my s3 bucket. I understand that tutorial missing a lot of details. More specifically, the tutorial does not say that the NewPostMutation.js needs to be changed.
I changed it the following way:
import gql from 'graphql-tag';
export default gql`
mutation AddPostMutation($author: String!, $title: String!, $url: String!, $content: String!, $file: S3ObjectInput ) {
addPost(
author: $author
title: $title
url: $url
content: $content
file: $file
){
__typename
id
author
title
url
content
version
}
}
`
Yet, even after I have implemented these changes, the file did not get uploaded...
There's a few moving parts under the hood you need to make sure you have in place before this "just works" (TM). First of all, you need to make sure you have an appropriate input and type for an S3 object defined in your GraphQL schema
enum Visibility {
public
private
}
input S3ObjectInput {
bucket: String!
region: String!
localUri: String
visibility: Visibility
key: String
mimeType: String
}
type S3Object {
bucket: String!
region: String!
key: String!
}
The S3ObjectInput type, of course, is for use when uploading a new file - either by way of creating or updating a model within which said S3 object metadata is embedded. It can be handled in the request resolver of a mutation via the following:
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id),
},
#set( $attribs = $util.dynamodb.toMapValues($ctx.args.input) )
#set( $file = $ctx.args.input.file )
#set( $attribs.file = $util.dynamodb.toS3Object($file.key, $file.bucket, $file.region, $file.version) )
"attributeValues": $util.toJson($attribs)
}
This is making the assumption that the S3 file object is a child field of a model attached to a DynamoDB datasource. Note that the call to $utils.dynamodb.toS3Object() sets up the complex S3 object file, which is a field of the model with a type of S3ObjectInput. Setting up the request resolver in this way handles the upload of a file to S3 (when all the credentials are set up correctly - we'll touch on that in a moment), but it doesn't address how to get the S3Object back. This is where a field level resolver attached to a local datasource becomes necessary. In essence, you need to create a local datasource in AppSync and connect it to the model's file field in the schema with the following request and response resolvers:
## Request Resolver ##
{
"version": "2017-02-28",
"payload": {}
}
## Response Resolver ##
$util.toJson($util.dynamodb.fromS3ObjectJson($context.source.file))
This resolver simply tells AppSync that we want to take the JSON string that is stored in DynamoDB for the file field of the model and parse it into an S3Object - this way, when you do a query of the model, instead of returning the string stored in the file field, you get an object containing the bucket, region, and key properties that you can use to build a URL to access the S3 Object (either directly via S3 or using a CDN - that's really dependent on your configuration).
Do make sure you have credentials set up for complex objects, however (told you I'd get back to this). I'll use a React example to illustrate this - when defining your AppSync parameters (endpoint, auth, etc.), there is an additional property called complexObjectCredentials that needs to be defined to tell the client what AWS credentials to use to handle S3 uploads, e.g.:
const client = new AWSAppSyncClient({
url: AppSync.graphqlEndpoint,
region: AppSync.region,
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
},
complexObjectsCredentials: () => Auth.currentCredentials(),
});
Assuming all of these things are in place, S3 uploads and downloads via AppSync should work.
Just to add to the discussion. For mobile clients, amplify (or if doing from aws console) will encapsulate mutation calls into an object. The clients won't auto upload if the encapsulation exists. So you can modify the mutation call directly in aws console so that the upload file : S3ObjectInput is in the calling parameters. This was happening the last time I tested (Dec 2018) following the docs.
You would change to this calling structure:
type Mutation {
createRoom(
id: ID!,
name: String!,
file: S3ObjectInput,
roomTourId: ID
): Room
}
Instead of autogenerated calls like:
type Mutation {
createRoom(input: CreateRoomInput!): Room
}
input CreateRoomInput {
id: ID
name: String!
file: S3ObjectInput
}
Once you make this change both iOS and Android will happily upload your content if you do what #hatboyzero has outlined.
[Edit] I did a bit of research, supposedly this has been fixed in 2.7.3 https://github.com/awslabs/aws-mobile-appsync-sdk-android/issues/11. They likely addressed iOS but I didn't check.

Correct code to upload local file to S3 proxy of API Gateway

I created an API function to work with S3. I imported the template swagger. After deployment, I tested with a Node.js project by the npm module aws-api-gateway-client.
It works well with: get bucket lists, get bucket info, get one item, put a bucket, put a plain text object, however I am blocked with put a binary file.
firstly, I ensure ACL is allowed with all permissions on S3. secondly, binary support also added
image/gif
application/octet-stream
The code snippet is as below. The behaviors are:
1) after invokeAPI, the callback function is never hit, after sometime, the Node.js project did not respond. no any error message. The file size (such as an image) is very small.
2) with only two times, the uploading seemed to work, but the result file size is bigger (around 2M bigger) than the original file, so the file is corrupt.
Could you help me out? Thank you!
var filepathname = './items/';
var filename = 'image1.png';
fs.stat(filepathname+filename, function (err, stats) {
var fileSize = stats.size ;
fs.readFile(filepathname+filename,'binary',function(err,data){
var len = data.length;
console.log('file len' + len);
var pathTemplate = '/my-test-bucket/' +filename ;
var method = 'PUT';
var params = {
folder: '',
item:''
};
var additionalParams = {
headers: {
'Content-Type': 'application/octet-stream',
//'Content-Type': 'image/gif',
'Content-Length': len
}
};
var result1 = apigClient.invokeApi(params,pathTemplate,method,additionalParams,data)
.then(function(result){
//never hit :(
console.log(result);
}).catch( function(result){
//never hit :(
console.log(result);
});;
});
});
We encountered the same problem. API Gateway is meant for limited data (10MB as of now), limits shown here,
http://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html
Self Signed URL to S3:
Create an S3 self signed URL for POST from the lambda or the endpoint where you are trying to post.
How do I put object to amazon s3 using presigned url?
Now POST the image directly to S3.
Presigned POST:
Apart from posting the image if you want to post additional properties, you can post it in multi-form format as well.
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#createPresignedPost-property
If you want to process the file after delivering to S3, you can create a trigger from S3 upon creation and process with your Lambda or anypoint that need to process.
Hope it helps.

image upload to amazon s3, need to be binary?

i have never used image upload before, don't really understand the mechanism .
Trying to upload image from backend, to amazon s3 buckets. does the image have to be converted to binary in this case?
the following code is what i got from amazon tutorial (can't find a good document&tutorial of how to use their api..)
what i need to add on the code to upload image ?
var params = {Bucket:bucketName, Key: keyName, Body: "?"};
s3.putObject(params, function(err,data){
if(err)
console.log(err)
else
console.log("Successfully uploaded data to " + bucketName + "/" + keyName);
})
there's alternative such as readasurl, to convert the image into a string, when the image is small. so.. no need to even use Amazon S3, which is so complicated...

Set content type in S3 when attaching via Paperclip 4?

I'm trying to attach CSV files to a Rails3 model using paperclip 4.1.1, but I'm having trouble getting the content-type as reported by S3 to be text/csv (instead I am getting text/plain). When I subsequently download the file from S3, the extension is getting changed to match the content-type instead of preserving the original extension (so test.csv is downloaded as test.txt).
From what I can see, when you upload a file, the FileAdapter will cache the content-type on creation with whatever value was determined by the ContentTypeDetector (which calls file -b --mime filename). Unfortunately, CSV files return text/plain which makes sense, as how can you really distinguish this? Attempts to set the content-type with attachment.instance_write(:content_type, 'text/csv') only set the value in the model and do not affect what gets written to S3.
FileAdapter's content_type initialized here: https://github.com/thoughtbot/paperclip/blob/v4.0/lib/paperclip/io_adapters/file_adapter.rb#L14
Call which creates that io_adapter:
https://github.com/thoughtbot/paperclip/blob/v4.0/lib/paperclip/attachment.rb#L98
I really have a generic upload here (so I can't hard-code the content type in the S3 headers definition in has_attached_file), and I don't really want the content-type spoofing protection. Any ideas/suggestions? I would prefer not to downgrade to 3.5 because it would mean just delaying the pain, but if that's the only way, I'll entertain it...
If you are using fog then you can do something like this:
has_attached_file :report,
fog_file: lambda { |attachment|
{
content_type: 'text/csv',
content_disposition: "attachment; filename=#{attachment.original_filename}",
}
}
If you are using Amazon S3 as your storage provider, then something like this should work:
has_attached_file :report
s3_headers: lambda { |attachment|
{
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=#{attachment.original_filename}",
}
}
Had this problem just recently and both the post process and the lambda don't work so did a work around. Same with others observation, the values of the attachment is empty when calling the s3 lambda headers.
add this line to the model
attr_accessor :tmp_content_type, :tmp_file_name
override the file assignment method so we could get the file info and store it for later use
def file=(f)
set_tmp_values(f.path)
file.assign(f)
end
def set_tmp_values(file_path)
self.tmp_file_name = File.basename(file_path)
self.tmp_content_type = MIME::Types.type_for(file_path).first.content_type
end
Use the temp vars
:s3_headers => lambda { |attachment|
{
'Content-Type' => attachment.tmp_content_type,
"Content-Disposition" => "attachment; filename=\"# {attachment.tmp_file_name}\""
}
}