Accessing AWS Keyspaces service from an AWS Lambda through boto3 - aws-lambda

I am trying to access the keyspaces service through boto3 since it is referenced in the official doc ... However when I try from a sample lambda:
def lambda_handler(event: Dict[str, Any], context: Any):
client = boto3.client('keyspaces')
print(client.get_table(keyspaceName='mykeyspace', tableName='mytable'))
return {
'status': 'completed'
}
I get the following error message, which basically says that "keyspaces" are not available.
{
"errorMessage": "Unknown service: 'keyspaces'. Valid service names are: accessanalyzer, account, acm, acm-pca, alexaforbusiness, amp, amplify, amplifybackend, amplifyuibuilder, apigateway, apigatewaymanagementapi, apigatewayv2, appconfig, appconfigdata, appflow, appintegrations, application-autoscaling, application-insights, applicationcostprofiler, appmesh, apprunner, appstream, appsync, athena, auditmanager, autoscaling, autoscaling-plans, backup, backup-gateway, batch, braket, budgets, ce, chime, chime-sdk-identity, chime-sdk-meetings, chime-sdk-messaging, cloud9, cloudcontrol, clouddirectory, cloudformation, cloudfront, cloudhsm, cloudhsmv2, cloudsearch, cloudsearchdomain, cloudtrail, cloudwatch, codeartifact, codebuild, codecommit, codedeploy, codeguru-reviewer, codeguruprofiler, codepipeline, codestar, codestar-connections, codestar-notifications, cognito-identity, cognito-idp, cognito-sync, comprehend, comprehendmedical, compute-optimizer, config, connect, connect-contact-lens, connectparticipant, cur, customer-profiles, databrew, dataexchange, datapipeline, datasync, dax, detective, devicefarm, devops-guru, directconnect, discovery, dlm, dms, docdb, drs, ds, dynamodb, dynamodbstreams, ebs, ec2, ec2-instance-connect, ecr, ecr-public, ecs, efs, eks, elastic-inference, elasticache, elasticbeanstalk, elastictranscoder, elb, elbv2, emr, emr-containers, es, events, evidently, finspace, finspace-data, firehose, fis, fms, forecast, forecastquery, frauddetector, fsx, gamelift, glacier, globalaccelerator, glue, grafana, greengrass, greengrassv2, groundstation, guardduty, health, healthlake, honeycode, iam, identitystore, imagebuilder, importexport, inspector, inspector2, iot, iot-data, iot-jobs-data, iot1click-devices, iot1click-projects, iotanalytics, iotdeviceadvisor, iotevents, iotevents-data, iotfleethub, iotsecuretunneling, iotsitewise, iotthingsgraph, iottwinmaker, iotwireless, ivs, kafka, kafkaconnect, kendra, kinesis, kinesis-video-archived-media, kinesis-video-media, kinesis-video-signaling, kinesisanalytics, kinesisanalyticsv2, kinesisvideo, kms, lakeformation, lambda, lex-models, lex-runtime, lexv2-models, lexv2-runtime, license-manager, lightsail, location, logs, lookoutequipment, lookoutmetrics, lookoutvision, machinelearning, macie, macie2, managedblockchain, marketplace-catalog, marketplace-entitlement, marketplacecommerceanalytics, mediaconnect, mediaconvert, medialive, mediapackage, mediapackage-vod, mediastore, mediastore-data, mediatailor, memorydb, meteringmarketplace, mgh, mgn, migration-hub-refactor-spaces, migrationhub-config, migrationhubstrategy, mobile, mq, mturk, mwaa, neptune, network-firewall, networkmanager, nimble, opensearch, opsworks, opsworkscm, organizations, outposts, panorama, personalize, personalize-events, personalize-runtime, pi, pinpoint, pinpoint-email, pinpoint-sms-voice, polly, pricing, proton, qldb, qldb-session, quicksight, ram, rbin, rds, rds-data, redshift, redshift-data, rekognition, resiliencehub, resource-groups, resourcegroupstaggingapi, robomaker, route53, route53-recovery-cluster, route53-recovery-control-config, route53-recovery-readiness, route53domains, route53resolver, rum, s3, s3control, s3outposts, sagemaker, sagemaker-a2i-runtime, sagemaker-edge, sagemaker-featurestore-runtime, sagemaker-runtime, savingsplans, schemas, sdb, secretsmanager, securityhub, serverlessrepo, service-quotas, servicecatalog, servicecatalog-appregistry, servicediscovery, ses, sesv2, shield, signer, sms, sms-voice, snow-device-management, snowball, sns, sqs, ssm, ssm-contacts, ssm-incidents, sso, sso-admin, sso-oidc, stepfunctions, storagegateway, sts, support, swf, synthetics, textract, timestream-query, timestream-write, transcribe, transfer, translate, voice-id, waf, waf-regional, wafv2, wellarchitected, wisdom, workdocs, worklink, workmail, workmailmessageflow, workspaces, workspaces-web, xray",
"errorType": "UnknownServiceError",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 39, in lambda_handler\n client = boto3.client(\"keyspaces\")\n",
" File \"/var/runtime/boto3/__init__.py\", line 93, in client\n return _get_default_session().client(*args, **kwargs)\n",
" File \"/var/runtime/boto3/session.py\", line 270, in client\n return self._session.create_client(\n",
" File \"/var/runtime/botocore/session.py\", line 841, in create_client\n client = client_creator.create_client(\n",
" File \"/var/runtime/botocore/client.py\", line 83, in create_client\n service_model = self._load_service_model(service_name, api_version)\n",
" File \"/var/runtime/botocore/client.py\", line 150, in _load_service_model\n json_model = self._loader.load_service_model(service_name, 'service-2',\n",
" File \"/var/runtime/botocore/loaders.py\", line 132, in _wrapper\n data = func(self, *args, **kwargs)\n",
" File \"/var/runtime/botocore/loaders.py\", line 377, in load_service_model\n raise UnknownServiceError(\n"
]
}
Does anybody know if keyspaces are actually available in AWS Lambda through boto3?

