Skip to content

Instantly share code, notes, and snippets.

@pgasiorowski
Created November 15, 2016 08:24
Show Gist options
  • Save pgasiorowski/14809c4d08baa4364c8fbd6f1a5c63e3 to your computer and use it in GitHub Desktop.
Save pgasiorowski/14809c4d08baa4364c8fbd6f1a5c63e3 to your computer and use it in GitHub Desktop.
Run ansible in AWS Lambda
#!/usr/bin/env python2
# Based on: https://serversforhackers.com/running-ansible-2-programmatically
import os
import sys
from ansible.executor import playbook_executor
from ansible.inventory import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
class Options(object):
"""
Options class to replace Ansible OptParser
"""
def __init__(self, **kwargs):
props = (
'ask_pass', 'ask_sudo_pass', 'ask_su_pass', 'ask_vault_pass',
'become_ask_pass', 'become_method', 'become', 'become_user',
'check', 'connection', 'diff', 'extra_vars', 'flush_cache',
'force_handlers', 'forks', 'inventory', 'listhosts', 'listtags',
'listtasks', 'module_path', 'module_paths',
'new_vault_password_file', 'one_line', 'output_file',
'poll_interval', 'private_key_file', 'python_interpreter',
'remote_user', 'scp_extra_args', 'seconds', 'sftp_extra_args',
'skip_tags', 'ssh_common_args', 'ssh_extra_args', 'subset', 'sudo',
'sudo_user', 'syntax', 'tags', 'timeout', 'tree',
'vault_password_files', 'verbosity')
for p in props:
if p in kwargs:
setattr(self, p, kwargs[p])
else:
setattr(self, p, None)
class Runner(object):
def __init__(self, playbook, hosts='hosts', options={}, passwords={}, vault_pass=None):
# Set options
self.options = Options()
for k, v in options.iteritems():
setattr(self.options, k, v)
# Executor has its own verbosity setting
playbook_executor.verbosity = self.options.verbosity
# Gets data from YAML/JSON files
self.loader = DataLoader()
# Set vault password
if vault_pass is not None:
self.loader.set_vault_password(vault_pass)
elif 'VAULT_PASS' in os.environ:
self.loader.set_vault_password(os.environ['VAULT_PASS'])
# All the variables from all the various places
self.variable_manager = VariableManager()
if self.options.python_interpreter is not None:
self.variable_manager.extra_vars = {
'ansible_python_interpreter': self.options.python_interpreter
}
# Set inventory, using most of above objects
self.inventory = Inventory(
loader=self.loader, variable_manager=self.variable_manager,
host_list=hosts)
if len(self.inventory.list_hosts()) == 0:
raise Exception('RUN: Provided hosts list is empty.')
self.inventory.subset(self.options.subset)
if len(self.inventory.list_hosts()) == 0:
raise Exception('RUN: Specified limit does not match any hosts.')
self.variable_manager.set_inventory(self.inventory)
# Setup playbook executor, but don't run until run() called
self.pbex = playbook_executor.PlaybookExecutor(
playbooks=[playbook],
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
passwords=passwords)
def run(self):
# Run Playbook and get stats
self.pbex.run()
stats = self.pbex._tqm._stats
return stats
def main(event, context):
runner = Runner(
playbook='site.yaml',
hosts='hosts',
options=event
)
return runner.run()
if __name__ == '__main__':
main()
@pgreene
Copy link

pgreene commented Jan 10, 2023

Is there a working updated version anywhere? I took this and updated the syntax to work in python3 & the initial error is;

Traceback (most recent call last):
  File "/Users/pgreene/Repos/won/won-devops/terraform/plans/prod/bullhorn-db-hosts-file-upd/dependencies/prod-bullhorn-db-hosts-file-upd.py", line 106, in <module>
    main()
TypeError: main() missing 2 required positional arguments: 'event' and 'context'

Then if you fix that error, it leads to other errors & down the rabbit hole you go.
Here's how I've updated it to work in python3:

#!/usr/bin/env python3

# Based on: https://serversforhackers.com/running-ansible-2-programmatically

import os
import sys

from ansible.executor import playbook_executor
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager

class Options(object):
    """
    Options class to replace Ansible OptParser
    """
    def __init__(self, **kwargs):
        props = (
            'ask_pass', 'ask_sudo_pass', 'ask_su_pass', 'ask_vault_pass',
            'become_ask_pass', 'become_method', 'become', 'become_user',
            'check', 'connection', 'diff', 'extra_vars', 'flush_cache',
            'force_handlers', 'forks', 'inventory', 'listhosts', 'listtags',
            'listtasks', 'module_path', 'module_paths',
            'new_vault_password_file', 'one_line', 'output_file',
            'poll_interval', 'private_key_file', 'python_interpreter',
            'remote_user', 'scp_extra_args', 'seconds', 'sftp_extra_args',
            'skip_tags', 'ssh_common_args', 'ssh_extra_args', 'subset', 'sudo',
            'sudo_user', 'syntax', 'tags', 'timeout', 'tree',
            'vault_password_files', 'verbosity')

        for p in props:
            if p in kwargs:
                setattr(self, p, kwargs[p])
            else:
                setattr(self, p, None)


class Runner(object):
    def __init__(self, playbook, hosts='hosts', options={}, passwords={}, vault_pass=None):

        # Set options
        self.options = Options()
        for k, v in options.iteritems():
            setattr(self.options, k, v)

        # Executor has its own verbosity setting
        playbook_executor.verbosity = self.options.verbosity

        # Gets data from YAML/JSON files
        self.loader = DataLoader()

        # Set vault password
        if vault_pass is not None:
            self.loader.set_vault_password(vault_pass)
        elif 'VAULT_PASS' in os.environ:
            self.loader.set_vault_password(os.environ['VAULT_PASS'])

        # All the variables from all the various places
        self.variable_manager = VariableManager()
        if self.options.python_interpreter is not None:
            self.variable_manager.extra_vars = {
                'ansible_python_interpreter': self.options.python_interpreter
            }

        # Set inventory, using most of above objects
        self.inventory = InventoryManager(
            loader=self.loader, variable_manager=self.variable_manager,
            host_list=hosts)

        if len(self.inventory.list_hosts()) == 0:
            raise Exception('RUN: Provided hosts list is empty.')

        self.inventory.subset(self.options.subset)

        if len(self.inventory.list_hosts()) == 0:
            raise Exception('RUN: Specified limit does not match any hosts.')

        self.variable_manager.set_inventory(self.inventory)

        # Setup playbook executor, but don't run until run() called
        self.pbex = playbook_executor.PlaybookExecutor(
            playbooks=[playbook],
            inventory=self.inventory,
            variable_manager=self.variable_manager,
            loader=self.loader,
            options=self.options,
            passwords=passwords)

    def run(self):
        # Run Playbook and get stats
        self.pbex.run()
        stats = self.pbex._tqm._stats

        return stats

def main(event, context):
    runner = Runner(
        playbook='playbook.yml',
        hosts='hosts',
        options=event
    )
    return runner.run()

if __name__ == '__main__':
    main()
    #main(event=sys.argv[1:],context=sys.argv[1:])

@piotr-lwks
Copy link

These days I would rather use the official Ansible docker image in Lambda, it is much more convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment