Amazon Dynomo DB: BatchPutItem из-за APPSYNC_ASSUME_ROLE и ошибки несоответствия типа

Я создаю приложение React с помощью AWS Amplify. Я использую пулы пользователей Cognito для аутентификации и бэкэнд GraphQL AppSync для моего бэкэнда.

Я пытаюсь написать собственный преобразователь для пакетной мутации. Вот схема, которую я использую:

type Todo @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  title: String!
  description: String
  completed: Boolean
}

input CreateTodoInput {
  id: ID
  title: String!
  description: String
  completed: Boolean
}

type Mutation {
  batchAddTodos(todos: [CreateTodoInput]): [Todo]
}

Эта схема включает аутентификацию для GraphQL API с помощью пользовательских пулов Cognito.

Чтобы эта настраиваемая мутация работала, необходимо добавить настраиваемые преобразователи. Я изменил amplify/api/<your-api-name>/stacks/CustomResources.json, чтобы он содержал следующие ресурсы:

// Left everything as it was
 "Resources": {
    "EmptyResource": {
      "Type": "Custom::EmptyResource",
      "Condition": "AlwaysFalse"
    },
    "BatchAddTodosResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Ref": "AppSyncApiId"
        },
        "DataSourceName": "TodoTable",
        "TypeName": "Mutation",
        "FieldName": "batchAddTodos",
        "RequestMappingTemplateS3Location": {
          "Fn::Sub": [
            "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.req.vtl",
            {
              "S3DeploymentBucket": {
                "Ref": "S3DeploymentBucket"
              },
              "S3DeploymentRootKey": {
                "Ref": "S3DeploymentRootKey"
              }
            }
          ]
        },
        "ResponseMappingTemplateS3Location": {
          "Fn::Sub": [
            "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.res.vtl",
            {
              "S3DeploymentBucket": {
                "Ref": "S3DeploymentBucket"
              },
              "S3DeploymentRootKey": {
                "Ref": "S3DeploymentRootKey"
              }
            }
          ]
        }
      }
    }
  },
// ... more code that I didn't touch

Для настраиваемого преобразователя запросов я написал следующий шаблон:

#foreach($item in ${ctx.args.todos})
    ## [Start] Owner Authorization Checks **
    #set( $isOwnerAuthorized = false )
    ## Authorization rule: { allow: "owner", ownerField: "owner", identityField: "cognito:username" } **
    #set( $allowedOwners0 = $util.defaultIfNull($item.owner, null) )
    #set( $identityValue = $util.defaultIfNull($ctx.identity.claims.get("username"),
    $util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____")) )
    #if( $util.isList($allowedOwners0) )
        #foreach( $allowedOwner in $allowedOwners0 )
            #if( $allowedOwner == $identityValue )
                #set( $isOwnerAuthorized = true )
            #end
        #end
    #end
    #if( $util.isString($allowedOwners0) )
        #if( $allowedOwners0 == $identityValue )
            #set( $isOwnerAuthorized = true )
        #end
    #end
    #if( $util.isNull($allowedOwners0) && (! $item.containsKey("owner")) )
        $util.qr($item.put("owner", $identityValue))
        #set( $isOwnerAuthorized = true )
    #end
    ## [End] Owner Authorization Checks **

    ## [Start] Throw if unauthorized **
    #if( !($isStaticGroupAuthorized == true || $isDynamicGroupAuthorized == true || $isOwnerAuthorized
        == true) )
        $util.unauthorized()
    #end
    ## [End] Throw if unauthorized **
#end

#set($todosdata = [])
#foreach($item in ${ctx.args.todos})
    $util.qr($item.put("createdAt", $util.time.nowISO8601()))
    $util.qr($item.put("updatedAt", $util.time.nowISO8601()))
    $util.qr($item.put("__typename", "Todo"))
    $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId())))
    $util.qr($todosdata.add($util.dynamodb.toMapValues($item)))
#end
{
  "version": "2018-05-29",
  "operation": "BatchPutItem",
  "tables": {
      "TodoTable": $utils.toJson($todosdata)
  }
}

В первом цикле я пытался проверить, есть ли у пользователя доступ к создаваемым им задачам. Во втором цикле я добавил данные, которые добавляются резолверами, сгенерированными Amplify CLI. Сюда входят __typename, отметки времени и id.

После этого я прошу создать ресурсы. Я следил за этим руководством для код. Обратите внимание, что мне пришлось обновить версию до "2018-05-29". Код, сгенерированный Amplify CLI, обычно имеет версию "2017-02-28" (я не знаю, имеет ли это значение).

Я также написал следующее сопоставление для распознавателя ответов:

#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($ctx.result.data)

Это в основном указывает AppSync возвращать данные и ошибку для всех необработанных элементов.

Сначала я попытался сделать запрос с помощью React:

import API, { graphqlOperation } from '@aws-amplify/api';

// ... later