The error indicates that keyspaces is not available with current boto3 version available with lambda runtime. As per below link it is boto3-1.20.32
https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
I have also gone through boto3 changelog and unable to find when keyspaces support added to boto3, but I am able to see some changes/fixes done to boto3 version 1.21.x
https://github.com/boto/boto3/blob/develop/CHANGELOG.rst#12120
How ever you can still try creating lambda layers with latest boto3 versions as explained below and use keyspaces with it.
https://aws.amazon.com/premiumsupport/knowledge-center/lambda-python-runtime-errors/
Below link talks about one another way to connect to keyspaces via lambda:
https://github.com/aws-samples/aws-keyspaces-lambda-python
secret_client = boto3.client('secretsmanager')
secret_response = secret_client.get_secret_value(SecretId=CASSANDRA_CREDS)
secret = json.loads(secret_response.get('SecretString'))
cassandra_user = secret['ServiceSpecificCredential']['ServiceUserName']
cassandra_password = secret['ServiceSpecificCredential']['ServicePassword']
auth_provider = PlainTextAuthProvider(username=cassandra_user, password=cassandra_password)
ssl_context = SSLContext(PROTOCOL_TLSv1)
ssl_context.load_verify_locations('AmazonRootCA1.pem')
ssl_context.verify_mode = CERT_REQUIRED
cluster = Cluster(['cassandra.{}.amazonaws.com'.format(AWS_DEFAULT_REGION)], port=9142, ssl_context=ssl_context, auth_provider=auth_provider)
session = cluster.connect()

Related

boto3 and lambda: Invalid type for parameter KeyConditionExpression when using DynamoDB resource and localstack

