Amplify-cli: How to upload S3Objects with GraphQL AppSync (iOS)

Created on 25 Jan 2019  路  15Comments  路  Source: aws-amplify/amplify-cli

* Which Category is your question related to? *
Uploading S3Objects with GraphQL AppSync and iOS
I am not able to store complex objects (in my case an image) to S3.

* What AWS Services are you utilizing? *
I use Amplify iOS with GraphQL Transform and Codegen to generate a Swift api for AppSync.

* Provide additional details e.g. code snippets *

The docs say:

The GraphQL Transform, Amplify CLI, and Amplify Library make it simple to add complex object support with Amazon S3 to an application.

The AppSync SDKs and Amplify library handle uploading the files to S3 transparently.

But I can not make it work. I do not even know if this is caused by poor documentation, or by lacking support for iOS, or by bugs.
I will walk you through the steps I made and the issues I encountered.

My setup is best described by the output of the amplify status command:

| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Api      | myservice  | No Change | awscloudformation |
| Auth     | mycognito | No Change | awscloudformation |
| Storage  | mystorage   | No Change | awscloudformation |

So far, so good. The api is working, I use the generated Swift api, my model objects are stored in DynamoDB as expected.
Authorisaton via user pools works too.

Here's my schema:

type User 
  @model   
  @auth(
    rules: [
      { 
        allow: owner, 
        ownerField: "id", 
        identityField: "sub", 
        mutations: [create, update, delete],
        queries: null
      }
    ]
  ) 
{
  id: ID!
  name: String
  about: String
  createdAt: String!
  updatedAt: String! 
}

Now I want to add an image property to the User. I follow the iOS tutorial and add an S3Object. The new schema:

type User 
  @model   
  @auth(
    rules: [
      { 
        allow: owner, 
        ownerField: "id", 
        identityField: "sub", 
        mutations: [create, update, delete],
        queries: null
      }
    ]
  ) 
{
  id: ID!
  name: String
  about: String
  createdAt: String!
  updatedAt: String! 
  image: S3Object
}

type S3Object {
    bucket: String!
    region: String!
    key: String!
}

After amplify push I have a new Swift api, and a new generated schema.
The generated schema now has an auto generated S3ObjectInput type

input S3ObjectInput {
    bucket: String!
    region: String!
    key: String!
}

That's great, but there is no place to put a path to a local file to be uploaded to S3. So how can the AppSync SDKs and Amplify library handle uploading the file to S3 transparently?

So, I study the React tutorial that is part of the iOS documentation (why is there no iOS documentation or Swift sample code?)
I notice that the S3ObjectInput in the React tutorial has 2 more properties: mimeType and localUri

As a workaround I decide to define the S3ObjectInput in my own scheme as follows:

input S3ObjectInput {
    bucket: String!
    region: String!
    key: String!
    mimeType: String!
    localUri: String!
}

I check the generated scheme and it looks good. Looks like my 'educated guess' (undocumented) has the potential to solve the issue.

So next step: Upload an image via the newly generated Swift api. The swift api now has a nice S3ObjectInput class where I can add the mimeType and the localUri.

Here's a snippet of the Swift code

       let s3ObjectInput = S3ObjectInput(bucket: "xxxxxxxxxx",
                                          region: "eu-west-1",
                                          key: userIdentity,
                                          mimeType: contentType,
                                          localUri: localUri.absoluteString)
        let input = UpdateUserInput(id: userIdentity,
                                    name: nil,
                                    about:nil,
                                    createdAt: "createdAt",
                                    updatedAt: "updatedAt",
                                    image: s3ObjectInput )

        let updateUserMutation = UpdateUserMutation(input: input)
       .....
            service.appSyncClient?.perform(mutation: updateUserMutation) { (result, error) in
        ......
            }
        }

I execute this code. The result:

No transparent upload of the image to S3, no error logs. Does the iOS AppSync client even support auto upload? Or do I miss anything?

The entire S3ObjectInput object is stored in DynamoDB. That includes the localUri. But I do not want the localUri to be stored in DynamoDB at all. Shouldn't it be stripped? In related issue #395 I read (for the aws js client):

But localUri and mimeType in S3Object in the db will always not be set as the aws js client will delete these