async function handleClick() {
  const todoFixtures = [
    { id: 1, title: 'Get groceries', description: '', completed: false },
    { id: 2, title: 'Go to the gym', description: 'Leg Day', completed: true }
  ];

  try {
    const input = { todos: prepareTodos(todoFixtures) };
    const res = await API.graphql(graphqlOperation(batchAddTodos, input));
    console.log(res);
  } catch (err) {
    console.log('error ', err);
  }
}

prepareTodos просто избавляется от id полей и устанавливает для пустых полей значение null (чтобы DynamoDB не кричал на меня). Код находится внизу, так как он не имеет отношения к делу.

Поскольку это не удалось, я попытался выполнить мутацию через консоль AppSync:

mutation add {
  batchAddTodos(todos: [
    {title: "Hello", description: "Test", completed: false}
  ]) {
    id title
  }
}

Но обе попытки выдают следующую ошибку:

{
  "data": {
    "batchAddTodos": null
  },
  "errors": [
    {
      "path": [
        "batchAddTodos"
      ],
      "data": null,
      "errorType": "DynamoDB:AmazonDynamoDBException",
      "errorInfo": null,
      "locations": [
        {
          "line": 32,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "User: arn:aws:sts::655817346595:assumed-role/Todo-role-naona7ytt5drxazwmtp7a2uccy-batch/APPSYNC_ASSUME_ROLE is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:eu-central-1:655817346595:table/TodoTable (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException; Request ID: EP48SJPVMB9G9M69HPR0BO8SKJVV4KQNSO5AEMVJF66Q9ASUAAJG)"
    },
    {
      "path": [
        "batchAddTodos"
      ],
      "locations": null,
      "message": "Can't resolve value (/batchAddTodos) : type mismatch error, expected type LIST"
    }
  ]
}

Это заставляет меня думать, что код React «правильный» или, по крайней мере, так же ошибочен, как и код AppSync. Но я подозреваю, что ошибка где-то в отображении шаблона решателя. Я просто не могу его найти. Что здесь не так?

Может быть, сгенерированная предполагаемая роль не поддерживает версию "2018-05-19"? Вот код для роли, созданной по умолчанию (я этого не писал):

 "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
},
"Policies": [
    {
        "PolicyName": "DynamoDBAccess",
        "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "dynamodb:BatchGetItem",
                        "dynamodb:BatchWriteItem",
                        "dynamodb:PutItem",
                        "dynamodb:DeleteItem",
                        "dynamodb:GetItem",
                        "dynamodb:Scan",
                        "dynamodb:Query",
                        "dynamodb:UpdateItem"
                    ],
                    "Resource": [
                        {
                            "Fn::Sub": [
                                "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}",
                                {
                                    "tablename": {
                                        "Fn::If": [
                                            "HasEnvironmentParameter",
                                            {
                                                "Fn::Join": [
                                                    "-",
                                                    [
                                                        "Todo",
                                                        {
                                                            "Ref": "GetAttGraphQLAPIApiId"
                                                        },
                                                        {
                                                            "Ref": "env"
                                                        }
                                                    ]
                                                ]
                                            },
                                            {
                                                "Fn::Join": [
                                                    "-",
                                                    [
                                                        "Todo",
                                                        {
                                                            "Ref": "GetAttGraphQLAPIApiId"
                                                        }
                                                    ]
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        },
                        {
                            "Fn::Sub": [
                                "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}/*",
                                {
                                    "tablename": {
                                        "Fn::If": [
                                            "HasEnvironmentParameter",
                                            {
                                                "Fn::Join": [
                                                    "-",
                                                    [
                                                        "Todo",
                                                        {
                                                            "Ref": "GetAttGraphQLAPIApiId"
                                                        },
                                                        {
                                                            "Ref": "env"
                                                        }
                                                    ]
                                                ]
                                            },
                                            {
                                                "Fn::Join": [
                                                    "-",
                                                    [
                                                        "Todo",
                                                        {
                                                            "Ref": "GetAttGraphQLAPIApiId"
                                                        }
                                                    ]
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    }
]

prepareTodos:

const map = f => arr => arr.map(f);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const dissoc = prop => ({ [prop]: _, ...obj }) => obj;
const mapObj = f => obj =>
  Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: f(obj[key]) }), {});
const replaceEmptyStringWithNull = x => (x === '' ? null : x);
const prepareTodos = map(
  pipe(
    dissoc('id'),
    mapObj(replaceEmptyStringWithNull)
  )
);

Изменить: мне удалось устранить несоответствие типов. В ответ должен вернуть: $util.toJson($ctx.result.data.TodoTable).


person J. Hesters    schedule 20.03.2019    source источник


Ответы (1)


$util.toJson($ctx.result.data.TodoTable) в шаблоне ответа, а также замена TodoTable на фактическое имя таблицы (вы можете найти его в DynamoDB в своей консоли, похоже, это Todo-dqeronnsgvd2pf3facjmlgtsjk-master) устранили ошибки.

Также здесь - пошаговое руководство, которое я написал, когда наткнулся на эту проблему.

person J. Hesters    schedule 21.03.2019
comment
Есть ли альтернативный способ сделать это? Хотите получить дополнительную поддержку на текущий момент? Спасибо - person Subhendu Kundu; 26.03.2020