I'm having very inconsistent results when trying to use boto3 dynamodb resources from my local machine vs from within a lambda function in localstack. I have the following simple lambda handler, that just queries a table based on the Hash Key:
import boto3
from boto3.dynamodb.conditions import Key
def handler(event, context):
dynamodb = boto3.resource(
"dynamodb", endpoint_url=os.environ["AWS_EP"]
)
table = dynamodb.Table("precalculated_scores")
items = table.query(
KeyConditionExpression=Key("customer_id").eq(event["customer_id"])
)
return items
The environment variable "AWS_EP" is set to my localstack DNS when protyping (http://localstack:4566).
When I call this lamdba I get the following error:
{
"errorMessage": "Parameter validation failed:\nInvalid type for parameter KeyConditionExpression, value: <boto3.dynamodb.conditions.Equals object at 0x7f7440201960>, type: <class 'boto3.dynamodb.conditions.Equals'>, valid types: <class 'str'>",
"errorType": "ParamValidationError",
"stackTrace": [
" File \"/opt/code/localstack/localstack/services/awslambda/lambda_executors.py\", line 1423, in do_execute\n execute_result = lambda_function_callable(inv_context.event, context)\n",
" File \"/opt/code/localstack/localstack/services/awslambda/lambda_api.py\", line 782, in exec_local_python\n return inner_handler(event, context)\n",
" File \"/var/lib/localstack/tmp/lambda_script_l_dbef16b3.py\", line 29, in handler\n items = table.query(\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/boto3/resources/factory.py\", line 580, in do_action\n response = action(self, *args, **kwargs)\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/boto3/resources/action.py\", line 88, in __call__\n response = getattr(parent.meta.client, operation_name)(*args, **params)\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/botocore/client.py\", line 514, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/botocore/client.py\", line 901, in _make_api_call\n request_dict = self._convert_to_request_dict(\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/botocore/client.py\", line 962, in _convert_to_request_dict\n request_dict = self._serializer.serialize_to_request(\n",
" File \"/opt/code/localstack/.venv/lib/python3.10/site-packages/botocore/validate.py\", line 381, in serialize_to_request\n raise ParamValidationError(report=report.generate_report())\n"
]
}
Which is a weird error - From what I researched on other question it usually happens when using the boto3 client, but I am using boto3 resources. Furthermore, when I run the code locally in my machine it runs fine.
At first I thought that it might be due to different versions for boto3 (My local machine is using version 1.24.96, while the version inside the lambda runtime is 1.16.31). However I downgraded my local version to the same as the one in the runtime, and I keep getting the same results.
After some answers on this question I managed to get the code working against actual AWS services, but it still won't work when running against localstack.
Am I doing anything wrong? Os might this be a bug with localstack?
--- Update 1 ---
Changing the return didn't solve the problem:
return {"statusCode": 200, "body": json.dumps(items)}
--- Update 2 ---
The code works when running against actual AWS services instead of running against localstack. Updating the question with this information.
This works fine from both my local machine and Lambda:
import json, boto3
from boto3.dynamodb.conditions import Key
def lambda_handler(event, context):
dynamodb = boto3.resource(
"dynamodb",
endpoint_url="https://dynamodb.eu-west-1.amazonaws.com"
)
table = dynamodb.Table("test")
items = table.query(
KeyConditionExpression=Key("pk").eq("1")
)
print(items)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
Also be sure that event["customer_id"] is in fact a string value as expected by the eq function.
I would check to ensure you have the endpoint setup correctly and that you have the current version deployed.
It may also be the fact you are trying to return the results of your API call via the handler, instead of a proper JSON response as expected:
return {
'statusCode': 200,
'body': json.dumps(items)
}

Bubble.io API Connector with Lambda + AWS Rekognition Problem

Hi I was trying to use AWS Rekognition -> Lambda -> AWS API -> Bubble.io
I want to pass a image base64 code to process the image recognition.
Here is the lambda code:
import json
import boto3
import base64
def lambda_handler(event, context):
def detect_labels():
# 1. create a client object, to connect to rekognition
client=boto3.client('rekognition')
image = base64.b64decode(event['face'])
# 4. call Rekognition, store result in 'response'
response = client.detect_labels(
Image={
'Bytes': image
},
MaxLabels=20,
)
#6. Return response from function
return response
# Call detect_labels
response = detect_labels()
# Return results to API gateway
return {
'statusCode': 200,
'body': json.dumps(response)
}
and if I test it within the lambda test with config:
{
"face": "<imgbase64codehere>"
}
It works fine and return success message.
However I set the same thing in Bubble.io
Setting img. It wont work.
I check the Cloudwatch, the error is:
[ERROR] KeyError: 'face'
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 30, in lambda_handler
response = detect_labels()
File "/var/task/lambda_function.py", line 14, in detect_labels
image = base64.b64decode(event['face'])
[ERROR] KeyError: 'face' Traceback (most recent call last): File "/var/task/lambda_function.py", line 30, in lambda_handler response = detect_labels() File "/var/task/lambda_function.py", line 14, in detect_labels image = base64.b64decode(event['face'])
I am sure there is not connection problem (if I dont use JSON it works fine)
So what do I do wrong in the setting?
Thanks for your help in advance
In your test you are sending an input of just a JSON request with face.
It looks like the implementation you've setup uses Amazon API Gateway. If you are doing that your Lambda function might receive the event in a different format depending on how you've implemented the API Gateway integration.
The Lambda developer guide has an example of the JSON format. You will probably want to access the body attribute.

AWS Lambda Function does not get credentials from ENV variables (invalid security token)

I have a small lambda function written in Ruby that works on AWS but shows ERROR: The security token included in the request is invalid locally.
The purpose of this function is to read something from DynamoDB table. Here are the important parts of the function:
require 'json'
require 'aws-sdk-dynamodb'
def lambda_handler(event:, context:)
p 'AWS_ACCESS_KEY_ID: ' + ENV['AWS_ACCESS_KEY_ID']
p 'AWS_SECRET_ACCESS_KEY: ' + ENV['AWS_SECRET_ACCESS_KEY']
dynamodb = Aws::DynamoDB::Client.new(region: 'eu-north-1')
...
dynamodb.get_item(payload).item
...
end
When I try to invoke the function locally using sam local start-api I'm getting this:
START RequestId: 56d1c0f0-cad8-45b8-9a95-344c06f0aea4 Version: $LATEST
"AWS_ACCESS_KEY_ID: AKI**************TPW"
"AWS_SECRET_ACCESS_KEY: 1ew****************************AAn"
Error raised from handler method
{
"errorMessage": "The security token included in the request is invalid",
"errorType": "Function<Aws::DynamoDB::Errors::UnrecognizedClientException>",
"stackTrace": [
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'",
"/var/runtime/gems/aws-sdk-dynamodb-1.63.0/lib/aws-sdk-dynamodb/plugins/simple_attributes.rb:119:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:22:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/seahorse/client/plugins/request_callback.rb:71:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/seahorse/client/plugins/response_target.rb:24:in `call'",
"/var/runtime/gems/aws-sdk-core-3.121.0/lib/seahorse/client/request.rb:72:in `send_request'",
"/var/runtime/gems/aws-sdk-dynamodb-1.63.0/lib/aws-sdk-dynamodb/client.rb:3314:in `get_item'",
"/var/task/units.rb:29:in `unit'"
]
}
END RequestId: 56d1c0f0-cad8-45b8-9a95-344c06f0aea4
REPORT RequestId: 56d1c0f0-cad8-45b8-9a95-344c06f0aea4 Init Duration: 0.06 ms Duration: 685.33 ms Billed Duration: 700 ms Memory Size: 128 MB Max Memory Used: 128 MB
Lambda returned empty body!
As you can see ENV variables are set correctly because they are being printed to the console.
According to the documentation it should be enough to set those two ENV variables but looks like I'm missing something.
When I change the instantiation from
dynamodb = Aws::DynamoDB::Client.new(region: 'eu-north-1')
to
dynamodb = Aws::DynamoDB::Client.new(region: 'eu-north-1', credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']))
Then all of a sudden lambda function connects to DynamoDB without problems.
How to make the function connect to DynamoDB locally? Assuming that I don't want to pass :credentials explicitly because this would break the function on AWS (because on AWS it connects to dynamodb using AmazonDynamoDBFullAccess policy)
Why are you trying to pass AK/SK via your application ?
SAM uses your AWS Cli configuration in order to connect to DynamoDB. So normally, your application should be able to connect to DynamoDB if your local AK/SK are valid and you have the right IAM role to access your DynamoDB table.
On the other hand, on aws, lambda needs IAM role with DynamoDB access so no need for AK/SK as environment variables.

Endpoint URL for DynamoDB inside localstack's Lambda function

I'm using localstack for local development. I have a DynamoDB table named readings and I'd like to insert items from a lambda function.
I have deployed simple lambda function in python runtime:
import os
import boto3
def lambda_handler(events, context):
DYNAMODB_ENDPOINT_URL = os.environ.get("DYNAMODB_ENDPOINT_URL")
dynamodb = boto3.resource("dynamodb", endpoint_url=DYNAMODB_ENDPOINT_URL)
readings_table = dynamodb.Table(DYNAMODB_READINGS_TABLE_NAME)
readings_table.put_item(Item={"reading_id": "10", "other": "test"})
But I'm getting error: [ERROR] EndpointConnectionError: Could not connect to the endpoint URL: "http://localstack:4569/"
I've tried combinations of localhost and localstack along with ports: 4566 and 4569. All of them fail.
Here's my docker-compse service that I use to start localstack
localstack:
image: localstack/localstack:0.11.2
ports:
- 4566:4566
- 8080:8080
environment:
SERVICES: "dynamodb,sqs,lambda,iam"
DATA_DIR: "/tmp/localstack/data"
PORT_WEB_UI: "8080"
LOCALSTACK_HOSTNAME: localstack
LAMBDA_EXECUTOR: docker
AWS_ACCESS_KEY_ID: "test"
AWS_SECRET_ACCESS_KEY: "test"
AWS_DEFAULT_REGION: "us-east-1"
volumes:
- localstack_volume:/tmp/localstack/data
- /var/run/docker.sock:/var/run/docker.sock
# When a container is started for the first time, it will execute files with extensions .sh that are found in /docker-entrypoint-initaws.d.
# Files will be executed in alphabetical order. You can easily create aws resources on localstack using `awslocal` (or `aws`) cli tool in the initialization scripts.
# source: https://github.com/localstack/localstack/pull/1018/files#diff-04c6e90faac2675aa89e2176d2eec7d8R185
- ./localstack-startup-scripts/:/docker-entrypoint-initaws.d/
What would be the correct endpoint url that I have to set inside in my lambda so that I can send requests to localstack's DynamoDB?
According to the docs LOCALSTACK_HOSTNAME is a read-only env var:
LOCALSTACK_HOSTNAME: Name of the host where LocalStack services are available. Use this hostname as endpoint (e.g., http://${LOCALSTACK_HOSTNAME}:4566) in order to access the services from within your Lambda functions (e.g., to store an item to DynamoDB or S3 from a Lambda).
try with
ports:
- "0.0.0.0:4566-4599:4566-4599"
Hope it helps
By following Robert Taylor's answer, I was able to get the following working in my Java Lambda (this should really be a comment in that answer, but code formatting for big snippet is not adequate in comment, so I'm sharing this as a separate answer to make people's life easier):
var url = System.getenv("LOCALSTACK_HOSTNAME");
var credentials = new BasicAWSCredentials("mock_access_key", "mock_secret_key");
var ep = new AwsClientBuilder.EndpointConfiguration(String.format("http://%s:4566",url), "us-east-1");
s3 = AmazonS3ClientBuilder.standard()
.withEndpointConfiguration(ep)
.withPathStyleAccessEnabled(true)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
dynamoDBMapper = new DynamoDBMapper(
AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(ep)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build()
);

amazon elasticsearch service ConnectionTimeout

from elasticsearch import Elasticsearch
from elasticsearch import helpers
es_url = '*****.us-east-1.es.amazonaws.com'
# es_conn = Elasticsearch(es_url)
es_conn = Elasticsearch([{'host': es_url, 'port': 443, 'use_ssl': True}])
while 1:
for ....:
actions.append(....)
if len(actions) >= 5000:
helpers.bulk(es_conn, actions)
actions = []
helpers.bulk(es_conn, actions)
The code above run on an ec2 instance, it often throw the following error:
helpers.bulk(es_conn, actions)
File "/usr/local/lib/python2.7/site-packages/elasticsearch/helpers/__init__.py", line 194, in bulk
for ok, item in streaming_bulk(client, actions, **kwargs):
File "/usr/local/lib/python2.7/site-packages/elasticsearch/helpers/__init__.py", line 162, in streaming_bulk
for result in _process_bulk_chunk(client, bulk_actions, raise_on_exception, raise_on_error, **kwargs):
File "/usr/local/lib/python2.7/site-packages/elasticsearch/helpers/__init__.py", line 91, in _process_bulk_chunk
raise e
ConnectionTimeout: ConnectionTimeout caused by - ReadTimeoutError(HTTPSConnectionPool(host='search-shinezoneels-pc3ib5rkhuylqynfoz6rph7gh4.us-east-1.es.amazonaws.com', port=443): Read timed out.)
At the same time, I run the code on another EMR instance, the error did not happen at all. Bulk speed on ec2 instance is about twice of EMR instance, but often error. How to fix it?

Resources