From c3727571047f0ea97c7f7f4c277a42078048247b Mon Sep 17 00:00:00 2001 From: Rory Finnegan Date: Mon, 22 Dec 2014 23:01:35 -0600 Subject: [PATCH] Added an Ansible plugin, which runs a playbook on the chroot before before build completion. NOTE: I'm not doing any validation on the opt_flags param and I don't recommend using for more then adding a -vvvv. Also, I'm purposely excluding the vault flags (which also pretty commonly used) because you shouldn't be baking private keys and certs into your images. Instead, just avoid running the vault specific code or use the opt_flags if absolutely necessary. --- bootstrapvz/plugins/ansible/__init__.py | 13 +++ .../plugins/ansible/manifest-schema.yml | 29 ++++++ bootstrapvz/plugins/ansible/tasks.py | 98 +++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 bootstrapvz/plugins/ansible/__init__.py create mode 100644 bootstrapvz/plugins/ansible/manifest-schema.yml create mode 100644 bootstrapvz/plugins/ansible/tasks.py diff --git a/bootstrapvz/plugins/ansible/__init__.py b/bootstrapvz/plugins/ansible/__init__.py new file mode 100644 index 0000000..f060ff3 --- /dev/null +++ b/bootstrapvz/plugins/ansible/__init__.py @@ -0,0 +1,13 @@ +import tasks + + +def validate_manifest(data, validator, error): + import os.path + schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml')) + validator(data, schema_path) + + +def resolve_tasks(taskset, manifest): + taskset.add(tasks.AddPackages) + taskset.add(tasks.CheckPlaybookPath) + taskset.add(tasks.RunAnsiblePlaybook) diff --git a/bootstrapvz/plugins/ansible/manifest-schema.yml b/bootstrapvz/plugins/ansible/manifest-schema.yml new file mode 100644 index 0000000..0fc2f57 --- /dev/null +++ b/bootstrapvz/plugins/ansible/manifest-schema.yml @@ -0,0 +1,29 @@ +--- +$schema: http://json-schema.org/draft-04/schema# +title: Ansible plugin manifest +type: object +properties: + plugins: + type: object + properties: + ansible: + type: object + properties: + extra_vars: {type: string} + tags: {type: string} + skip_tags: {type: string} + opt_flags: + type: array + flag: {type: string} + minItems: 1 + hosts: + type: array + host: {type: string} + minItems: 1 + playbook: {$ref: '#/definitions/absolute_path'} + required: + - playbook +definitions: + absolute_path: + pattern: ^/[^\0]+$ + type: string diff --git a/bootstrapvz/plugins/ansible/tasks.py b/bootstrapvz/plugins/ansible/tasks.py new file mode 100644 index 0000000..9d1c893 --- /dev/null +++ b/bootstrapvz/plugins/ansible/tasks.py @@ -0,0 +1,98 @@ +from bootstrapvz.base import Task +from bootstrapvz.common import phases +from bootstrapvz.common.tasks import apt +import os + + +class CheckPlaybookPath(Task): + description = 'Checking whether the playbook path exist' + phase = phases.preparation + + @classmethod + def run(cls, info): + from bootstrapvz.common.exceptions import TaskError + playbook = info.manifest.plugins['ansible']['playbook'] + if not os.path.exists(playbook): + msg = 'The playbook file {playbook} does not exist.'.format(playbook=playbook) + raise TaskError(msg) + if not os.path.isfile(playbook): + msg = 'The playbook path {playbook} does not point to a file.'.format(playbook=playbook) + raise TaskError(msg) + + +class AddPackages(Task): + description = 'Making sure python is installed' + phase = phases.preparation + predecessors = [apt.AddDefaultSources] + + @classmethod + def run(cls, info): + info.packages.add('python') + + +class RunAnsiblePlaybook(Task): + description = 'Running ansible playbooks' + phase = phases.system_modification + + @classmethod + def run(cls, info): + from bootstrapvz.common.tools import log_check_call + + # Extract playbook and directory + playbook = info.manifest.plugins['ansible']['playbook'] + playbook_dir = os.path.dirname(os.path.realpath(playbook)) + + # Check for hosts + hosts = None + if 'hosts' in info.manifest.plugins['ansible']: + hosts = info.manifest.plugins['ansible']['hosts'] + + # Check for extra vars + extra_vars = None + if 'extra_vars' in info.manifest.plugins['ansible']: + extra_vars = info.manifest.plugins['ansible']['extra_vars'] + + tags = None + if 'tags' in info.manifest.plugins['ansible']: + tags = info.manifest.plugins['ansible']['tags'] + + skip_tags = None + if 'skip_tags' in info.manifest.plugins['ansible']: + skip_tags = info.manifest.plugins['ansible']['skip_tags'] + + opt_flags = None + if 'opt_flags' in info.manifest.plugins['ansible']: + opt_flags = info.manifest.plugins['ansible']['opt_flags'] + + # build the inventory file + inventory = os.path.join(info.root, 'tmp/bootstrap-inventory') + with open(inventory, 'w') as handle: + conn = '{} ansible_connection=chroot'.format(info.root) + content = "" + + if hosts: + for host in hosts: + content += '[{}]\n{}\n'.format(host, conn) + else: + content = conn + + handle.write(content) + + # build the ansible command + cmd = ['ansible-playbook', '-i', inventory, os.path.basename(playbook)] + if extra_vars: + tmp_cmd = ['--extra-vars', '\"{}\"'.format(extra_vars)] + cmd.extend(tmp_cmd) + if tags: + tmp_cmd = ['--tags={}'.format(tags)] + cmd.extend(tmp_cmd) + if skip_tags: + tmp_cmd = ['--skip_tags={}'.format(skip_tags)] + cmd.extend(tmp_cmd) + if opt_flags: + # Should probably do proper validation on these, but I don't think it should be used very often. + cmd.extend(opt_flags) + + # Run and remove the inventory file + log_check_call(cmd, cwd=playbook_dir) + os.remove(inventory)