How to set "--limit" option in Ansible playbook api - ansible

I'm writing python script to run ansible playbook, using Ansible 2.4.2.0.
As I know there is an option --limit, which can limit the Ansible play on a specific host.
For example:
Here is the /etc/ansible/hosts
[test]
192.168.0.1
192.168.0.2
Below command will let Ansible only execute test.yml on 192.168.0.1:
ansible-playbook test.yml --limit="192.168.0.1"
I want to know how to set options in ansible playbook api to do the same thing.
I tried add to limit='192.168.0.1 in options, but it doesn't work.
Below is the Python script I used.
from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.executor.playbook_executor import PlaybookExecutor
loader = DataLoader()
inventory = InventoryManager(loader=loader, sources=['/etc/ansible/hosts'])
variable_manager = VariableManager(loader=loader, inventory=inventory)
Options = namedtuple('Options', ['listtags', 'listtasks', 'listhosts', 'syntax', 'connection','module_path', 'forks', 'remote_user', 'become', 'become_method', 'become_user', 'verbosity', 'check', 'diff', 'ask_sudo_pass', 'limit'])
options = Options(listtags=None, listtasks=None, listhosts=None, syntax=None, connection='smart', module_path=None, forks=100, remote_user=None, become=None, become_method='sudo', become_user='root', verbosity=None, check=False, diff=False, ask_sudo_pass=None, limit='192.168.0.1')
passwords = {}
pbex = PlaybookExecutor(playbooks=['/home/test.yml'], inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=passwords)
pbex.run()

Ansible is opensourced, so you can always peek into the existing code.
It's here in ansible-playbook CLI code:
inventory.subset(self.options.subset)
if len(inventory.list_hosts()) == 0 and no_hosts is False:
# Invalid limit
raise AnsibleError("Specified --limit does not match any hosts")
Source: Ansible's code source
So, your case, after your instantiation of InventoryManager, you should add:
inventory.subset('192.168.0.1')

Related

Pass Ansible variables into custom Ansible module

I have a custom module that resides in the library/ directory of my Ansible role. I can call the module from within my playbook, and the code executes correctly, but only if the values it expects are hardcoded in the module code itself. How can I pass values to the module from the playbook?
I've tried the following:
- name: Create repo and use specific KMS key
ecr_kms:
repositoryName: "new-ecr-repo"
encryptionConfiguration.kmsKey: 'my-kms-key-id"
and
- name: Create repo and use specific KMS key
ecr_kms:
repositoryName: "{{ repo_name }}"
encryptionConfiguration.kmsKey: "{{ kms_key_id }}"
Which I would expect to work, but neither does and, I get the following errors:
botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid length for parameter repositoryName, value: 0, valid min length: 2
Invalid length for parameter encryptionConfiguration.kmsKey, value: 0, valid min length: 1
The service module I'm trying to use
The code of the custom module:
#!/usr/bin/python
from urllib import response
import boto3
from jinja2 import Template
from ansible.module_utils.basic import AnsibleModule
def create_repo():
client = boto3.client('ecr')
response = client.create_repository(
#registryId='',
repositoryName='',
imageTagMutability='IMMUTABLE',
imageScanningConfiguration={
'scanOnPush': True
},
encryptionConfiguration={
'encryptionType': 'KMS',
'kmsKey': ""
}
)
def main():
create_repo()
if __name__ in '__main__':
main()
You do need to make your module aware of the arguments you want it to accept, so, in your main function:
#!/usr/bin/env python
from ansible.module_utils.basic import AnsibleModule
def create_repo(repositoryName, kmsKey):
# Call to the API comes here
def main():
module = AnsibleModule(
argument_spec = dict(
repositoryName = dict(type = 'str', required = True),
kmsKey = dict(type = 'str', required = True),
)
)
params = module.params
create_repo(
params['repositoryName'],
params['kmsKey']
)
if __name__ == '__main__':
main()
More can be found in the relevant documentation: Argument spec.
With this, your taks would be:
- name: Create repo and use specific KMS key
ecr_kms:
repositoryName: "{{ repo_name }}"
kmsKey: "{{ kms_key_id }}"
PS, word of advice: avoid using a dot in a YAML key, that would just be making your life complicated for no actual good reason.

ansible.inventory.Inventory class replacement from Ansible 2.4+ onwards

I'm currently using ansible 2.3.3 and trying to upgrade it to a newever version but since 2.4+ the ansible.inventory.Inventory class has been removed.
Does anybody know what is the equivalent recommended replacement of ansible.inventory.Inventory in Ansible 2.4+?
from ansible.inventory import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
inv = Inventory(
loader = DataLoader(),
variable_manager = VariableManager(),
host_list = my_path,
)
There isn't a one to one replacement in the newer code but the following code can be used but note that the inventory now has a different data structure
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
m = InventoryManager(loader=DataLoader(), sources='/path/to/inventory')
m.hosts
m.groups

