From 6c7df70d2eb5a1d4020150bbb7d8e874b0e8ed52 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 7 Jan 2015 04:01:21 +0100 Subject: [PATCH 01/12] Merge test scripts --- test-with-env.sh | 18 ------------------ test.sh | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 18 deletions(-) delete mode 100755 test-with-env.sh diff --git a/test-with-env.sh b/test-with-env.sh deleted file mode 100755 index 69880d3..0000000 --- a/test-with-env.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -if [ ! -d "./p-env/" ]; then - virtualenv p-env -fi - -source ./p-env/bin/activate - -TMP_DEPS=/tmp/prudentia_test_temp_deps_${RANDOM} -pip freeze -l > ${TMP_DEPS} -if ! cmp ./requirements.txt ${TMP_DEPS} > /dev/null 2>&1 -then - echo "Installing Python dependencies ..." - cat ${TMP_DEPS} - pip install -r ./requirements.txt -fi - -nosetests -c nose.cfg $@ diff --git a/test.sh b/test.sh index 0b62d9f..99f5595 100755 --- a/test.sh +++ b/test.sh @@ -1,3 +1,25 @@ #!/bin/bash +python -c 'import sys; print sys.real_prefix' 2>/dev/null && INVENV=1 || INVENV=0 + +if [ ${INVENV} == 0 ]; then + echo -e "No active Virtual Environment.\n" + if [ ! -d "./p-env/" ]; then + virtualenv p-env + fi + + source ./p-env/bin/activate + + TMP_DEPS=/tmp/prudentia_test_temp_deps_${RANDOM} + pip freeze -l > ${TMP_DEPS} + if ! cmp ./requirements.txt ${TMP_DEPS} > /dev/null 2>&1 + then + echo "Installing Python dependencies ..." + cat ${TMP_DEPS} + pip install -r ./requirements.txt + fi +else + echo -e "Virtual Env active.\n" +fi + nosetests -c nose.cfg $@ From 41d553a59d41bdaa7c19f3c7a1d42476d9d5f7f9 Mon Sep 17 00:00:00 2001 From: Craig Latta Date: Wed, 7 Jan 2015 12:11:27 +0100 Subject: [PATCH 02/12] Proofread README.rst. --- README.rst | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index be965ff..a3a5615 100644 --- a/README.rst +++ b/README.rst @@ -4,26 +4,26 @@ Prudentia is a Continuous Deployment toolkit written in Python. Mission ------- -Prudentia's mission is to help you to get production (or any other environment) ready in minutes instead of days, through +Prudentia's mission is to help you to get production (or any other environment) ready in minutes instead of days, by streamlining all the actions needed to provision your architectural components. Features -------- -Prudentia uses Ansible_ as main automation system, so it easily understand playbooks. -A playbook is one of the information needed to define a Prudentia Box. +Prudentia uses Ansible_ as its main automation system, so it easily understands Ansible playbooks. +A playbook is one of the components needed to define a Prudentia Box. Prudentia currently offers: -* A CLI_ (supporting auto-completion) used to interactively define Boxes and run operation on them +* a CLI_ (supporting auto-completion) used to interactively define Boxes and run operations on them * Here-Document_ format to script Prudentia environments -* Provision existing server that can be accessed trough SSH -* Manage the lifecycle of a Box that has been created through Prudentia -* Create Boxes using one of the available providers +* provisioning of an existing server that can be accessed trough SSH +* management of the lifecycle of a Box that has been created through Prudentia +* creating Boxes using one of these providers: * Vagrant * DigitalOcean - * Local - * Ssh + * local + * ssh Currently, all features work with Python 2.6 and 2.7. Work is under way to support Python 3.3+ in the same codebase. @@ -51,7 +51,7 @@ To uninstall: Box operations -------------- -Simple providers (e.g. LocalProvider or SshProvider) have the following operation available: +Simple providers (e.g. LocalProvider or SshProvider) have the following operations available: * register * unregister @@ -61,7 +61,7 @@ Simple providers (e.g. LocalProvider or SshProvider) have the following operatio * unset * provision -Factory providers (e.g. VagrantProvider or DigitalOceanProvider) extend simple providers and adds those operations: +Factory providers (e.g. VagrantProvider or DigitalOceanProvider) extend simple providers and add these operations: * create * restart @@ -73,7 +73,7 @@ Usage ----- We'll show a usage example of the SshProvider bundled with Prudentia. -**Make sure you have a server that you can ssh onto**. +**Make sure you have a server that you can ssh into**. .. code-block:: bash @@ -92,8 +92,8 @@ Let's start registering a new box:: Now Prudentia is asking for a playbook path, and this is actually an Ansible playbook. You can use one of the samples that you can find in the `examples/boxes` directory. -For instance the `tasks.yml` that will run some Ansible tasks that we've defined (those tasks are not that meaningful but -they are used as sanity check in our tests). +For instance, the `tasks.yml` that will run some Ansible tasks that we've defined (those tasks are not that meaningful, but +they are used as a sanity check in our tests). So let's continue using the `tasks.yml`:: @@ -106,15 +106,15 @@ So let's continue using the `tasks.yml`:: Box example -> (/path/to/prudentia/examples/boxes/tasks.yml, tasks-host, ip.of.your.server, _your_user_) added. -You will notice for some questions Prudentia gives us a suggested answer within `[ ]`. For instance the Box name is been -suggested as `tasks-host`, if you like the suggestion just press enter to choose it. +You will notice that, for some questions, Prudentia gives us a suggested answer within `[ ]`. For instance, the suggested Box name is +`tasks-host`. If you like the suggestion, just press enter to choose it. -So far we've registered a Prudentia Box that can be used to play around. If you want to check again the definition of it:: +So far we've registered a Prudentia Box that can be used to play around. If you want to check the definition again:: (Prudentia > Ssh) list example -> (/path/to/prudentia/examples/boxes/tasks.yml, tasks-host, ip.of.your.server, _your_user_) -Now that we have double checked that our Box has been registered, we can provision it:: +Now that we have double-checked that our Box has been registered, we can provision it:: (Prudentia > Ssh) provision example @@ -178,7 +178,7 @@ More Info --------- Here you can find a guide on how to use Prudentia to `provision a Digital Ocean droplet`_ with the StarterSquad website on it. -Another important source of information is `Iwein's post`_ that gives you an idea of what Continuous Delivery is and where +Another important source of information is `Iwein's post`_ that gives you an idea of what Continuous Delivery is, and where Prudentia fits into the flow. From 72aa3ac053caa18f13dd3dfe4b82fe3c1f3462f0 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 7 Jan 2015 13:17:34 +0100 Subject: [PATCH 03/12] Adds back box operation descriptions Configures temporary ansible log path --- README.rst | 38 +++++++++++++++++++++----------------- prudentia/ansible.cfg | 1 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index a3a5615..67e3c96 100644 --- a/README.rst +++ b/README.rst @@ -51,27 +51,28 @@ To uninstall: Box operations -------------- -Simple providers (e.g. LocalProvider or SshProvider) have the following operations available: +Simple providers (e.g. Local provider or SSH provider) have the following operations available: -* register -* unregister -* reconfigure -* list -* set -* unset -* provision +* *register*: adds a new box definition to the registry +* *unregister*: removes a box from the registry +* *reconfigure*: changes the definition of an existing box +* *list*: lists all boxes in the registry +* *set*: defines or override a playbook variable +* *unset*: removes variable +* *provision*: runs tasks defined in the playbook associated with a box -Factory providers (e.g. VagrantProvider or DigitalOceanProvider) extend simple providers and add these operations: +Factory providers (e.g. Vagrant provider or DigitalOcean provider) extend simple providers and add allow you to change +the box life cycle: -* create -* restart -* stop -* destroy -* phoenix (shortcut for stop -> destroy -> create -> start -> provision) +* *create*: instantiate a new instance based of the box definition +* *restart*: reloads the instance +* *stop*: shuts down the instance +* *destroy*: kill the instance +* *phoenix*: shortcut for stop -> destroy -> create -> start -> provision (citing `phoenix server`_ Martin Fowler's article) Usage ----- -We'll show a usage example of the SshProvider bundled with Prudentia. +We'll show a usage example of the SSH provider bundled with Prudentia. **Make sure you have a server that you can ssh into**. @@ -173,7 +174,10 @@ The same sequence of operations can be executed using the `Here-Document`_ input provision tasks-host EOF - + +This shows how to use the SSH provider. If you got curious enough I invite you to check out the other providers as well. + + More Info --------- Here you can find a guide on how to use Prudentia to `provision a Digital Ocean droplet`_ with the StarterSquad website on it. @@ -198,7 +202,7 @@ You can e-mail me at: .. _Ansible: https://github.com/ansible/ansible .. _CLI: http://en.wikipedia.org/wiki/Command-line_interface .. _Here-Document: http://en.wikipedia.org/wiki/Here_document#Unix_shells - +.. _phoenix server: http://martinfowler.com/bliki/PhoenixServer.html .. _provision a Digital Ocean droplet: http://www.startersquad.com/blog/simple-deployments-with-prudentia/ .. _Iwein's post: http://www.startersquad.com/blog/getting-ready-for-continuous-delivery/ diff --git a/prudentia/ansible.cfg b/prudentia/ansible.cfg index 7c4710c..4412835 100644 --- a/prudentia/ansible.cfg +++ b/prudentia/ansible.cfg @@ -1,6 +1,7 @@ [defaults] host_key_checking=False nocows=1 +log_path=/tmp/prudentia-ansible.log [paramiko_connection] record_host_keys=False From 1a162e23194b367d08850a652dcb7c1b72652a24 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 7 Jan 2015 23:57:15 +0100 Subject: [PATCH 04/12] Makes execution clear when running one off command Simplifies main cli Adds extra cli tests --- bin/prudentia | 8 ++------ prudentia/cli.py | 16 +++++++++++----- prudentia/simple.py | 2 +- tests/cli_test.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 tests/cli_test.py diff --git a/bin/prudentia b/bin/prudentia index 94f1237..2f4372c 100755 --- a/bin/prudentia +++ b/bin/prudentia @@ -25,12 +25,8 @@ if __name__ == "__main__": exit_error = 0 if len(sys.argv) > 1: - env = sys.argv[1] - one_cmd_executed = cli.do_use(env) - if not one_cmd_executed: - cli.cmdloop() - else: - exit_error = 0 if cli.env_cli.provider.provisioned else 1 + one_cmd_provisioned = cli.do_use(sys.argv[1], *sys.argv[2:]) + exit_error = 0 if one_cmd_provisioned else 1 else: cli.cmdloop() diff --git a/prudentia/cli.py b/prudentia/cli.py index ad62c14..4712fde 100644 --- a/prudentia/cli.py +++ b/prudentia/cli.py @@ -4,7 +4,6 @@ # Setting ansible config file environment variable as first thing os.environ['ANSIBLE_CONFIG'] = path.join(path.dirname(path.realpath(__file__)), 'ansible.cfg') -import sys from cmd import Cmd from digital_ocean import DigitalOceanCli @@ -40,18 +39,25 @@ def complete_use(self, text, line, begidx, endidx): else: return [e for e in Environments.keys() if e.startswith(text)] - def do_use(self, env): + def do_use(self, env, *args): + result = False if env in Environments.keys(): self.env_cli = Environments[env]() - if len(sys.argv) > 2: - cmd = ' '.join(sys.argv[2:]) + if args: + cmd = ' '.join(args) print "Executing: '{0}'\n".format(cmd) self.env_cli.onecmd(cmd) else: self.env_cli.cmdloop() + + result = self.env_cli.provider.provisioned else: print "Provider '{0}' NOT found.".format(env) - return not self.parent_loop + + # If this function was called inside a cmd loop the return values indicates whether execution will be terminated + # returning False will cause interpretation to continue. + # Otherwise the return value is the result of the provisioning command. + return False if self.parent_loop else result def do_EOF(self, line): print "\n\nBye!" diff --git a/prudentia/simple.py b/prudentia/simple.py index 4f3912c..96b5ad0 100644 --- a/prudentia/simple.py +++ b/prudentia/simple.py @@ -148,7 +148,7 @@ def __init__(self, name, general_type=None, box_extra_type=None): self.extra_vars = {'prudentia_dir': prudentia_python_dir()} self.tags = {} self.load_tags() - self.provisioned = None + self.provisioned = False def boxes(self): return self.env.boxes.values() diff --git a/tests/cli_test.py b/tests/cli_test.py new file mode 100644 index 0000000..60abe56 --- /dev/null +++ b/tests/cli_test.py @@ -0,0 +1,35 @@ +import unittest +from prudentia.cli import CLI + + +class TestCli(unittest.TestCase): + def setUp(self): + self.cli = CLI() + + + # CmdLoop tests return always False indicating that the execution will not be terminated + def test_loop_use_wrong_env(self): + self.cli.parent_loop = True + self.assertEqual(self.cli.do_use('whatever-env'), False) + + def test_loop_run_failing_provision(self): + self.cli.parent_loop = True + self.assertEqual(self.cli.do_use('local', 'provision', 'whatever-box'), False) + + def test_loop_run_list(self): + self.cli.parent_loop = True + self.assertEqual(self.cli.do_use('local', 'list'), False) + + + # Terminal tests will return the value of provider.provisioned + def test_terminal_use_wrong_env(self): + self.cli.parent_loop = False + self.assertEqual(self.cli.do_use('whatever-env'), False) + + def test_terminal_run_failing_provision(self): + self.cli.parent_loop = False + self.assertEqual(self.cli.do_use('local', 'provision', 'whatever-box'), False) + + def test_terminal_run_list(self): + self.cli.parent_loop = False + self.assertEqual(self.cli.do_use('local', 'list'), False) From 83a05a8cbb08b8faff684240e4e106c7d88085ce Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Thu, 8 Jan 2015 00:36:02 +0100 Subject: [PATCH 05/12] Makes provision accept multiple tags Fixes tags auto-completion filtering already used tags Adds tags to sample tasks --- examples/boxes/tasks.yml | 2 ++ prudentia/local.py | 7 +++---- prudentia/simple.py | 16 +++++++++------- tests/cli_test.py | 1 + tests/local_test.py | 7 ++++++- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/examples/boxes/tasks.yml b/examples/boxes/tasks.yml index d5119d5..807cb5e 100644 --- a/examples/boxes/tasks.yml +++ b/examples/boxes/tasks.yml @@ -7,9 +7,11 @@ tasks: - name: Uname command: uname -a + tags: uname - name: Shuffle debug: msg={{ item }} with_items: "{{ mylist | shuffle }}" + tags: shuffle - include: "{{ prudentia_dir }}/tasks/noop.yml" diff --git a/prudentia/local.py b/prudentia/local.py index eee33e8..f9041af 100644 --- a/prudentia/local.py +++ b/prudentia/local.py @@ -1,7 +1,6 @@ import logging import ansible.constants as C - from domain import Box from simple import SimpleProvider, SimpleCli from utils.io import input_value, input_path @@ -48,7 +47,7 @@ def reconfigure(self, previous_box): logging.exception('Box not reconfigured.') print '\nThere was some problem while reconfiguring the box: %s\n' % e - def provision(self, box, tag=None): + def provision(self, box, *tags): remote_user = C.DEFAULT_REMOTE_USER if box.remote_user: remote_user = box.remote_user @@ -61,8 +60,8 @@ def provision(self, box, tag=None): box.use_prudentia_lib = True only_tags = None - if tag: - only_tags = [tag] + if tags is not (): + only_tags = tags self.provisioned = run_playbook( playbook_file=box.playbook, diff --git a/prudentia/simple.py b/prudentia/simple.py index 96b5ad0..d64c5fc 100644 --- a/prudentia/simple.py +++ b/prudentia/simple.py @@ -1,3 +1,4 @@ +import logging from os.path import dirname import re import os @@ -23,6 +24,7 @@ def cmdloop(self, intro=None): try: Cmd.cmdloop(self, intro) except Exception as e: + logging.exception('Got a nasty error.') print '\nGot a nasty error: %s\n' % e def _get_box(self, box_name): @@ -34,6 +36,7 @@ def _get_box(self, box_name): return b def complete_box_names(self, text, line, begidx, endidx): + completions = [''] tokens = line.split(' ') action = tokens[0] box_name = tokens[1] @@ -50,8 +53,8 @@ def complete_box_names(self, text, line, begidx, endidx): completions = self.provider.tags[box_name][:] else: completions = [t for t in self.provider.tags[box_name] if t.startswith(text)] - else: - completions = [''] + current_tags = tokens[2:] + completions = [c for c in completions if c not in current_tags] return completions @@ -84,8 +87,7 @@ def do_provision(self, line): tokens = line.split(' ') box = self._get_box(tokens[0]) if box: - tag = tokens[1] if len(tokens) > 1 else None - self.provider.provision(box, tag) + self.provider.provision(box, *tokens[1:]) def help_unregister(self): @@ -217,7 +219,7 @@ def suggest_name(self, hostname): else: return hostname + '-' + str(random.randint(0, 100)) - def provision(self, box, tag=None): + def provision(self, box, *tags): remote_user = C.DEFAULT_REMOTE_USER if box.remote_user: remote_user = box.remote_user @@ -229,8 +231,8 @@ def provision(self, box, tag=None): transport = 'paramiko' only_tags = None - if tag: - only_tags = [tag] + if tags is not (): + only_tags = tags self.provisioned = run_playbook( playbook_file=box.playbook, diff --git a/tests/cli_test.py b/tests/cli_test.py index 60abe56..d5e34cf 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,4 +1,5 @@ import unittest + from prudentia.cli import CLI diff --git a/tests/local_test.py b/tests/local_test.py index f139c9f..d112a51 100644 --- a/tests/local_test.py +++ b/tests/local_test.py @@ -17,10 +17,15 @@ def setUp(self): def test_provision_sample_task(self): r_box = Box('local-testbox', self.tests_path + '/../examples/boxes/tasks.yml', 'tasks-host', '127.0.0.1') self.provider.add_box(r_box) + self.provider.provision(r_box) - self.provider.remove_box(r_box) self.assertEqual(self.provider.provisioned, True) + self.provider.provision(r_box, 'uname', 'shuffle') + self.assertEqual(self.provider.provisioned, True) + + self.provider.remove_box(r_box) + def test_should_list_tag(self): e_box = Box('simple-box', self.tests_path + '/dev.yml', 'hostname', '0.0.0.0') self.provider.load_tags(e_box) From 629114dd407b59df14453220dbd7e4479419fa98 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Sat, 10 Jan 2015 14:12:42 +0100 Subject: [PATCH 06/12] Adds general message the fist time the user is asked to input some value Restores change cwd in the Prudentia script --- prudentia.sh | 14 ++++++++++++++ prudentia/digital_ocean.py | 4 ++-- prudentia/local.py | 4 ++-- prudentia/ssh.py | 8 ++++---- prudentia/utils/io.py | 10 ++++++++++ prudentia/vagrant.py | 4 ++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/prudentia.sh b/prudentia.sh index 8f6cbc8..b159120 100755 --- a/prudentia.sh +++ b/prudentia.sh @@ -1,5 +1,19 @@ #!/bin/bash +#http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in +SOURCE="${BASH_SOURCE[0]}" +# resolve $SOURCE until the file is no longer a symlink +while [ -h "${SOURCE}" ]; do + DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" + SOURCE="$(readlink "${SOURCE}")" + # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located + [[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" +done +DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" + +# Change cwd in Prudentia dir +cd ${DIR} + if [ -z "$( which python )" ]; then echo "Please, install Python (>=2.6)." exit 1 diff --git a/prudentia/digital_ocean.py b/prudentia/digital_ocean.py index 353a0f3..ee493db 100644 --- a/prudentia/digital_ocean.py +++ b/prudentia/digital_ocean.py @@ -72,7 +72,7 @@ def register(self): print "\nBox %s added." % box except Exception as e: logging.exception('Box not added.') - print '\nThere was some problem while adding the box: %s\n' % e + print '\nError: %s\n' % e def _print_object_id_name(self, objs): return '\n'.join([str(o['id']) + ' -> ' + o['name'] for o in objs]) @@ -111,7 +111,7 @@ def reconfigure(self, previous_box): print "\nBox %s reconfigured." % box except Exception as e: logging.exception('Box not reconfigured.') - print '\nThere was some problem while reconfiguring the box: %s\n' % e + print '\nError: %s\n' % e def create(self, box): g = self.env.general diff --git a/prudentia/local.py b/prudentia/local.py index f9041af..10ed342 100644 --- a/prudentia/local.py +++ b/prudentia/local.py @@ -31,7 +31,7 @@ def register(self): print "\nBox %s added." % box except Exception as e: logging.exception('Box not added.') - print '\nThere was some problem while adding the box: %s\n' % e + print '\nError: %s\n' % e def reconfigure(self, previous_box): try: @@ -45,7 +45,7 @@ def reconfigure(self, previous_box): print "\nBox %s reconfigured." % box except Exception as e: logging.exception('Box not reconfigured.') - print '\nThere was some problem while reconfiguring the box: %s\n' % e + print '\nError: %s\n' % e def provision(self, box, *tags): remote_user = C.DEFAULT_REMOTE_USER diff --git a/prudentia/ssh.py b/prudentia/ssh.py index ee25a1e..e5dfef0 100644 --- a/prudentia/ssh.py +++ b/prudentia/ssh.py @@ -25,7 +25,7 @@ def register(self): playbook = input_path('absolute playbook path') hostname = self.fetch_box_hostname(playbook) name = input_value('box name', self.suggest_name(hostname)) - ip = input_value('address of the instance') + ip = input_value('ip or hostname of the instance') user = input_value('remote user', C.active_user) pwd = input_value('password for the remote user', default_description='ssh key', mandatory=False, hidden=True) @@ -34,7 +34,7 @@ def register(self): print "\nBox %s added." % box except Exception as e: logging.exception('Box not added.') - print '\nThere was some problem while adding the box: %s\n' % e + print '\nError: %s\n' % e def reconfigure(self, previous_box): try: @@ -42,7 +42,7 @@ def reconfigure(self, previous_box): playbook = input_path('absolute playbook path', previous_box.playbook) hostname = self.fetch_box_hostname(playbook) - ip = input_value('address of the instance', previous_box.ip) + ip = input_value('ip or hostname of the instance', previous_box.ip) user = input_value('remote user', previous_box.remote_user) if previous_box.remote_pwd: pwd = input_value('password for the remote user', default_value=previous_box.remote_pwd, default_description='*****', mandatory=False, hidden=True) @@ -54,4 +54,4 @@ def reconfigure(self, previous_box): print "\nBox %s reconfigured." % box except Exception as e: logging.exception('Box not reconfigured.') - print '\nThere was some problem while reconfiguring the box: %s\n' % e + print '\nError: %s\n' % e diff --git a/prudentia/utils/io.py b/prudentia/utils/io.py index 6c82c5c..8535296 100644 --- a/prudentia/utils/io.py +++ b/prudentia/utils/io.py @@ -13,7 +13,16 @@ def xstr(s): return '' if s is None else str(s) +def first_time_input(): + if first_time_input.show: + print '\nPlease enter values for the following settings, ' \ + 'press \'Enter\' to accept the default value (if its given in brackets).\n' + first_time_input.show = False +first_time_input.show = True + + def input_value(topic, default_value=None, default_description=None, mandatory=True, hidden=False): + first_time_input() default = default_description if default_description else default_value if default: input_msg = 'Specify the %s [default: %s]: ' % (topic, default) @@ -53,6 +62,7 @@ def input_path(topic, default_value=None, default_description=None, mandatory=Tr def input_yes_no(topic, default='n'): + first_time_input() input_msg = 'Do you want to %s? [default: %s]: ' % (topic, default.upper()) answer = raw_input(input_msg).strip() if not len(answer): diff --git a/prudentia/vagrant.py b/prudentia/vagrant.py index 079bb28..6c02094 100644 --- a/prudentia/vagrant.py +++ b/prudentia/vagrant.py @@ -52,7 +52,7 @@ def register(self): print "\nBox %s added." % box except Exception as e: logging.exception('Box not added.') - print '\nThere was some problem while adding the box: %s\n' % e + print '\nError: %s\n' % e def add_box(self, box): SimpleProvider.add_box(self, box) @@ -82,7 +82,7 @@ def reconfigure(self, previous_box): print "\nBox %s reconfigured." % box except Exception as e: logging.exception('Box not reconfigured.') - print '\nThere was some problem while reconfiguring the box: %s\n' % e + print '\nError: %s\n' % e def _input_shares(self): shares = [] From 2b02477bad982565a6800ea7b354c0e1ca8beff7 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 20 Jan 2015 09:22:28 +0100 Subject: [PATCH 07/12] Enables python unbuffered setting env var --- bin/prudentia | 1 + prudentia.sh | 2 +- prudentia/cli.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/prudentia b/bin/prudentia index 2f4372c..134bcd1 100755 --- a/bin/prudentia +++ b/bin/prudentia @@ -9,6 +9,7 @@ from prudentia.cli import CLI if __name__ == "__main__": + os.environ['PYTHONUNBUFFERED'] = '1' PRUDENTIA_USER_DIR = path.join(path.expanduser('~'), 'prudentia') if not os.path.exists(PRUDENTIA_USER_DIR): diff --git a/prudentia.sh b/prudentia.sh index b159120..fdbf9b5 100755 --- a/prudentia.sh +++ b/prudentia.sh @@ -37,4 +37,4 @@ then pip install -r ./requirements.txt fi -PYTHONPATH=. PYTHONUNBUFFERED=1 bin/prudentia "$@" 2>&1 +PYTHONPATH=. bin/prudentia "$@" 2>&1 diff --git a/prudentia/cli.py b/prudentia/cli.py index 4712fde..793f8bb 100644 --- a/prudentia/cli.py +++ b/prudentia/cli.py @@ -1,7 +1,7 @@ import os from os import path -# Setting ansible config file environment variable as first thing +# Setting Ansible config file environment variable as first thing os.environ['ANSIBLE_CONFIG'] = path.join(path.dirname(path.realpath(__file__)), 'ansible.cfg') from cmd import Cmd From b9f521b4d59fb6f84671fa8a75409564b5327ea4 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 20 Jan 2015 09:31:08 +0100 Subject: [PATCH 08/12] Adds package version info to Readme --- README.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 67e3c96..d884208 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Prudentia |build-status| |pypi-downloads| -========================================= +Prudentia |build-status| |pypi-downloads| |pypi-version| +======================================================== Prudentia is a Continuous Deployment toolkit written in Python. Mission @@ -173,6 +173,8 @@ The same sequence of operations can be executed using the `Here-Document`_ input _your_user_ provision tasks-host + + unregister tasks-host EOF This shows how to use the SSH provider. If you got curious enough I invite you to check out the other providers as well. @@ -214,3 +216,6 @@ You can e-mail me at: .. |pypi-downloads| image:: https://pypip.in/d/prudentia/badge.png :target: https://pypi.python.org/pypi/prudentia :alt: PyPI: Package status +.. |pypi-version| image:: https://badge.fury.io/py/prudentia.png + :target: http://badge.fury.io/py/prudentia + :alt: PyPI: Package version From 1f159e452ed429cce8c670e0ab9b351c40c80173 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 21 Jan 2015 15:31:25 +0100 Subject: [PATCH 09/12] Enables symlinks feature in VirtualBox --- prudentia/Vagrantfile.j2 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prudentia/Vagrantfile.j2 b/prudentia/Vagrantfile.j2 index c1fc64e..ec6d5ba 100644 --- a/prudentia/Vagrantfile.j2 +++ b/prudentia/Vagrantfile.j2 @@ -11,6 +11,9 @@ Vagrant.configure("2") do |config| vb.customize ["modifyvm", :id, "--memory", "{{ b.extra.mem }}"] vb.customize ["modifyvm", :id, "--vtxvpid", "off"] vb.customize ["modifyvm", :id, "--cpus", "1"] + + # Enables symlinks feature in VirtualBox (https://github.com/mitchellh/vagrant/issues/713#issuecomment-4416384) + vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"] end {% for s in b.extra.shares %} @@ -19,4 +22,4 @@ Vagrant.configure("2") do |config| end {% endfor %} -end \ No newline at end of file +end From eeec6feca604c20b41f46c739ce6efb220f0073f Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 21 Jan 2015 16:17:59 +0100 Subject: [PATCH 10/12] feature(digital-ocean): Registers an existing droplet fixes(digital-ocean): Do not recreate droplet when user reconfigures it without destroying it --- prudentia/digital_ocean.py | 101 +++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/prudentia/digital_ocean.py b/prudentia/digital_ocean.py index ee493db..c481ec0 100644 --- a/prudentia/digital_ocean.py +++ b/prudentia/digital_ocean.py @@ -8,7 +8,7 @@ from domain import Box from factory import FactoryProvider, FactoryCli from utils.provisioning import run_module, local_inventory, create_user -from utils.io import input_yes_no, input_value, input_path +from utils.io import input_yes_no, input_value, input_path, xstr class DigitalOceanCli(FactoryCli): @@ -43,29 +43,54 @@ def _input_general_env_conf(self): def register(self): try: + name = None + ip = None + ext = DOExt() + + if input_yes_no('register an existing droplet'): + droplet_id = input_value('droplet id') + droplet_info = self.manager.show_droplet(droplet_id) + ext.id = droplet_id + name = droplet_info['name'] + created = droplet_info['created_at'] + print '\nFound droplet \'{0}\' (created {1})'.format(name, created) + ip = xstr(droplet_info['ip_address']) + print 'IP: %s' % ip + ext.image = droplet_info['image_id'] + print 'Image: %s' % ext.image + ext.size = droplet_info['size_id'] + print 'Size: %s' % ext.size + ext.region = droplet_info['region_id'] + print 'Region: %s\n' % ext.region + playbook = input_path('absolute playbook path') hostname = self.fetch_box_hostname(playbook) - name = input_value('box name', self.suggest_name(hostname)) - ip = 'TBD' + + if not name: + name = input_value('box name', self.suggest_name(hostname)) + user = input_value('remote user', C.active_user) - ext = DOExt() - all_images = self.manager.all_images() - print '\nAvailable images: \n%s' % self._print_object_id_name(all_images) - ext.image = input_value('image', self.DEFAULT_IMAGE_ID) + if not ext.image: + all_images = self.manager.all_images() + print '\nAvailable images: \n%s' % self._print_object_id_name(all_images) + ext.image = input_value('image', self.DEFAULT_IMAGE_ID) - all_sizes = self.manager.sizes() - print '\nAvailable sizes: \n%s' % self._print_object_id_name(all_sizes) - ext.size = input_value('size', self.DEFAULT_SIZE_ID) + if not ext.size: + all_sizes = self.manager.sizes() + print '\nAvailable sizes: \n%s' % self._print_object_id_name(all_sizes) + ext.size = input_value('size', self.DEFAULT_SIZE_ID) - all_keys = self.manager.all_ssh_keys() - print '\nAvailable keys: \n%s' % self._print_object_id_name(all_keys) - default_keys = ','.join([str(k['id']) for k in all_keys]) - ext.keys = input_value('keys', default_keys) + if not ext.region: + all_regions = self.manager.all_regions() + print '\nAvailable regions: \n%s' % self._print_object_id_name(all_regions) + ext.region = input_value('region', self.DEFAULT_REGION_ID) - all_regions = self.manager.all_regions() - print '\nAvailable regions: \n%s' % self._print_object_id_name(all_regions) - ext.region = input_value('region', self.DEFAULT_REGION_ID) + if not ext.keys: + all_keys = self.manager.all_ssh_keys() + print '\nAvailable keys: \n%s' % self._print_object_id_name(all_keys) + default_keys = ','.join([str(k['id']) for k in all_keys]) + ext.keys = input_value('keys', default_keys) box = Box(name, playbook, hostname, ip, user, extra=ext) self.add_box(box) @@ -86,25 +111,29 @@ def reconfigure(self, previous_box): playbook = input_path('absolute playbook path', previous_box.playbook) hostname = self.fetch_box_hostname(playbook) - ip = 'TBD' + ip = previous_box.ip user = input_value('remote user', previous_box.remote_user) - ext = DOExt() - all_images = self.manager.all_images() - print '\nAvailable images: \n%s' % self._print_object_id_name(all_images) - ext.image = input_value('image', previous_box.extra.image) + if not previous_box.extra.id: + ext = DOExt() + ext.id = previous_box.extra.id + all_images = self.manager.all_images() + print '\nAvailable images: \n%s' % self._print_object_id_name(all_images) + ext.image = input_value('image', previous_box.extra.image) - all_sizes = self.manager.sizes() - print '\nAvailable sizes: \n%s' % self._print_object_id_name(all_sizes) - ext.size = input_value('size', previous_box.extra.size) + all_sizes = self.manager.sizes() + print '\nAvailable sizes: \n%s' % self._print_object_id_name(all_sizes) + ext.size = input_value('size', previous_box.extra.size) - all_keys = self.manager.all_ssh_keys() - print '\nAvailable keys: \n%s' % self._print_object_id_name(all_keys) - ext.keys = input_value('keys', previous_box.extra.keys) + all_keys = self.manager.all_ssh_keys() + print '\nAvailable keys: \n%s' % self._print_object_id_name(all_keys) + ext.keys = input_value('keys', previous_box.extra.keys) - all_regions = self.manager.all_regions() - print '\nAvailable regions: \n%s' % self._print_object_id_name(all_regions) - ext.region = input_value('region', previous_box.extra.region) + all_regions = self.manager.all_regions() + print '\nAvailable regions: \n%s' % self._print_object_id_name(all_regions) + ext.region = input_value('region', previous_box.extra.region) + else: + ext = previous_box.extra box = Box(previous_box.name, playbook, hostname, ip, user, extra=ext) self.add_box(box) @@ -113,6 +142,11 @@ def reconfigure(self, previous_box): logging.exception('Box not reconfigured.') print '\nError: %s\n' % e + def add_box(self, box): + if not box.ip: + self.create(box) + super(FactoryProvider, self).add_box(box) + def create(self, box): g = self.env.general e = box.extra @@ -133,7 +167,8 @@ def create(self, box): box.ip = droplet['ip_address'] print 'Droplet created with id: {0} -> {1}\n'.format(box.extra.id, box.ip) else: - print 'Droplet {0} already exists.'.format(e.id) + info = self.manager.show_droplet(e.id) + print 'Droplet {0} already exists - status: {1}.'.format(e.id, info['status']) create_user(box) def start(self, box): @@ -150,6 +185,8 @@ def stop(self, box): def destroy(self, box): if input_yes_no('destroy the droplet \'{0}\''.format(box.name)): box_id = box.extra.id + box.ip = None + box.extra.id = None print 'Destroying droplet %s ...' % box_id self.manager.destroy_droplet(box_id, scrub_data=True) From 7339307f32f3683889105fa48454bf55a73ec39b Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 3 Feb 2015 18:20:42 +0100 Subject: [PATCH 11/12] Adds release history and authors. Appends release history to PyPI package description. --- AUTHORS.rst | 17 ++++++++ HISTORY.rst | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 4 +- 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 AUTHORS.rst create mode 100644 HISTORY.rst diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..fd8620c --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,17 @@ +Prudentia is written and maintained by Tiziano Perrucci and various contributors: + +Development Lead +```````````````` + +- Tiziano Perrucci `@TizianoPerrucci `_ + + +Patches, Suggestions and Contributions +`````````````````````````````````````` + +- Iwein Fuld `@iwein `_ +- Dmitry Evseev `@dmitryevseev `_ +- Albert Sikkema `@asikkema `_ +- Anna Nachesa `@ashalynd `_ +- Sebastian Sastre `@sebastianconcept `_ +- Craig Latta `@ccrraaiigg `_ diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..a6b4657 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,110 @@ +Release History +--------------- + +0.7 (2015-02-03) +++++++++++++++++ + +**Bugfixes** + +- Fixes stop recreation DigitalOcean droplet when user reconfigures box without destroying it. + +**Improvements** + +- Makes provision accept multiple tags. +- Suggests tags during auto-completion filtering out the ones that have already been selected. +- Enables symlinks feature in VirtualBox. +- Registers an existing DigitalOcean droplet using the id. + +**Misc** + +- Adds History and Authors. + +0.6 (2015-01-07) +++++++++++++++++ + +**Bugfixes** + +- Fix creation user dir. + +**Documentation** + +- Described properly box operations. + +0.5 (2015-01-07) +++++++++++++++++ + +**Bugfixes** + +- Fixes error when running an action against a non existing box. + +**Improvements** + +- Drops execution of the script to install Vagrant. +- Publishes Prudentia on PyPI. +- Adds Python 2.6 to Travis build options. +- Refactor nodejs bundled task to use nvm (#11). +- Hides password when user enters it during box definition (#10). +- Executes extra checks when user inputs file paths (#8). +- Updates Readme doc. +- Updates and cleans up examples. +- Creates Local Provider. +- Adds bundled tasks: fontforge, opencv, noop, postgres, sbt, ssl-self-certificate, timezone. + +**Behavioral Changes** + +- Restructures python packages. +- Moves Prudentia environments directory under user home. +- Avoids check and install Vagrant package when using Vagrant Provider. + +**Misc** + +- Adds license. + +0.4 (2014-02-09) +++++++++++++++++ + +**Bugfixes** + +- Fixes several issue with Vagrantfile. +- Fixes provisioning non existing box. + +**Improvements** + +- Adds set/unset action used to set an environment variable. +- Sets default for yes/no question if no answer was given. +- Integrates Travis CI. +- Suggest box name based on playbook hosts name. +- Exit with error code 1 if one off cmd provisioning fails. +- Add example box. + +0.3 (2014-01-16) +++++++++++++++++ + +**Improvements** + +- Creates DigitalOcean Provider and Ssh Provider. +- Introduces Environment and Box entities. +- Adds bundled tasks: chrome, protractor, mongodb, python. +- Introduces bash utility. + +0.2 (2013-10-15) +++++++++++++++++ + +**Bugfixes** + +- Fixes provision without tags. + +**Improvements** + +- Loads box playbook tags and use in action argument suggestion. + +0.1 (2013-09-17) +++++++++++++++++ + +**Beginning** + +- Adds script to install Vagrant and Ansible. +- Creates Vagrant Provider with basic commands: add, remove, provision, phoenix, restart, destroy. +- Adds bundled tasks: common-setup, git, github, java7, jenkins, mercurial, mysql, nginx, nodejs, redis, ruby, sbt, ssh-key, tor. +- Provides tags support for provision action. +- Adds shared folder to Vagrant box definition. diff --git a/setup.py b/setup.py index 717ba4a..84697e3 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,8 @@ with open('README.rst', 'r', 'utf-8') as f: readme = f.read() +with open('HISTORY.rst', 'r', 'utf-8') as f: + history = f.read() setup( name='prudentia', @@ -22,7 +24,7 @@ description='Continuous Deployment toolkit.', author=__author__, author_email='tiziano@startersquad.com', - long_description=readme, + long_description=readme + '\n\n' + history, url='https://github.com/StarterSquad/prudentia', license='MIT', install_requires=['ansible', 'dopy', 'boto'], From 57a6f51f483e81a2c100e7682e65184d0828bdd2 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Wed, 4 Feb 2015 20:00:21 +0100 Subject: [PATCH 12/12] Bumps version to 0.7 --- prudentia/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prudentia/__init__.py b/prudentia/__init__.py index 7ab2364..d6deb30 100644 --- a/prudentia/__init__.py +++ b/prudentia/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.6' +__version__ = '0.7' __author__ = 'Tiziano Perrucci'