{"id":489,"date":"2023-01-18T09:30:51","date_gmt":"2023-01-18T09:30:51","guid":{"rendered":"https:\/\/blog.67bricks.com\/?p=489"},"modified":"2023-01-20T15:28:00","modified_gmt":"2023-01-20T15:28:00","slug":"setting-up-local-aws-environment-using-localstack","status":"publish","type":"post","link":"https:\/\/blog.67bricks.com\/?p=489","title":{"rendered":"Setting up local AWS environment using Localstack"},"content":{"rendered":"\n<p>When Cloud services are used in an application, it might be tricky to mock them during local development. Some approaches include: 1) doing nothing thus letting your application fail when it makes a call to a Cloud service;  2) creating sets of fake data to return from calls to AWS S3, for example; 3) using an account in the Cloud for development purposes. A nice in-between solution is using Localstack, a Cloud service emulator. Whereas the number of services available and the functionality might be a bit limited compared to the real AWS environment, it works rather well for our team.<\/p>\n\n\n\n<p>This article will describe how to set it up for local development in Docker. <\/p>\n\n\n\n<p><strong>Docker-compose setup:<\/strong><\/p>\n\n\n\n<p>In the services section of our <em>docker-compose.yml<\/em> we have Localstack container definition: <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">localstack:\n    image: localstack\/localstack:latest\n    hostname: localstack\n    environment:\n      - SERVICES=s3,sqs\n      - HOSTNAME_EXTERNAL=localstack\n      - DATA_DIR=\/tmp\/localstack\/data\n      - DEBUG=1\n      - AWS_ACCESS_KEY_ID=test\n      - AWS_SECRET_ACCESS_KEY=test\n      - AWS_DEFAULT_REGION=eu-central-1\n    ports:\n      - \"4566:4566\"\n    volumes:\n      - localstack-data:\/tmp\/localstack:rw\n      - .\/create_localstack_resources.sh:\/docker-entrypoint-initaws.d\/create_localstack_resources.sh\n<\/pre>\n\n\n\n<p>Although we don&#8217;t need to connect to any AWS account, we do need dummy AWS variables (with any value). We specify which services we want to run using Localstack &#8211; in this case it&#8217;s SQS and S3.<\/p>\n\n\n\n<p>We also need to set HOSTNAME_EXTERNAL because SQS API needs the container to be aware of the hostname that it can be accessed on.<\/p>\n\n\n\n<p>Another point is that that we cannot use the entrypoint definition because Localstack has a directory <em>docker-entrypoint-initaws.d<\/em> from where shell scripts are run when the container starts up. That&#8217;s why we&#8217;re mapping the container volume to a folder wherer those scripts are. In our case <em>create_localstack_resources.sh<\/em> will create all the necessary S3 buckets and the SQS queue:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXPECTED_BUCKETS=(\"bucket1\" \"bucket2\" \"bucket3\")\nEXISTING_BUCKETS=$(aws --endpoint-url=http:\/\/localhost:4566 s3 ls --output text)\n\necho \"creating buckets\"\nfor BUCKET in \"${EXPECTED_BUCKETS[@]}\"\ndo\n  echo $BUCKET\n  if [[ $EXISTING_BUCKETS != *\"$BUCKET\"* ]]; then\n    aws --endpoint-url=http:\/\/localhost:4566 s3 mb s3:\/\/$BUCKET\n  fi\ndone\n\necho \"creating queue\"\nif [[ $EXISTING_QUEUE != *\"$EXPECTED_QUEUE\"* ]]; then\n    aws --endpoint-url=http:\/\/localhost:4566 sqs create-queue --queue-name my-queue\\\n    --attributes '{\n      \"RedrivePolicy\": \"{\\\"deadLetterTargetArn\\\":\\\"arn:aws:sqs:eu-central-1:000000000000:my-dead-letter-queue\\\",\\\"maxReceiveCount\\\":\\\"3\\\"}\",\n      \"VisibilityTimeout\": \"120\"\n    }'\nfi\n<\/pre>\n\n\n\n<p>Note that AWS CLI command syntax is different to the real AWS CLI (otherwise you&#8217;d create resources in the account for which you have the credentials set up!), and includes Localstack endoint flag:  <em>&#8211;endpoint-url=http:\/\/localhost:4566<\/em><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Configuration files<\/strong><\/p>\n\n\n\n<p>We use Scala with Play framework for this particular application, and therefore have <em>.conf<\/em> files. In <em>local.conf<\/em> file we have the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><p class=\"wp-block-preformatted\">aws {\n   localstack.endpoint=\"http:\/\/localstack:4566\"\n   region = \"eu-central-1\"\n   s3.bucket1 = \"bucket1\"\n   s3.bucket2 = \"bucket2\"\n   sqs.my_queue = \"my-queue\"\n   sqs.queue_enabled = true\n}<\/p><\/pre>\n\n\n\n<p>The real <em>application.conf<\/em> file has resource names injected at the instance startup. They live in an autoscaling group launch template where they are created by Terraform (out of scope of this post).<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Initializing SQS client based on the environment<\/strong><\/p>\n\n\n\n<p>The example here is for creating an SQS client. Below are snippets most relevant to the topic.<\/p>\n\n\n\n<p>In order to initialize the SQS Service so that it can be injected into other services we can do this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><p>lazy val awsSqsService: QueueService = createsSqsServiceFromConfig()<\/p><\/pre>\n\n\n\n<p>In <em> <span style=\"background-color: rgba(0, 0, 0, 0.2); font-size: revert; color: initial;\">createsSqsServiceFromConfig<\/span><\/em> we check if the configuration has a Localstack endpoint and if so, we build LocalStack client:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><p class=\"wp-block-preformatted\">protected def createsSqsServiceFromConfig(): QueueService = {\n  readSqsClientConfig().map { config =&gt;\n  val sqsClient: SqsClient = config.localstackEndpoint match {\n    case Some(endpoint) =&gt; new LocalStackSqsClient(endpoint, config.region)\n    case None =&gt; new AwsSqsClient(config.region)\n  }\n  new SqsQueueService(config.queueName, sqsClient)\n }.getOrElse(fakeAwsSqsService)\n}<\/p><\/pre>\n\n\n\n<p><em>readSqsClientConfig<\/em> is used  to get configuration values from <em>.conf<\/em> files:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><p>private def readSqsClientConfig = {<br>   val sqsName = config.get[String](\"aws.sqs.my_queue\")<br>   val sqsRegion = config.get<a href=\"\">[String](\"aws.region\") <\/a><br>   val localStackEndpoint = config.getOptional[<a href=\"\">String](\"aws.localstack.endpoint\")<\/a><br>   SqsClientConfig(sqsName, sqsRegion, localStackEndpoint)<br>}<\/p><\/pre>\n\n\n\n<p>Finally  LocalStackSqsClient initialization looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><p>class LocalStackSqsClient(endpoint: String, region:String) extends SqsClient with Logging {<br>    private val sqsEndpoint = new EndpointConfiguration(endpoint, region)<br>    private val awsCreds = new BasicAWSCredentials(\"test\", \"test\")<br>    private lazy val sqsClientBuilder = AmazonSQSClientBuilder.standard()<br>      .withEndpointConfiguration(sqsEndpoint)<br>      .withCredentials(new AWSStaticCredentialsProvider(awsCreds))<br>    private lazy val client = sqsClientBuilder.build()<\/p><p>\noverride def BuildClient(): AmazonSQS = {\n        log.debug(\"Initializing LocalStack SQS service\")\n        client\n    }\n}<\/p><\/pre>\n\n\n\n<p>Real AWS Client for the test\/live environment (a snippet):<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">    AmazonSQSClientBuilder.standard()\n      .withCredentials(new DefaultAWSCredentialsProviderChain)\n      .withRegion(region)<\/code><\/pre>\n\n\n\n<p>Notice that we need fake <em>BasicAWSCredentials<\/em> that allows us to pass in dummy AWS access key and secret key<em> <\/em>and then we use <em>AWSStaticCredentialsProvider<\/em>, an implementation of <em>AWSCredentialsProvider<\/em> that just wraps static <em>AWSCredentials<\/em>. When real AWS environment is used, instead of  <em>AWSStaticCredentialsProvider<\/em>  we use <em>DefaultAWSCredentialsProviderChain<\/em>, which picks the EC2 Instance Role if it&#8217;s unable to find credentials by any other methods.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>And that&#8217;s it. Happy coding!<\/p>\n\n\n","protected":false},"excerpt":{"rendered":"<p>When Cloud services are used in an application, it might be tricky to mock them during local development. Some approaches include: 1) doing nothing thus letting your application fail when it makes a call to a Cloud service; 2) creating sets of fake data to return from calls to AWS S3, for example; 3) using &hellip; <a href=\"https:\/\/blog.67bricks.com\/?p=489\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Setting up local AWS environment using Localstack&#8221;<\/span><\/a><\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-489","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/posts\/489","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=489"}],"version-history":[{"count":23,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/posts\/489\/revisions"}],"predecessor-version":[{"id":641,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=\/wp\/v2\/posts\/489\/revisions\/641"}],"wp:attachment":[{"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=489"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=489"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.67bricks.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=489"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}