Your First Integration: Geolocate IP

Socless uses Integrations to take actions within playbooks. An Integration is an AWS Lambda functions that uses the socless_bootstrap function from the Socless Python library to manage its execution life-cycle (i.e fetching its inputs and saving its outputs). Integrations typically integrate with 3rd party APIs – hence the name 'Integrations' – to accomplish a task.

The bare-bones, pseudo-code implementation for any Socless integration is shown below

from socless import socless_bootstrap

def handle_state(param1, param2, …):
    """Core action logic goes here"""
    # Implement core actions (e.g api requests) and return dictionary with desired results
    return {…}

def lambda_handler(event, lambda_context):
    """Handles life-cycle of handle_state’s execution"""
    return socless_bootstrap(event, lambda_context, handle_state, include_event=False)

The socless_bootstrap function takes the below parameters:

Parameter Description Required? Default
event Payload containing information the Integration needs to execute. Passed in either by an executing playbook or by a developer during testing. Yes N/A
handle_state A function that implements the core logic of the Integration. Must return a dictionary Yes N/A
include_event Boolean to specify if the whole playbook execution context (not lambda_context) should be made available to the handle_state function during its execution. Typically set to True for integrations that need to refer to elements of execution context in string templates. If True param1 of handle_state must be the keyword context No FALSE
lambda_context The Lambda context object passed in by AWS whenever it triggers a Lambda function Yes N/A

In this tutorial, we'll learn the basics of Integration development by writing one that geolocates an IP address and returns its country, latitude and longitude.

Setting Up

To begin, change out of socless directory at your command-line (if you’re still within it) to a higher-level project folder.

Next, run the commands below to download the socless-integrations-template which contains the pre-configurations we need for developing our Integration

git clone git@github.com:twilio-labs/socless-integrations-template.git socless-tutorial
cd socless-tutorial
./setup
virtualenv --python=python3.7 venv
source venv/bin/activate

The commands:

  • clone the socless-integrations-template into a folder named socless-tutorial
  • run a setup script that installs the development dependencies we need (serverless framework and relevant plugins) then deletes the setup script.
  • create and activate a Python 3.7 virtual environment which will be used to package our Integration when we're ready to deploy it.

After running the commands, you should be left with a socless-tutorial directory that contains a functions folder. This folder is where the code for our tutorial Integrations will live.

Now that we're setup, let's code our Integration

Coding the GeoIP Integration

For IP address geolocation, we’ll rely on https://tools.keycdn.com/geo.json which is a free geolocation API that requires no auth keys. This allows us keep things simple. Click the link to get an idea of the data the API returns.

The geolocation logic for our integration will:

  • be implemented in a handle_state function that only needs an IP as a parameter.
  • use the python requests library to perform a GET request to https://tools.keycdn.com/geo.json with the url parameter host={ip}
  • return a dictionary containing the country name, city name, latitude and longitude of the IP address.

Our Integration will have a lambda_handler function that

  • serves as AWS Lambda's entry point for our integration
  • invokes socless_bootstrap to manage its life-cycle
  • return the results of socless_bootstrap

Here’s what the implementation of the integration described above looks like

import requests
from socless import socless_bootstrap

def handle_state(ip):
    r = requests.get("https://tools.keycdn.com/geo.json", params={"host": ip})
    geoip_info = r.json()['data']['geo']
    desired_results = {
        "country": geoip_info['country_name'],
        "latitude": str(geoip_info['latitude']),
        "longitude": str(geoip_info['longitude'])
        }
    return desired_results


def lambda_handler(event, context):
    return socless_bootstrap(event, context, handle_state)

In the socless-tutorial/functions directory, create a subdirectory called geoip and save the implementation in a file called lambda_function.py. That’s all the code we need to write for our Integrations logic.

Specify Dependencies

Notice that our integration makes use of the Python requests library which isn’t one of the libraries pre-packaged with AWS Lambda. As such, we’ll need to specify requests as a dependency with our deployment package. To do so, open the pre-existing requirements.txt file in the functions directory, and add requests at the bottom of the file. Your file should end up looking like this:

git+https://github.com/twilio-labs/socless_python.git#egg=socless
requests

The first dependency in the file is the Socless Python library that provides the socless_bootstrap function. Any dependency listed in the functions/requirements.txt file gets deployed will all functions in the functions folder. To learn more about configuring dependencies for functions, visit the Socless & Serverless documentation page

Our integration’s implementation is complete and our dependencies have been specified. The last thing we need to do is configure the function for deployment.

Configuring our integration for Deployment

Socless Integrations are deployed using the Serverless Framework. To deploy our integration, open the serverless.yml file and configure a function that:

  • is reference-able in our config as GeoIP
  • is named socless_tutorial_geoip
  • has a handler at lambda_function.lambda_handler
  • has the description: "Integration to geolocate an IP address"
  • creates its source package from functions/geoip

Here's what that configuration looks like:

functions:
  GeoIP:
    name: socless_tutorial_geoip
    handler: lambda_function.lambda_handler
    description: Integration to geolocate an ip
    package:
      include:
        - functions/geoip

Next, we need to ensure that the AWS ARN of the function is output from the deployment stack after deployment. Doing so will allow the arn to be referenced by the playbook we will write shortly. To accomplish this, include the below configuration in the serverless.yml file, starting on the same indentation level as the functions key-word in the file

resources:
  Outputs:
    GeoIP:
      Description: ARN of GeoIP integration
      Value:
        Fn::Sub: ${GeoIPLambdaFunction.Arn}     

With that, our Integration is fully configured for deployment.

Deploying the Integration

If your Python 3.7 virtual environment is no longer active, reactivate it by running the command . venv/bin/activate from within your socless-tutorial directory.

Now, deploy the integration be executing:

npm run dev

Once the deployment is complete, you should see the ARN for the Integration's Lambda function displayed. You don't need to note this ARN down as we'll reference it by the GeoIP name whenever we need it.

Testing the Integration

Log into the AWS console, navigate to the Lambda service in your Socless dev region, and open the Lambda function for the integration we just deployed (socless_tutorial_geoip).

Click Test, and configure the below test event:

{
    "_testing": true,
    "State_Config": {
        "Name": "Test_State",
        "Parameters": {
            "ip": "113.63.125.3"
        }
    }
}

Observe that the key-value pair in the Parameters object maps exactly to the parameters we specified for the handle_state function of our integration. This test event simulates a small subset of the data that the integration would receive when it's called by an actual playbook execution.

Give the test event any name of your choosing then hit create. Once the test event is created, click Test to test the integration. If the test executes successfully, you'll have an Execution result like below which shows the test event and the resulting geolocated IP in a results object.

{
  "_testing": true,
  "State_Config": {
    "Name": "Test_State",
    "Parameters": {
      "ip": "113.63.125.3"
    }
  },
  "results": {
    "country": "China",
    "latitude": '29.65',
    "longitude": '91.1'
  }
}

Feel free to change the IP address in your test case and play around a little.

Conclusion

Congratulations! You've written, deployed and tested your first integration! Pat yourself on the back, take a breather, then head to the next page to write your second integration