Ansible version : 2.0.0.1
I've been looking around quite a bit now, and most documentation I find is either incomplete or deprecated (this post is for version 1.8.4, ie)
I'm trying to launch an Ansible playbook through the Python API. Ansible's documentation seem to be showing how to generate and play tasks, but not how to load and run a playbook yml file. I've been digging into the code to try to understand how to launch it, and I think I've done some progress, but I'm really hitting a wall. Here's what I have so far :
def createcluster(region, environment, cluster):
Options = namedtuple('Options', ['region','env', 'cluster'])
# initialize needed objects
variable_manager = VariableManager()
loader = DataLoader()
options = Options(region=region, env=environment, cluster=cluster)
options.listhosts = False
vault_password = getpass.getpass('Enter vault password :')
passwords = dict(vault_pass=vault_password)
#Getting hosts
hostsread = open('provisioning/inventory/hosts','r')
hosts = hostsread.read()
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=hosts)
variable_manager.set_inventory(inventory)
#Create and load the playbook file
playbook = Playbook(loader)
playbook.load('provisioning/cluster.yml', variable_manager,loader)
#Create an executor to launch the playbook ?
executor = None
executor = PlaybookExecutor(playbook,inventory,variable_manager,loader,options,passwords)
try:
result = executor.run()
finally:
if executor is not None:
executor.cleanup()
I'm not sure at all about the executor part, and I keep getting a "AttributeError: 'Options' object has no attribute 'listhosts'" error when I try to launch the code (weirdly enough as it should just ignore its absence, I think (line 60))
How am I supposed to load a YML file and launch it through the Python API ? Am I on the good path or did I lose myself ? Why isn't Ansible better documented ? Why would 42 be the answer to 7*7 ?
Here is an example with Ansible 2:
#!/usr/bin/python2
from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
from ansible.inventory import Inventory
from ansible.playbook import Playbook
from ansible.executor.playbook_executor import PlaybookExecutor
Options = namedtuple('Options', ['connection', 'forks', 'become', 'become_method', 'become_user', 'check', 'listhosts', 'listtasks', 'listtags', 'syntax', 'module_path'])
variable_manager = VariableManager()
loader = DataLoader()
options = Options(connection='local', forks=100, become=None, become_method=None, become_user=None, check=False, listhosts=False, listtasks=False, listtags=False, syntax=False, module_path="")
passwords = dict(vault_pass='secret')
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list='localhost')
variable_manager.set_inventory(inventory)
playbooks = ["./test.yaml"]
executor = PlaybookExecutor(
playbooks=playbooks,
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
options=options,
passwords=passwords)
executor.run()
Tested with Python 2.7.10 and ansible 2.0.1.0
Posting for completion.
I had trouble setting verbosity for ansible 2.4. I will mainly talk about that.
Ansible use a global Display
object in the __main__
file (the one you launch) if it doesn't exist some imports will create it.
This is considered a bad practice and not PEP8 compliant (second bullet point)
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
It is called in almost every file (108). Like so you have a new display in your entry point and then all other module will retrieve this first declared display.
You just have to declare a Display object like so:
from ansible.utils.display import Display
display = Display(verbosity=5)
You can alternatively use this after: display.verbosity = 1000
I wanted to be able to completely remove ansible output (negative value = no output)
I ended up creating a new class like so:
from ansible.utils.display import Display
class AnsibleDisplay(Display):
'''
This class override the display.display() function
'''
def display(self, *args, **kwargs):
if self.verbosity >= 0:
super(AnsibleDisplay, self).display(*args, **kwargs)
Then import it in my __main__
file
# Ansible call this global object for display
sys.path.append(ROOT_DIR + os.sep + 'lib')
from ansible_display import AnsibleDisplay
display = AnsibleDisplay(verbosity=0)
And only after import all other modules
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
## For ansible
import json
from collections import namedtuple
# Ansible call this global object for display
sys.path.append(ROOT_DIR + os.sep + 'lib')
from ansible_display import AnsibleDisplay
display = AnsibleDisplay(verbosity=0)
# Load other libs after to make sure they all use the above 'display'
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.playbook_executor import PlaybookExecutor
def apply_verbosity(args):
global display
verb = -1 if args.verbosity is None else args.verbosity
display.verbosity = verb
def ansible_part():
playbook_path = "%s/ansible/main_playbook.yml" % (ROOT_DIR)
inventory_path = "%s/watev/my_inventory.ini"
Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff', 'listhosts', 'listtasks', 'listtags', 'syntax'])
# initialize needed objects
loader = DataLoader()
options = Options(connection='local', module_path='%s/' % (ROOT_DIR), forks=100, become=None, become_method=None, become_user=None, check=False,
diff=False, listhosts=True, listtasks=False, listtags=False, syntax=False)
passwords = dict(vault_pass='secret')
# create inventory and pass to var manager
inventory = InventoryManager(loader=loader, sources=[inventory_path])
variable_manager = VariableManager(loader=loader, inventory=inventory)
pbex = PlaybookExecutor(playbooks=[playbook_path], inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=passwords)
results = pbex.run()
def main():
ansible_part()
listhosts=True, listtasks=False, listtags=False, syntax=False
import __main__
makes debugging impractical because when using debugger (in my case pudb), the __main__
file is the debugger file hence from __main__ import display
will never workhth
[Edit1]: added note
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With