Enables whitelisting and configuring callbacks

Change-Id: Ida7b84795d922b85ec9cc6161ab1203fb82da825
This commit is contained in:
Albin Vass 2020-04-03 10:59:00 +02:00 committed by Albin Vass
parent d873dc84ef
commit 518cf7fe5e
9 changed files with 212 additions and 0 deletions

View File

@ -845,6 +845,31 @@ The following sections of ``zuul.conf`` are used by the executor:
Value to pass to `git config user.name
<https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup>`_.
.. attr:: ansible_callback "<name>"
To whitelist ansible callback ``<name>``. Any attributes found is this section
will be added to the ``callback_<name>`` section in ansible.cfg.
An example of what configuring the builtin mail callback would look like.
The configuration in zuul.conf.
.. code-block:: ini
[ansible_callback "mail"]
to = user@example.org
sender = zuul@example.org
Would generate the following in ansible.cfg:
.. code-block:: ini
[defaults]
callback_whitelist = mail
[callback_mail]
to = user@example.org
sender = zuul@example.org
Operation
~~~~~~~~~

View File

@ -0,0 +1,5 @@
---
features:
- |
Zuul now supports whitelisting and configuring ansible callbacks with
:attr:`ansible_callback "<name>"`.

View File

@ -0,0 +1,4 @@
- hosts: localhost
gather_facts: smart
tasks:
- command: echo test

View File

@ -0,0 +1,35 @@
from ansible.plugins.callback import CallbackBase
import os
DOCUMENTATION = '''
options:
file_name:
description: ""
ini:
- section: callback_test_callback
key: file_name
required: True
type: string
'''
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 1.0
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
super(CallbackModule, self).__init__()
def set_options(self, task_keys=None, var_options=None, direct=None):
super(CallbackModule, self).set_options(task_keys=task_keys,
var_options=var_options,
direct=direct)
self.file_name = self.get_option('file_name')
def v2_on_any(self, *args, **kwargs):
path = os.path.join(os.path.dirname(__file__), self.file_name)
self._display.display("Touching file: {}".format(path))
with open(path, 'w'):
pass

View File

@ -0,0 +1,21 @@
- pipeline:
name: promote
manager: supercedent
post-review: true
trigger:
gerrit:
- event: change-merged
- job:
name: callback-test
parent: null
run: playbooks/callback.yaml
nodeset:
nodes:
- name: ubuntu-xenial
label: ubuntu-xenial
- project:
promote:
jobs:
- callback-test

View File

@ -0,0 +1,6 @@
- tenant:
name: tenant-one
source:
gerrit:
config-projects:
- common-config

View File

@ -0,0 +1,48 @@
# Checks to make sure no key is configured in the
# [defaults] section of ansible.cfg, setting the
# same key twice would cause an error.
# Equal sign in section name will not be treated as configuration field in ansible
[ansible_callback "nocows = True"]
[ansible_callback "nocows = False"]
# \n will not be treated as a newline character
[ansible_callback "\nnocows = True"]
[ansible_callback "\nnocows = False"]
# A single '%' sign would cause error if interpolation syntax is enabled
[ansible_callback "ansible_interpolation"]
test_field = test-%%-value
[ansible_callback "test_callback"]
file_name = callback-success
[gearman]
server=127.0.0.1
[statsd]
# note, use 127.0.0.1 rather than localhost to avoid getting ipv6
# see: https://github.com/jsocol/pystatsd/issues/61
server=127.0.0.1
[scheduler]
tenant_config=main.yaml
[merger]
git_dir=/tmp/zuul-test/merger-git
git_user_email=zuul@example.com
git_user_name=zuul
[executor]
git_dir=/tmp/zuul-test/executor-git
[connection gerrit]
driver=gerrit
server=review.example.com
user=jenkins
sshkey=fake_id_rsa_path
[connection smtp]
driver=smtp
server=localhost
port=25
default_from=zuul@example.com
default_to=you@example.com

View File