Generate list of IP addresses from start and end values with Ansible

Is there a way to generate a list of IP addresses between two arbitrary IPs (not from a subnet/range) with Ansible (v2.9)?
I've searched and the ipaddr filter looks like a good candidate, but from the documentation I couldn't figure out if it supports this.
I'm looking for a solution that allows me to get a list like
[ '10.0.0.123', '10.0.0.124', ... , '10.0.1.23' ]
from a task like
- name: generate IP list
set_fact:
ip_list: "{{ '10.0.0.123' | ipaddr_something('10.0.1.23') }}"
Create a filter plugin. For example
shell> cat filter_plugins/netaddr.py
import netaddr
def netaddr_iter_iprange(ip_start, ip_end):
return [str(ip) for ip in netaddr.iter_iprange(ip_start, ip_end)]
class FilterModule(object):
''' Ansible filters. Interface to netaddr methods.
https://pypi.org/project/netaddr/
'''
def filters(self):
return {
'netaddr_iter_iprange' : netaddr_iter_iprange,
}
Then, the task below shall create the list
- set_fact:
ip_list: "{{ '10.0.0.123'|netaddr_iter_iprange('10.0.1.23') }}"

Ansible callback plugin: how to get play attribute values with variables expanded?

I have a play below and am trying to get the resolved value of the remote_user attribute inside the callback plugin.
- name: test play
hosts: "{{ hosts_pattern }}"
strategy: free
gather_facts: no
remote_user: "{{ my_remote_user if my_remote_user is defined else 'default_user' }}"
tasks:
- name: a test task
shell: whoami && hostname
I am currently accessing the play field attribute as follows:
def v2_playbook_on_play_start(self, play):
self._play_remote_user = play.remote_user
And I also tried saving the remote_user within v2_playbook_on_task_start to see if this does the trick, as this is where the templated task name is made available.
def v2_playbook_on_task_start(self, task, is_conditional):
self._tasks[task._uuid].remote_user = task.remote_user
self._tasks[task._uuid].remote_user_2 = task._get_parent_attribute('remote_user')
However all cases above give me {{ my_remote_user if my_remote_user is defined else 'default_user' }} instead of the expanded/resolved value.
In general, is there a neat way to get a collection of all play attributes with resolved values as defined in the playbook?
Happily much easier for action plugins.
ActionBase class has templar and loader properties already.
One can iterate over task_vars and render all with Templar.template
for k in task_vars:
new_module_args = merge_hash(
new_module_args,
{k: self._templar.template(task_vars.get(k, None))}
)
and call module
result = self._execute_module(
module_name='my_module',
task_vars=task_vars,
module_args=new_module_args
)
I don't think there is an easy way to achieve this.
PlayContext is templated inside task_executor here.
And this happens after all callback methods are already notified.
So you should use Templar class manually (but I'm not sure you can get correct variables context for it to work correctly).
Credit goes Konstantin's tip to use the Templar class.
I came up with a solution for Ansible 2.3.1 - not entirely sure if it's the optimum one but it seems to work. This is an example code:
from ansible.plugins.callback import CallbackBase
from ansible.template import Templar
from ansible.plugins.strategy import SharedPluginLoaderObj
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'your_name'
def __init__(self):
super(CallbackModule, self).__init__()
# other shenanigans
def v2_playbook_on_start(self, playbook):
self.playbook = playbook
def v2_playbook_on_play_start(self, play):
self.play = play
def _all_vars(self, host=None, task=None):
# host and task need to be specified in case 'magic variables' (host vars, group vars, etc) need to be loaded as well
return self.play.get_variable_manager().get_vars(
loader=self.playbook.get_loader(),
play=self.play,
host=host,
task=task
)
def v2_runner_on_ok(self, result):
templar = Templar(loader=self.playbook.get_loader(),
shared_loader_obj=SharedPluginLoaderObj(),
variables=self._all_vars(host=result._host, task=result._task))
remote_user = templar.template(self.play.remote_user)
# do something with templated remote_user

How and where to define a custom jinja2 test

I know this is obvious to everybody but me.
But where and how do I define a custom jinja2 test?
I tried specifying in my flask:
import flask
import jinja2
from jinja2 import environment as env
from jinja2 import *
app = Flask(__name__)
app.config.from_object(__name__)
app = Flask(__name__)
# jinja2 filter
def isList(value):
return isinstance(value, list)
env.tests['isList'] = isList
The resulting error is:
AttributeError: 'module' object has no attribute 'tests'
The configured Jinja2 environment for Flask applications is app.jinja_env. If you change the last line in your code to
app.jinja_env.tests['isList'] = isList
it should work properly.

Resources