AWS DynamoDB query top 5 scores for country

Let’s take a look at how to query top 5 scores from AWS DynamoDB, let say we have some top scores table and want to get top 5 performers for the specific country. What we also need is not to waste the network resources, that’s why we will specify what exact fields we need to fetch from server – with help of projectionExpression.
Moreover let’s take a look at how to deal with the following error:
com.amazon.coral.validate#ValidationException”,”message”:”Invalid ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name”

it could be solved by adding reserved words to the expressionAttributeNames, e.g. instead of

queryExpression.projectionExpression = "userId,challenge,country,name,total"

where name and total are reserved words let’s do the following

queryExpression.expressionAttributeNames = [
            "#n": "name",
            "#t": "total",
        ]
queryExpression.projectionExpression = "userId,challenge,country,#n,#t"

That approach will eliminate the ValidationException – reserved keyword; reserved keyword error.

Here’s the whole method
Continue reading “AWS DynamoDB query top 5 scores for country”

Social Share Toolbar

AWS Mobile hub, swift and dynamoDB

If you will google “backend for ios application” or “backend for swift application” you can notice that Amazone Mobile Hub will be in the top results, articles whatever, the easiest way to start your backend right now is for sure https://aws.amazon.com/mobile especially if you already familiar with aws. The wizard is quite well and detailed, it will guide you through the process and will even generate sample project YourApp-aws-my-sample-app-ios-swift which will use the backend which you have just configured in the web.
I believe you can play with wizard by yourself and reach even the point when you will copy-paste all AWS stuff to you project – wizard greatly describe even this part. I just want to highlight several steps/moments which were not very obviously for me, and show the example how to use mobile hub even easier w/o copying all the code from AWS – I mean tables, helpers, and all that swift stuff, you will need only One file – aws data model, and that’s it, you are all set to make backend requests.