At this point I've given up my efforts. It involves too much guesswork and trial and error.

The concept of auto uploading to S3 is very nice, but the feature really needs working sample code and a dedicated tutorial to be of any use. Until then I consider it a waste of time (sorry).

code-gen enhancement

Most helpful comment

@helloniklas I gave up, it takes too long for this to be solved. It's been 4 months since my initial bug report. There have been partial fixes pretty quickly, but the issue you reported https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues/4 is a showstopper for the total solution to work.

All 15 comments

Thank you for your feedback. We are aware of this issue and we are working on updating codegen to support S3Objects

Feature is release and available in Amplify CLI 1.0 and above

It doesn't work yet.

I have updated to Amplify 1.1.0. The generated Swift code for S3ObjectInput is now correct (it has mimeType and localUri properties added. The generated schema for S3ObjectInput is

input S3ObjectInput {
    bucket: String!
    region: String!
    key: String!
}

But now I receive a GraphQLError when I perform the UpdateUserMutation:

The variables input contains a field name 'localUri' that is not defined for input object type 'S3ObjectInput'

My guess is that localUri and mimeType must be removed before sending them to the AppSync service. The upload to S3 does not work either, but that might be a side effect of the error above.

I have updated to Amplify CLI 1.1.7 with aws-mobile-appsync-sdk-ios 2.10.2. and completely recreated the appsync service from scratch. This solves the issue mentioned in my previous comment, but now I am stuck on issue https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues/4

I'm also having the same problem. Any luck with this yet?

No, there has been no response since Feb 14, I am totally stuck on this.

Would you mind sharing a sample project?

I am confused about this issue as well. I have no idea how to include the --add-s3-wrapper switch to codegen. I used amplify add api to create the API, but there was no option to add this --add-s3-wrapper switch.

How I got around this was to copy and paste the code from here into my API.swift file and all was fine.

What is the proper way of doing this using amplify CLI?

How I got around this was to copy and paste the code from here into my API.swift file and all was fine.
What is the proper way of doing this using amplify CLI?

If your schema has an input type with the following fields bucket, key, region codegen should automatically adds the S3Object

@yuth how do you then initiate the AppSync client as:

            let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: AWSAppSyncServiceConfig(), userPoolsAuthProvider: AWSMobileClient.sharedInstance(), s3ObjectManager: AWSS3TransferUtility.default())

won't work, giving Cannot convert value of type 'AWSS3TransferUtility' to expected argument type 'AWSS3ObjectManager?'

@peterfennema did you manage to get this all to work in the end?

@helloniklas I gave up, it takes too long for this to be solved. It's been 4 months since my initial bug report. There have been partial fixes pretty quickly, but the issue you reported https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues/4 is a showstopper for the total solution to work.

I beleive we've fixed this issue as a part of #1482
Here are the docs for the same - https://aws-amplify.github.io/docs/cli/graphql#s3-objects
Please feel free to comment on this thread if you're still stuck on this issue.

@kaustavghosh06 great, but this is still a bit confusing, The docs for iOS at https://aws-amplify.github.io/docs/ios/storage implies you have to write custom resolvers manually in the AppSync section. Is this now all done automatically? If so, the docs for iOS needs to be updated. Last time I attempted all this it did not work.

@kaustavghosh06 I can confirm that just updating the graphql schema to include reference to s3object with just bucket, region, and key like this >

type User @model
{
id: ID!
name: String
about: String
createdAt: String!
updatedAt: String!
image: S3Object
}

input S3ObjectInput {
bucket: String!
region: String!
key: String!
}
as written here: https://aws-amplify.github.io/docs/cli/graphql#s3-objects then doing amplify push creates the mutation input and mutation functions in the project and the cloud. However, calling the mutation does not save the file in S3 and does not result in any error message. I used Swift 5, iOS 12.4 in simulator. Do you have a successful sample that we can take a look at?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jeanpaulcozzatti picture jeanpaulcozzatti  路  3Comments

MageMasher picture MageMasher  路  3Comments

darrentarrant picture darrentarrant  路  3Comments

nicksmithr picture nicksmithr  路  3Comments

adriatikgashi picture adriatikgashi  路  3Comments