diff --git a/README.md b/README.md index 680c26f7..6aa7f732 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,43 @@ from x import resource all_resources = resource.load_all('rs') ``` +## Dry run + +Solar CLI has possibility to show dry run of actions to be performed. +To see what will happen when you run Puppet action, for example, try this: + +``` +solar resource action keystone_puppet run -d +``` + +This should print out something like this: + +``` +EXECUTED: +73c6cb1cf7f6cdd38d04dd2d0a0729f8: (0, 'SSH RUN', ('sudo cat /tmp/puppet-modules/Puppetfile',), {}) +3dd4d7773ce74187d5108ace0717ef29: (1, 'SSH SUDO', ('mv "1038cb062449340bdc4832138dca18cba75caaf8" "/tmp/puppet-modules/Puppetfile"',), {}) +ae5ad2455fe2b02ba46b4b7727eff01a: (2, 'SSH RUN', ('sudo librarian-puppet install',), {}) +208764fa257ed3159d1788f73c755f44: (3, 'SSH SUDO', ('puppet apply -vd /tmp/action.pp',), {}) +``` + +By default every mocked command returns an empty string. If you want it to return +something else (to check how would dry run behave in different situation) you provide +a mapping (in JSON format), something along the lines of: + +``` +solar resource action keystone_puppet run -d -m "{\"73c\": \"mod 'openstack-keystone'\n\"}" +``` + +The above means the return string of first command (with hash `73c6c...`) will be +as specified in the mapping. Notice that in mapping you don't have to specify the +whole hash, just it's unique beginning. Also, you don't have to specify the whole +return string in mapping. Dry run executor can read file and return it's contents +instead, just use the `>` operator when specifying hash: + +``` +solar resource action keystone_puppet run -d -m "{\"73c>\": \"./Puppetlabs-file\"}" +``` + ## Resource compiling You can compile all `meta.yaml` definitions into Python code with classes that diff --git a/main.yml b/main.yml index 34e5d1ee..605492dd 100644 --- a/main.yml +++ b/main.yml @@ -9,6 +9,7 @@ # PIP - apt: name=python-pip state=absent + - apt: name=python-six state=absent - shell: easy_install pip - shell: pip install -U pip - shell: pip install -U setuptools diff --git a/resources/cinder_glance_puppet/meta.yaml b/resources/cinder_glance_puppet/meta.yaml index 8d6a52b3..80b22525 100644 --- a/resources/cinder_glance_puppet/meta.yaml +++ b/resources/cinder_glance_puppet/meta.yaml @@ -17,7 +17,7 @@ input: value: false glance_request_timeout: schema: str - value: '' + value: git: schema: {repository: str!, branch: str!} diff --git a/solar/solar/cli/executors.py b/solar/solar/cli/executors.py new file mode 100644 index 00000000..e67c30a1 --- /dev/null +++ b/solar/solar/cli/executors.py @@ -0,0 +1,55 @@ +from hashlib import md5 + + +class DryRunExecutor(object): + def __init__(self, mapping=None): + from fabric import api as fabric_api + from fabric.contrib import project as fabric_project + import mock + + from solar.core.handlers import puppet + + self.executed = [] + + self.mapping = mapping or {} + + def dry_run_executor(command_name): + def wrapper(*args, **kwargs): + key = (len(self.executed), command_name, args, kwargs) + + self.executed.append(key) + + return self.find_hash(self.compute_hash(key)) + + return wrapper + + # Add your own mocks here, IO, whatever + fabric_api.local = mock.Mock(side_effect=dry_run_executor('LOCAL RUN')) + fabric_api.put = mock.Mock(side_effect=dry_run_executor('PUT')) + fabric_api.run = mock.Mock(side_effect=dry_run_executor('SSH RUN')) + fabric_api.sudo = mock.Mock(side_effect=dry_run_executor('SSH SUDO')) + fabric_project.rsync_project = mock.Mock(side_effect=dry_run_executor('RSYNC PROJECT')) + + def compute_hash(self, key): + return md5(str(key)).hexdigest() + + def find_hash(self, hash): + stripped_hashes = {k.replace('>', ''): k for k in self.mapping} + + hashes = [k for k in stripped_hashes if hash.startswith(k)] + + if len(hashes) == 0: + #raise Exception('Hash {} not found'.format(hash)) + return '' + elif len(hashes) > 1: + raise Exception('Hash {} not unique in {}'.format( + hash, hashes + )) + + hash = stripped_hashes[hashes[0]] + + if hash.endswith('>'): + with open(self.mapping[hash]) as f: + return f.read() + + return self.mapping[hash] diff --git a/solar/solar/cli/main.py b/solar/solar/cli/main.py index 71d3b36b..db525577 100644 --- a/solar/solar/cli/main.py +++ b/solar/solar/cli/main.py @@ -33,12 +33,12 @@ from solar.core import resource as sresource from solar.core.resource import assign_resources_to_nodes from solar.core import signals from solar.core.tags_set_parser import Expression -from solar.core import testing from solar.core.resource import virtual_resource as vr from solar.interfaces.db import get_db from solar import errors from solar.core.log import log +from solar.cli import executors from solar.cli.orch import orchestration from solar.cli.system_log import changes @@ -134,10 +134,15 @@ def init_actions(): @main.command() @click.option('-t', '--tags') @click.option('-a', '--action') - def run(action, tags): + @click.option('-d', '--dry-run', default=False, is_flag=True) + @click.option('-m', '--dry-run-mapping', default='{}') + def run(dry_run_mapping, dry_run, action, tags): from solar.core import actions from solar.core import resource + if dry_run: + dry_run_executor = executors.DryRunExecutor(mapping=json.loads(dry_run_mapping)) + resources = filter( lambda r: Expression(tags, r.get('tags', [])).evaluate(), db.get_list('resource')) @@ -146,6 +151,14 @@ def init_actions(): resource_obj = sresource.load(resource['id']) actions.resource_action(resource_obj, action) + if dry_run: + click.echo('EXECUTED:') + for key in dry_run_executor.executed: + click.echo('{}: {}'.format( + click.style(dry_run_executor.compute_hash(key), fg='green'), + str(key) + )) + def init_cli_connect(): @main.command() @@ -238,7 +251,12 @@ def init_cli_resource(): @resource.command() @click.argument('action') @click.argument('resource') - def action(action, resource): + @click.option('-d', '--dry-run', default=False, is_flag=True) + @click.option('-m', '--dry-run-mapping', default='{}') + def action(dry_run_mapping, dry_run, action, resource): + if dry_run: + dry_run_executor = executors.DryRunExecutor(mapping=json.loads(dry_run_mapping)) + click.echo( 'action {} for resource {}'.format(action, resource) ) @@ -250,6 +268,14 @@ def init_cli_resource(): log.debug(e) sys.exit(1) + if dry_run: + click.echo('EXECUTED:') + for key in dry_run_executor.executed: + click.echo('{}: {}'.format( + click.style(dry_run_executor.compute_hash(key), fg='green'), + str(key) + )) + @resource.command() def compile_all(): from solar.core.resource import compiler diff --git a/solar/solar/core/handlers/puppet.py b/solar/solar/core/handlers/puppet.py index 917f8a03..b47fc9b2 100644 --- a/solar/solar/core/handlers/puppet.py +++ b/solar/solar/core/handlers/puppet.py @@ -91,6 +91,7 @@ class LibrarianPuppet(ResourceSSHMixin): self.resource, 'sudo', 'cat', '/var/tmp/puppet/Puppetfile' ) + log.debug('Puppetlabs file is: \n%s\n', puppetlabs) git = self.resource.args['git'].value