Please start with completing this wizard https://aws.amazon.com/mobile
Several things they forgot to mention in the step “Use MySampleApp as an Example”:
1. While copying settings from Sample’s Info.plist to yours one, additionally copy “App Transport Security Settings”.
001
It is easy to forget and it’s really required to allow your app to make http requests to aws instances
2. On the step “Add a Run Script phase to your project” you need to add “bash “${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh” line to the “Build Phases”.
It’s vital to have “Run script” section under the “Embed frameworks” and basically all other, just drag it to the bottom, so you can avoid “…Frameworks/AWSCore.framework/strip-frameworks.sh: No such file or directory” error during the Build of your app.
So drag it down like that
002
and try to clean and build your project once again – should be fine in case if you had such an error.

Read further if you have successfully finished all wizards steps and if you have your app up and running and connected with aws backend.

Lets say we want to create a new table for collecting Feedback, we just need to post the “Feedback” and that’s it, let go ahead and create a new table in mobilehub, so I clicked to “Configure” section, than to NoSql Databases and choose “Add new table” option on the bottom, here is my scheme:
003
Ensure that in the “Queries this table can perform.” you have “Find all items with deviceId = ABC” option, otherwise you will have only one Feedback from one device, it will be the latest one.

You can try to download the latest version of Sample App, but eventually the only thing you nee is the table ID which looks like this “AppName-mobilehub-xxxxxxxxxx-Feedback”. You don’t really need those humongous Tables and Helper classes, the only one thing you might want to copy-paste from the latest Sample is the Feedback.swift which is data model for AWS DynamoDB, it looks like this (Feedback.swift):

//...
 
import Foundation
import UIKit
import AWSDynamoDB
class Feedback: AWSDynamoDBObjectModel, AWSDynamoDBModeling {
 
    var _deviceId: String?
    var _appName: String?
    var _text: String?
    var _userContact: String?
    var _date: String?
 
    class func dynamoDBTableName() -> String {
        return "AppName-mobilehub-xxxxxxxxxxx-Feedback"
    }
 
    class func hashKeyAttribute() -> String {
        return "_deviceId"
    }
 
    class func rangeKeyAttribute() -> String {
        return "_date"
    }
 
    override class func JSONKeyPathsByPropertyKey() -> [NSObject : AnyObject] {
        return [
            "_deviceId" : "deviceId",
            "_appName" : "appName",
            "_text" : "text",
            "_userContact" : "userContact",
            "_date" : "date",
        ]
    }
}

Ok, we are ready to post the feedback, here is all we need:

    func postFeedback(contact: String, text: String, completionHandler: (error: NSError?) -> Void) {
        let feedback = Feedback()
        feedback._deviceId = Identity.getUserUniqueId() // AWS Identity
        feedback._date = String(NSDate())
        feedback._appName = "AppName" // Just in case if you'll want to use this table from several apps.
        feedback._userContact = contact
        feedback._text = text
 
        insertFeedbackWithCompletionHandler(feedback, completionHandler: completionHandler)
    }
 
    // MARK: AWS Methods
    internal func insertFeedbackWithCompletionHandler(item: Feedback, completionHandler: (error: NSError?) -> Void) {
        let objectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
 
        objectMapper.save(item, completionHandler: {(error: NSError?) -> Void in
            dispatch_async(dispatch_get_main_queue(), {
                completionHandler(error: error)
            })
        })
    }

So this is basically it, go ahead and run you app and it should work (obviously you had to create a UI and call this method on your own). You might noticed this line “Identity.getUserUniqueId()” just in case here is what’s under the hood:

    func getUserUniqueId() -> String {
        let identityManager = AWSIdentityManager.defaultIdentityManager()
        return identityManager.identityId ?? TEST_ID
    }

This is it, AWS mobilehub is very easy and way to create a backend for you iso app, I still had no chance to push it’s limits and I’m sure that there is a price for such a high-level abstraction but it’s very easy for simple use cases. May be I’ll post some results on my researches on it’s performance or dealing with edge cases. It was kind of overview of this new technology.

P.S. To check out the content of your DynamoDB go here https://console.aws.amazon.com/dynamodb/home?region=us-east-1#tables: choose the right region, click to “…-Feedback” table and click “Items” tab, here you can see all the items from your database and even scan or query them.

Social Share Toolbar

Travis invalidate aws CloudFront cache

How to make aws CloudFront cache invalidation in travis CI.
NB: In previous articles You can always find how to set up deployment in travis ci to aws.
So, let say we need to deploy static files to aws s3 (example) let’s add “after_deploy” section to .travis.yml:

lang: ruby
#...
deploy:
  provider: s3
#...
after_deploy:
  - ./.travis/reset_cloudfront_cache.sh $SITE_KEY $SITE_KEY_SECRET

Note: that $SITE_KEY and $SITE_KEY_SECRET must be travis encrypted values (details). Also, this user (which key and secret we are going to use) must have CloudFront permissions, so do not forget to add him to the appropriate group in aws/IAM.

For the .travis.yml config that’s it, we just need to run reset_cloudfront_cache script from the after_deploy section (so it will be started after deploy is done).

Let’s take a look at reset_cloudfront_cache.sh:

###############################################################################
###  Resets CloudFront cache with boto/cfadmin utility
###  Run: ./this_script  
###############################################################################

#
# Travis specific part - run this script only for production
#

# If this is fork - just exit
if [[ -n "${TRAVIS_PULL_REQUEST}" && "${TRAVIS_PULL_REQUEST}" != "false"  ]]; then
  echo -e '\n============== deploy will not be started (from the fork) ==============\n'
  exit 0
fi

if [[ $TRAVIS_BRANCH == 'master' ]]; then
    echo -e "\nThis is master/production branch - let's reset the CloudFront cache\n"
else
    echo -e "\nReset of CloudFront cache will not be started for non-production branch - exit.\n"
    exit 0
fi

#
# Install boto
#
echo -e "\nInstalling boto...\n"
git clone git://github.com/boto/boto.git
cd boto
sudo python setup.py install
cd ../
rm -rf boto

#
# Set up credentials for boto
#
echo -e "\nSet up boto credentials...\n"
cat > ~/.boto < 
# XNXNXXNNNNXNXN - distribution configured for the Web - in aws amazon cloudfront distributions.
#
echo -e "\nCloudFront Invalidating...\n"
cfadmin invalidate XNXNXXNNNNXNXN /yoursite.com
cfadmin invalidate XNXNXXNNNNXNXN /.yoursite.com
cfadmin invalidate XNXNXXNNNNXNXN /index.html /static/main.css /static/main.js
echo -e "\nInvalidating is in progress...\n"
echo -e "\nYou can check the status on the 'Invalidations' tab here https://console.aws.amazon.com/cloudfront/home?region=your_region#distribution-settings:XNXNXXNNNNXNXN\n"

#
# Clean up
#
echo -e "\nRemove boto config file\n"
rm ~/.boto

The main idea of this script is to use boto/cfadmin utility to reset specific paths in the selected distribution (this is library based on CloudFront rest API).
So we need to install boto first, then create ~/.boto config file (with aws credentials) and just to reset\invalidate the CoudFront cache with the following command:

cfadmin invalidate <distribution> <path>

That will start the Invalidation process that may last up to 10-30 minutes.

Social Share Toolbar

Travis branch specific variable

How to set different values for variables in travis travis.yml based on the current branch?
Let’s take a look at the part of the travis config:

lang: ruby
env:
  global:
  - GET_ENV_VAR=./.travis/get_env_specific_value.sh
  #
  # Keys:
  #
  # SITE_DEV_KEY
  - secure: "..."
  # SITE_DEV_KEY_SECRET
  - secure: "..."
  # SITE_PROD_KEY
  - secure: "..."
  # SITE_PROD_KEY_SECRET
  - secure: "..."
  #
  # Branch specific variables:
  #
  - ENV=$($GET_ENV_VAR develop master)
  - SITE_KEY=$($GET_ENV_VAR $SITE_DEV_KEY $SITE_PROD_KEY)
  - SITE_KEY_SECRET=$($GET_ENV_VAR $SITE_DEV_KEY_SECRET $SITE_PROD_KEY_SECRET)
  - AWS_BUCKET=$($GET_ENV_VAR dev-bucket prod-bucket)
  - AWS_REGION=$($GET_ENV_VAR us-west-2 us-west-1)

The magic happens in this line

GET_ENV_VAR=./.travis/get_env_specific_value.sh

To be more specific the very magic happens in this script “./.travis/get_env_specific_value.sh”, let’s take a look at the source code of this bash script:

#!/bin/bash
 
# If this is fork - just exit
if [[ -n "${TRAVIS_PULL_REQUEST}" && "${TRAVIS_PULL_REQUEST}" != "false"  ]]; then
  echo -e '\n============== deploy will not be started (from the fork) ==============\n'
  exit 0
fi
 
# Choose necessary argument according to the current branch.
if [[ $TRAVIS_BRANCH == 'develop' ]]; then
    echo $1
elif [[ $TRAVIS_BRANCH == 'master' ]]; then
    echo $2
else
    exit 0
fi

The idea of this script is just to return 1-st or 2-nd argument depends on the current branch name.

Social Share Toolbar

Travis ci deploy static files to amazon s3

How to deploy static files to aws s3 bucket?
Here is the example of travis.yml config:

lang: ruby
env:
  global:
  # env variables should be defined here:
  - AWS_BUCKET=...
before_script:
  # workaround to fix rake not found
  - gem install rake
deploy:
  provider: s3
  access_key_id: $SITE_KEY
  secret_access_key: $SITE_KEY_SECRET
  bucket: $AWS_BUCKET
  region: $AWS_REGION
  skip_cleanup: true
  acl: public_read
  on:
    all_branches: true

To workaround problem with Rakefile not found you need to add this Rakefile:

task :default do
  puts "default rake task"
end
Social Share Toolbar