Deploy your source maps to Rollbar with Ansible

Posted on 2018-07-16 in Trucs et astuces

I recently had to integrate Rollbar (a service to track errors) with a JS application. One nice thing about Rollbar is that if you provide it with source maps, it will point to you where the error is in the original unminified code.

Since the application is deployed with Ansible, I wanted to upload the source maps with Ansible. There is a built in module to register deploys with Ansible: rollbar_deployment but that only solves half the problem since it only registers deployments. Example of usage:

- name: Register deploy
  rollbar_deployment:
    token: "{{ lookup('env','ROLLBAR_API_KEY') }}"
    environment: "{{ env }}"
    revision: "{{ version }}"

with:

  • {{ env }} the environment you are deploying to.
  • {{ version }} the revision you want to register.
  • You also need the ROLLBAR_API_KEY to be set and to contain your Rollbar server API key so you can authenticate to Rollbar.

Since I didn't found a module already done, I decide to build my own.

Below is the code of the Ansible module. Place it under library/rollbar_source_maps.py in your Ansible project. Note: it only works with Python 3+ and requires the requests library.

You will then be able to use it with:

- hosts: fronts
  tasks:
    - name: Upload source maps to Rollbar
      connection: local
      rollbar_source_maps:
        src: "{{ src }}/dist/scripts"
        base_url: "{{ base_url }}"
        version: "{{ version }}"
        token: "{{ lookup('env','ROLLBAR_API_KEY') }}"

Where:

  • {{ src }} points to the root of your JS project. The src key in the YAML must point to the folder that contains your source maps. Feel free to adapt to your path.
  • {{ base_url }} points to the base URL of your scripts. So, if they are all located under http://aot.testing/dist/scripts/ that what you must use here.
  • {{ version }} is the current version of the code you are deploying so Rollbar can track your deploys and help you find the one that caused a problem.
  • You also need the ROLLBAR_API_KEY to be set and to contain your Rollbar server API key so you can authenticate to Rollbar.
#!/usr/bin/python3

# Copyright 2018, Julien Enselme, <jujens@jujens.eu>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

ANSIBLE_METADATA = {
    'metadata_version': '1.0',
    'status': ['preview'],
    'supported_by': 'community',
}

DOCUMENTATION = '''
---
module: rollbar_source_maps
version_added: 1.0
author: "Julien Enselme (@Jenselme)"
short_description: Upload source maps to Rollbar
description:
- Upload source maps from a folder to Rollbar. We associate scripts and source maps thanks to the base URL and the name of the files. A source map must have the same name as the files and ends with .map
    (see https://docs.rollbar.com/docs/source-maps)
options:
token:
    description:
    - Your project access token.
    required: true
src:
    description:
    - The directory in which to look for source maps.
    required: true
base_url:
    description:
    - The base URL of your site so Rollbar can have the mapping of a file on the site to a file on the local system.
    required: true
version:
    description:
    - The version of the source maps.
    required: true
url:
    description:
    - Optional URL to submit the notification to.
    required: false
    default: 'https://api.rollbar.com/api/1/sourcemap'
validate_certs:
    description:
    - If C(no), SSL certificates for the target url will not be validated.
        This should only be used on personally controlled sites using
        self-signed certificates.
    required: false
    default: 'yes'
    choices: ['yes', 'no']
'''

EXAMPLES = '''
- name: Upload source maps to Rollbar
rollbar_source_maps:
    src: dist
    base_url: http://www.example.com/scripts/
    version: v1.0.0
    token: AAAA
'''

import os
import traceback

from pathlib import Path
from urllib.parse import urljoin

import requests

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils._text import to_native
from ansible.module_utils.urls import fetch_url


def main():

    module = AnsibleModule(
        argument_spec=dict(
            token=dict(required=True),
            src=dict(required=True),
            base_url=dict(required=True),
            version=dict(required=True),
            url=dict(
                required=False,
                default='https://api.rollbar.com/api/1/sourcemap'
            ),
            validate_certs=dict(default='yes', type='bool'),
        ),
        supports_check_mode=True,
    )

    if module.check_mode:
        module.exit_json(changed=True)

    params = dict(
        access_token=module.params['token'],
    )

    base_url = module.params.get('base_url')
    version = module.params.get('version')
    url = module.params.get('url')
    src = module.params.get('src')

    for file in Path(src).glob('*.js.map'):
        with file.open('rb') as source_map:
            source_map_content = source_map.read()

        minified_url = urljoin(base_url, file.name).replace('.map', '')

        try:
            resp = requests.post(url, files={
                'source_map': source_map_content,
            }, data={
                'access_token': module.params.get('token'),
                'minified_url': minified_url,
                'version': version,
            })
        except Exception as e:
            module.fail_json(msg='Unable to upload source maps to Rollbar: %s' % to_native(e), exception=traceback.format_exc())
        else:
            if resp.status_code != 200:
                module.fail_json(msg='HTTP result code: %d connecting to %s with error %s' % (resp.status_code, url, resp.text))

    module.exit_json(changed=True)


if __name__ == '__main__':
    main()