@ -15,6 +15,7 @@
import json
import logging
import configparser
import multiprocessing
import os
import time
@ -816,6 +817,50 @@ class TestExecutorFacts(AnsibleZuulTestCase):
self.assertEqual(18, len(date_time))
class TestAnsibleCallbackConfigs(AnsibleZuulTestCase):
config_file = 'zuul-executor-ansible-callback.conf'
tenant_config_file = 'config/ansible-callbacks/main.yaml'
def test_ansible_callback_config(self):
self.executor_server.keep_jobdir = True
A = self.fake_gerrit.addFakeChange('common-config', 'master', 'A')
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
callbacks = [
'callback_test_callback',
'callback_nocows = True',
'callback_nocows = False',
'callback_\\nnocows = True',
'callback_\\nnocows = False',
'callback_ansible_interpolation'
]
p = os.path.join(self.getJobFromHistory('callback-test').jobdir.root,
'ansible/playbook_0/ansible.cfg')
self.assertEqual(self.getJobFromHistory('callback-test').result,
'SUCCESS')
c = configparser.ConfigParser(interpolation=None)
c.read(p)
for callback in callbacks:
self.assertIn(callback, c.sections())
self.assertIn('test_field', c['callback_ansible_interpolation'])
self.assertIn('test-%-value',
c['callback_ansible_interpolation']['test_field'])
self.assertIn('file_name', c['callback_test_callback'])
self.assertEqual('callback-success',
c['callback_test_callback']['file_name'])
callback_result_file = os.path.join(
self.getJobFromHistory('callback-test').jobdir.root,
'trusted/project_0/review.example.com/',
'common-config/playbooks/callback_plugins/',
c['callback_test_callback']['file_name'])
self.assertTrue(os.path.isfile(callback_result_file))
class TestExecutorEnvironment(AnsibleZuulTestCase):
tenant_config_file = 'config/zuul-environment-filter/main.yaml'

View File

@ -861,6 +861,7 @@ class AnsibleJob(object):
self.callback_dir = os.path.join(plugin_dir, 'callback')
self.lookup_dir = os.path.join(plugin_dir, 'lookup')
self.filter_dir = os.path.join(plugin_dir, 'filter')
self.ansible_callbacks = self.executor_server.ansible_callbacks
def run(self):
self.running = True
@ -2076,6 +2077,11 @@ class AnsibleJob(object):
# and reduces CPU load of the ansible process.
config.write('internal_poll_interval = 0.01\n')
if self.ansible_callbacks:
config.write('callback_whitelist =\n')
for callback in self.ansible_callbacks.keys():
config.write(' %s,\n' % callback)
config.write('[ssh_connection]\n')
# NOTE(pabelanger): Try up to 3 times to run a task on a host, this
# helps to mitigate UNREACHABLE host errors with SSH.
@ -2095,6 +2101,12 @@ class AnsibleJob(object):
"-o UserKnownHostsFile=%s" % self.jobdir.known_hosts
config.write('ssh_args = %s\n' % ssh_args)
if self.ansible_callbacks:
for cb_name, cb_config in self.ansible_callbacks.items():
config.write("[callback_%s]\n" % cb_name)
for k, n in cb_config.items():
config.write("%s = %s\n" % (k, n))
def _ansibleTimeout(self, msg):
self.log.warning(msg)
self.abortRunningProc()
@ -2551,6 +2563,17 @@ class ExecutorServer(BaseMergeServer):
'ansible_setup_timeout', 60))
self.zone = get_default(self.config, 'executor', 'zone')
self.ansible_callbacks = {}
for section_name in self.config.sections():
cb_match = re.match(r'^ansible_callback ([\'\"]?)(.*)(\1)$',
section_name, re.I)
if not cb_match:
continue
cb_name = cb_match.group(2)
self.ansible_callbacks[cb_name] = dict(
self.config.items(section_name)
)
# TODO(tobiash): Take cgroups into account
self.update_workers = multiprocessing.cpu_count()
self.update_threads = []